数据结构:算法的时间复杂度和空间复杂度

news2025/6/18 1:19:19

Hello各位老铁们!我们又见面了,大家最近有没有坚持学习和敲代码呢?在这里小编就要督促一下大家了,我们每一天都敲一两道题,这样子久而久之的坚持下去你就会发现你已经在不知不觉的过程中变成了一个大佬。敲代码使我们快乐!

回归正文,那么本篇我们来了解一下数据结构。 

1.什么是数据结构 

数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。 

2.什么是算法? 

算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。

在多数情况下数据结构和算法是不分家的!

3.数据结构和算法的重要性 

关于数据结构和算法现在都成了大多数人的追求,因为时代在变化,随着技术的不断提升,许多大厂在招聘的时候都喜欢以算法编程题来作为考察目的,由此可以看出,现在对于程序猿的代码能力要求是非常高的,关于算法的水平提升不并不能在短时间内提升,需要经过长期的积累,与练习,所以呢要找到好offer,就抓紧时间联系起来吧!

4.如何学习好算法和数据结构呢?

1.以代码为基础,然后进行大量的练习,说的夸张一点,学完算法和数据结构有黑眼圈都是很正常的事情!

2.要多画图思考,画图可以将我们的逻辑呈现出来,更方便有利于我们做题和思考。 

5.算法的效率 

关于一道题的算法有很多种,但是往往我们需要算法效率最高的那一种,那么我们怎么样来判断一个算法的效率呢? 

5.1如何衡量一个算法的好坏 

就拿斐波那契数列来举例说明:

long long fib(int n)
{
	if (n < 3)
		return 1;

	return fib(n-1) + fib(n - 2);
}

斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?

5.2 算法的复杂度

算法在编写成可执行程序后,运行时需要耗费时间资源空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。


时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。

在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

关于复杂度的考察在一些校招的企业面试也作为考察的方向 

 6.时间复杂度

6.1时间复杂度的概念 

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?

是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。

也就是说只要找到某条语句与问题规模N之间的关系就可以通过数学表达式来计算出算法的时间复杂度。

举一个简单的例子:

计算一下Func1中++count语句总共执行了多少次?

void Func1(int N)
{
	int count = 0;
	for (int i = 0; i < N; ++i)
	{
		for (int j = 0; j < N; ++j)
		{
			++count;
		}
	}
	for (int k = 0; k < 2 * N; ++k)
	{
		++count;
	}
	int M = 10;
	while (M--)
	{
		++count;
	}
	printf("%d\n", count);
}

Func1 执行的基本操作次数 :
                                   F(N) = N^2 + 2*N + 10

N = 10 F(N) = 130
N = 100 F(N) = 10210
N = 1000 F(N) = 1002010


实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法

6.2大O的渐进表示法

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:


1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。

了解了大O渐进表示法,那么我们再回过头来看我们的Func1的执行次数:

                                                                F(N) = N^2 

使用大O表示法:Func1的时间复杂度位: O(N^2)

N = 10 F(N) = 100
N = 100 F(N) = 10000
N = 1000 F(N) = 1000000
 

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
 

另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)


例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

6.3常见的时间复杂度计算练习

实例1 

// 计算Func2的时间复杂度?
void Func2(int N)
{
	int count = 0;
	for (int k = 0; k < 2 * N; ++k)
	{
		++count;
	}
	int M = 10;
	while (M--)
	{
		++count;
	}
	printf("%d\n", count);
}

 F(N) = N*2 + 10

所以Func2的时间复杂度为:O(N)  

实例2

 计算Func3的时间复杂度?
void Func3(int N, int M)
{
	int count = 0;
	for (int k = 0; k < M; ++k)
	{
		++count;
	}
	for (int k = 0; k < N; ++k)
	{
		++count;
	}
	printf("%d\n", count);
}

F(N) = (N + M)

所以Func3的时间复杂度为:O(N + M)

这里的N和M没有任何的关系

实例3 

// 计算Func4的时间复杂度?
void Func4(int N)
{
	int count = 0;
	for (int k = 0; k < 100; ++k)
	{
		++count;
	}
	printf("%d\n", count);
}

可以看到这个程序执行了100次,但是执行的次数和N没有关系,所以可以说它执行了常数次,因此执行常数次的程序时间复杂度为O(1)

即Func4的时间复杂度为O(N)

实例4 

// 计算strchr的时间复杂度?
const char* strchr(const char* str, int character);

strchr函数是在一个字符串中找一个字符,因此决定它执行次数的因素就是字符串的长度,最坏的情况可能就是遍历完整个字符串才将这个字符找到了。

所以strchr函数的时间复杂度为O(N)

实例5 

// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{
	assert(a);
	for (size_t end = n; end > 0; --end)
	{
		int exchange = 0;
		for (size_t i = 1; i < end; ++i)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		if (exchange == 0)
			break;
	}
}

冒泡排序算法要进行排序最好的情况执行了1次,最坏的情况需要排序n^2次

所以BubbleSort函数的时间复杂度为:O(N^2)

实例6 

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
	assert(a);
	int begin = 0;
	int end = n - 1;
	// [begin, end]:begin和end是左闭右闭区间,因此有=号
	while (begin <= end)
	{
		int mid = begin + ((end - begin) >> 1);
		if (a[mid] < x)
			begin = mid + 1;
		else if (a[mid] > x)
			end = mid - 1;
		else
			return mid;
	}
	return -1;
}

这是一个二分查找算法,每一次查找都会使区间变为一半,在这里我们假设查找了x次,那么就表示给N连续除了x个2,因此就可以得到一个表达式:2^x = N,那我们转化一下就可以得到x = logN,所以二分查找算法的时间复杂度为:O(logN)

 实例7

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
	if (0 == N)
		return 1;
	return Fac(N - 1) * N;
}

实例8 

//计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
	if (0 == N)
		return 1;
	for (size_t i = 0; i < N; i++)
	{
		//....
	}
	return Fac(N - 1) * N;
}

 实例9

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
	if (N < 3)
		return 1;
	return Fib(N - 1) + Fib(N - 2);
}

关于递归的斐波那契数列的时间复杂度的计算要涉及到二叉树相关的概念:

 

 所以递归斐波那契数列的时间复杂度为O(2^N)

7.空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用额外存储空间大小的量度
空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数
空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

实例1

// 计算BubbleSort的空间复杂度?
void BubbleSort(int* a, int n)
{
	assert(a);
	for (size_t end = n; end > 0; --end)
	{
		int exchange = 0;
		for (size_t i = 1; i < end; ++i)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		if (exchange == 0)
			break;
	}
}

在整个冒泡排序的过程中额外创建的变量都是常数个,所以BubbleSort函数的空间复杂度是O(1)

实例2 

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{
	if (n == 0)
		return NULL;
	long long* fibArray = (long long*)malloc((n + 1) * sizeof(long long));
	fibArray[0] = 0;
	fibArray[1] = 1;
	for (int i = 2; i <= n; ++i)
	{
		fibArray[i] = fibArray[i - 1] + fibArray[i - 2];
	}
	return fibArray;
}

可以看到在这个函数中fibArray是由malloc开辟来的,malloc函数开辟的空间中有n+1个空间,所以该函数的空间复杂度为O(N)

实例3

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
	if (N == 0)
		return 1;
	return Fac(N - 1) * N;
}

 这里就要用到关于函数栈帧的创建与销毁相关的知识,调用函数的时候需要进行栈空间的开辟,所以这里调用了N次Fac函数,创建了N个函数栈帧空间,每一个空间中都有常数个变量,所以整个过程中额外使用的空间是常数倍的N,因此Fac函数的空间复杂度为:O(N)

 

实例4 

// 计算斐波那契递归Fib的空间复杂度?
long long Fib(size_t N)
{
	if (N < 3)
		return 1;
	return Fib(N - 1) + Fib(N - 2);
}

函数在调用时会创建函数栈帧,当不使用时就会销毁,在递归这个斐波那契数列中是先递归从N到1,然后再往前面返回,在往前返回的时候会销毁之前创建的函数栈帧,当下一次再创建的时候还是用的同一块空间,我们可以来验证一下:

#include <stdio.h>

void test1()
{
	int b = 0;
	printf("%p\n", &b);
}
void test2()
{
	int c = 0;
	printf("%p\n", &c);
}
int main()
{
	int a = 0;
	printf("%p\n", &a);
	test1();
	test2();
	return 0;
}

 可以看到在调用两个函数时里面创建的变量的地址都是一样的。所以函数栈帧会临时创建临时销毁,所以递归的斐波那契数列一共创建了N个函数栈帧,所以它的空间复杂度为O(N)

 8.常见复杂度的对比

 


 

关于算法的时间复杂度和空间复杂度就到此结束啦,大家如果喜欢,请献上你的三连,感谢各位老铁!! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/411865.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

unity蒙板测试例子——传送门

返回目录 Unity蒙板测试例子——传送门一、例子介绍 大家好&#xff0c;我是阿赵。 蒙板测试&#xff08;Stencil Test&#xff09;是渲染管线的合并输出环节里面的一种测试&#xff0c;和透明度测试或者深度测试一样&#xff0c;它实际上也是控制颜色值是否应该显示出来的一种…

natapp + nginx 实现内网穿透

环境是我本地的win10 第一步&#xff1a;下载nginx压缩包并解压(这个自行百度吧) 第二步&#xff1a; 修改nginx的配置文件&#xff1a; 在最下方添加&#xff1a; #testserver {listen 8081;#你要映射的端口server_name localhost; #使用本地IPlocation / {proxy_pass…

面试官:如何搭建Prometheus和Grafana对业务指标进行监控?

Prometheus和Grafana都是非常流行的开源监控工具&#xff0c;可以协同使用来实现对各种应用程序、系统、网络和服务器等的监视和分析。 下面对Prometheus和Grafana进行简要介绍&#xff1a; Prometheus Prometheus是一款开源、云原生的系统和服务监控工具&#xff0c;它采用p…

戴尔惠普联想笔记本性价比(戴尔和联想笔记本性价比)

联想。 联想做笔记本时间长&#xff0c;售后、质量都不错&#xff0c;追求稳定&#xff0c;性价比合理&#xff0c;长久使用考虑&#xff0c;不会有太大笔记本毛病。 联想1984就开始进入笔记本电脑行业&#xff0c;由中科院投资&#xff0c;后来收购了IBM之后&#xff0c;笔记…

第13届蓝桥杯省赛真题剖析-2022年4月17日Scratch编程初中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第122讲。 第13届蓝桥杯省赛举办了两次&#xff0c;这是2022年4月17日举行的第一次省赛&#xff0c;比赛仍然采取线上形…

Air32F103CBT6|CCT6|KEIL-uVsion5|本地编译|STClink|(6)、Air32F103编译下载

目录 一、环境搭建 准备工作 安装支持包 二、新建工程 添加外设库支持 测试代码 三、下载烧录 一、环境搭建 准备工作 安装MDK5&#xff0c;具体方法请百度&#xff0c;安装后需要激活才能编译大文件 下载安装AIR32F103的SDK&#xff1a;luatos-soc-air32f103: Air32f…

FL Studio2023中文版本水果编曲工具

编曲、剪辑、录音、混音&#xff0c;23余年的技术积淀和实力研发&#xff0c;FL Studio 已经从电音领域破圈&#xff0c;成功蜕变为全球瞩目的全能DAW&#xff0c;把电脑变成全功能音乐工作室&#xff0c;接下来我们会为您一一展示FL Studio 21也叫做水果音乐编曲软件&#xff…

Chat GPT太强,多种玩法曝光

1、模拟面试、考试&#xff0c;备考等&#xff0c;以面试为例&#xff0c;让它扮演一个角色 先让它扮演互联网行业的商业分析师 2、写各种发言稿、文章、报告&#xff0c;给它你的明确要求和目标&#xff0c;以发言稿为例&#xff0c;输入继续&#xff0c;可以持续优化 3、…

报名截至在即 | “泰迪杯”挑战赛最后一场赛前指导直播!

为推广我国高校数据挖掘实践教学&#xff0c;培养学生数据挖掘的应用和创新能力&#xff0c;增加校企交流合作和信息共享&#xff0c;提升我国高校的教学质量和企业的竞争能力&#xff0c;第十一届“泰迪杯”数据挖掘挑战赛&#xff08;以下简称挑战赛&#xff09;已于2023年3月…

白银实时行情操作中的一些错误及其解决办法(下)

小编根据大师&#xff0c;网络上的高手以及自己的经验整理出的一些交易中典型的错误&#xff0c;投资者可以参考参考&#xff0c;有则改之无则加勉~续上文…… 问题三&#xff1a;长线获利的交易不容易坚持同时陷入盘整或亏损的交易&#xff08;特别是大仓持有的品种&#xff…

银行数字化转型导师坚鹏:ChatGPT解密与银行应用案例

ChatGPT解密与银行应用案例 ——开启人类AI新纪元 打造数字化转型新利器 课程背景&#xff1a; 很多企业和员工存在以下问题&#xff1a; 不清楚ChatGPT对我们有什么影响&#xff1f; 不知道ChatGPT的发展现状及作用&#xff1f; 不知道ChatGPT的银行业应用案例&#xff1f;…

dy对接接口获取数据

1.需求方&#xff1a; 1.已知账户&#xff0c;获取该账户下每天的发视频数据&#xff0c;同时获取一个视频连续30天的点赞数,分享数&#xff0c;评论数。 2.需求方确定在标题中附带来源和作者相关信息&#xff0c;从标题中提取该部分信息&#xff0c;作为原创和作者绩效考核。…

项目人力资源管理

相关概念 组织结构图:用图形表示项目汇报关系。最常用的有层次结构图、矩阵图、文本格式的角色描述等3种。 任务分配矩阵(或称责任分配矩阵)(RAM):用来表示需要完成的工作由哪个团队成员负责的矩阵,或需要完成的工作与哪个团队成员有关的矩阵。 一、规划人力资源管理(编…

【Mybatis源码分析】类型处理器(TypeHandler)及其注册

TypeHandler和TypeHandlerRegistryTypeHandlerTypeHandler 源码分析TypeHandler注册&#xff0c;TypeHandlerRegistry源码分析TypeHandler拓展案例总结TypeHandler 大伙都知道Mybatis是对JDBC的封装&#xff0c;那Mybatis是如何处理JDBC类型和Java类型之间的转换的呢&#xff…

图文在线翻译-文本翻译

随着国际交流不断增加&#xff0c;越来越多的企业需要将产品介绍、宣传文案等相关信息翻译成多种语言&#xff0c;以扩大海外市场。但传统的翻译过程通常比较繁琐&#xff0c;耗费时间和人力成本也相对较高。为此&#xff0c;我们推出了一款批量图文翻译软件&#xff0c;帮助企…

【设计模式之美 设计原则与思想:规范与重构】35 | 实战一(下):手把手带你将ID生成器代码从“能用”重构为“好用”

上一节课中&#xff0c;我们结合 ID 生成器代码讲解了如何发现代码质量问题。虽然 ID 生成器的需求非常简单&#xff0c;代码行数也不多&#xff0c;但看似非常简单的代码&#xff0c;实际上还是有很多优化的空间。综合评价一下的话&#xff0c;小王的代码也只能算是“能用”、…

Apache配置与应用和优化

--------构建虚拟 Web 主机--------虚拟Web主机指的是在同一台服务器中运行多个Web站点&#xff0c;其中每一个站点实际上并不独立占用整个服务器&#xff0c;因此被称为“虚拟”Web 主机。通过虚拟 Web 主机服务可以充分利用服务器的硬件资源&#xff0c;从而大大降低网站构建…

【数据结构与算法】一、数据结构的基本概念

文章目录一、数据结构的基本概念1.1 数据结构的研究内容1.2 数据类型和抽象数据类型1.3 算法和算法分析1.3.1 算法的时间复杂度1.3.2 算法时间效率的比较1.4 知识回顾一、数据结构的基本概念 1.1 数据结构的研究内容 1.2 数据类型和抽象数据类型 抽象数据类型&#xff08;ADT…

初识linux之线程基本概念

目录 一、进程地址空间和页表再理解 二、线程 1.线程的概念 2. 进程与线程 3. 线程的意义 4.线程的优点缺点 4.1 优点 4.2 缺点 4.linux中线程的优缺点 4.1 优点 4.2 缺点 5. linux中线程创建相关接口 5.1 线程创建 6. 通过代码查看进程与线程的关系 6.1 线程库…

【软件设计师11】面向对象设计

面向对象设计 1. 设计原则 单一职责原则&#xff1a;设计目的单一的类 开放-封闭原则&#xff1a;对外扩展开放&#xff0c;对修改关闭 里氏(Liskov)替换原则&#xff1a;子类可以替换父类 依赖倒置原则&#xff1a;要依赖与对象&#xff0c;而不是具体实现&#xff1b;针…