数据结构(2.1)——时间复杂度和空间复杂度计算

news2024/11/24 23:04:03

前言

(1)因为上一篇博客:数据结构(2)—算法对于时间复杂度和空间复杂度计算的讲解太少。所以我在次增加多个案例讲解。
(2)上一篇已经详细介绍了,为什么我们的算法要使用复杂度这一个概念。因此,我这一篇将重点介绍,复杂度如何进行计算。

时间复杂度计算

(1)上一篇博客已经介绍了,一般情况下,我们是不会考虑空间复杂度的。所以我将会重点介绍时间复杂度的计算。
(2)我们之前说了,时间复杂度是采用的大O计法。(注:不懂的建议看上一篇的算法的时间复杂度部分,我已经介绍的很详细了)
(3)接下来我将直接上代码进行计算。我先贴代码,然后再讲解。如果感兴趣的,可以看代码自己先计算一边,然后再看解析。

示例1—入门训练

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);
}

(1)根据大O计法,我们知道,先要找到这个函数的具体执行时间。
(2)首先,我们看到两个for语句嵌套,第一个for语句里面需要执行N次,而第二个for语句嵌套在里面,所以最终的执行次数为N^2次。

	for (int i = 0; i < N ; ++ i)  //执行N次
	{
		for (int j = 0; j < N ; ++ j)  //执行N*N次,所以最终结果是执行了N^2次
		{
			++count;
		}
	}

(3)第二个for语句里面没有嵌套,条件判断为 <2*N,而且每次自增为1。所以这里需要执行2N次。

	for (int k = 0; k < 2 * N ; ++ k)
	{
		++count;
	}

(4)最后一个while中执行M次,M给定了一个常量10。所以这个while是执行10次。

	int M = 10;
	while (M--)
	{
		++count;
	}

(5)综上所述,最终得出的结论是,这个代码执行次数如下
T ( n ) = n 2 + 2 n + 10 T(n)=n^{2} + 2n +10 Tn=n2+2n+10
(6)而根据大O计法,可知,当f(n)为n^2。n趋向于无穷大,最终T(n)/f(n)为常数。所以时间复杂度为O(n ^ 2)。
(7)总结,大O计法就是保留代码执行字数的最高项。

示例2—最高项的常数不唯一

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);
}

(1)同理,先计算出这个函数总体执行次数。
(2)第一个for循环,判断条件为 k < 2 * N,K每次自增为1,所以需要执行2N次。

	for (int k = 0; k < 2 * N ; ++ k)
	{
		++count;
	}

(3)同理,这个while固定执行10次。

	int M = 10;
	while (M--)
	{
		++count;
	}

(4)所以,这个函数最终执行的次数为 2N+10。那么根据大O计法,这个时间复杂度难道是O(2N)?
(5)看到我这么说,那么答案肯定不对。大O计法规定了 ,如果最高项的常数不是1,则需要去除最高想的常数。比如,此题的最高项为N,他的常数为2,所以这一题的时间复杂度为O(N)。

示例3—出现多个变量

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);
}

(1)这个题目,我们会发现有两个变量,那么时间复杂度怎么进行计算呢?其实也没有想象的那么复杂,只需要按情况分析即可。
(2)第一,假设N和M差不多大小,那么时间复杂度就是O(N+M)。
(3)第二,假设N远大于M,那么时间复杂度就是O(N)。
(4)第二,假设N远小于M,那么时间复杂度就是O(M)。

示例4—执行次数唯一

void Func4(int N)
{
	int count = 0;
	for (int k = 0; k < 100; ++ k)
	{
		++count;
	}
	printf("%d\n", count);
}

(1)我们看这个函数,会发现,传入形参N是没有被使用到的。也就是说,这个函数固定运行100次。
(2)对于这种情况,可能有些人就有点懵了。那么他的时间复杂度是多少呢?
(3)大O计法规定了,如果是固定了执行次数的函数。时间复杂度为O(1)。

示例5—执行次数存在多种情况

const char * strchr ( const char * str, int character )
{
	while(*str != '\0')
	{
		if(*str == character)
		{
			return str;
		}
		str++;
	}
	return NULL;
}

(1)首先说明一下这个函数的作用。假设我们有一个字符串"sdyzscx",我要找到这个字符串的字符’y’,那么他将会遍历这个字符串。如果找到了字符’y’,将会返回该字符的位置。否则返回一个空指针。
(2)这个题目,我们会发现,很难找到他的具体运行时间。因为假设我们要寻找的字符永远是字符串的第一个,那么这个函数的执行字数永远是1,那么时间复杂度就是O(1)。我们将这种情况称之为,最好情况。
(3)但是假如我们每次要寻找的字符总是出现在中间位置,也就是只要执行N/2次。这种叫做平均情况。
(4)最后一种,就是我们保持绝对的悲观态度,假设我们寻找的字符永远是字符串最后一个字符,或者找不到。那么执行次数为N,时间复杂度为O(n)。这种叫做最坏情况。
(5)在实际中,一般我们都是关注的最坏运行情况,所以此题的时间复杂度为O(n)。

示例6—执行次数不直观

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)这就是一个冒泡排序算法,但是我们会发现,这个题目的似乎运行次数不太好进行计算。这个时候,最好的方式是带入具体数值。
(2)假设n为10,传入的数组元素有10个。
<1>第一次运行,第一个for语句进入判断,第二个for语句的判断end就是10。所以这里是执行9次。
<2>第二次运行,这个时候,end为9了。那么第二个for语句就是执行8次。
<3>依次类推,我们可以知道,这里就是执行了9!(注意’!'表示阶乘的意思,不明白的可以看看高中课本)。
<4>如果将具体数字转换为变量n,就可以得出,这个函数实际上执行次数为:(n-1)! 根据小学的知识,我们可以知道,执行次数为
( n − 1 + 1 ) ( n − 1 ) ) 2 = n 2 − n 2 \frac{(n-1+1)(n-1))}{2}=\frac{n^{2}-n}{2} 2(n1+1)(n1))=2n2n
<5>根据大O计法可知,最终的时间复杂度为O(n^2)

示例7—二分查找的时间复杂度

/* 作用:二分查找,用于寻找有序数组的值
 * 传入参数 : 
     * a : 有序数组的首元素地址
     * n : 该元素的长度
     * x : 要查找的元素
 * 返回值 : 如果找到了元素,返回该元素在数组的第几项。没有找到元素,返回-1。
*/
int BinarySearch(int* a, int n, int x)
{
	assert(a);
	int begin = 0;
	int end = n-1;
	while (begin < end)
	{
		int mid = begin + ((end-begin)>>1);
		if (a[mid] < x)
			begin = mid+1;
		else if (a[mid] > x)
			end = mid;
		else
			return mid;
	}
	return -1;
}

(1)这个函数就是一个有序数列的二分查找函数。
(2)看到这个函数,依旧是没有任何思路的,所以还是直接套数字。假设有一个数列[0,1,2,3,4,5,6,7,8,9],因为计算时间复杂度是按照最坏的条件来进行计算,所以假设我们需要找到的数字为0。
<1>首先,传入这个数组,begin为0,end为9(注意,end为9,但是是指向的数组的最后一个元素,因为数组的第一个元素从0开始)。mid=(9-0)>>1 =4,所以min指向元素4。

在这里插入图片描述

<2>我们会发现0是小于4的,所以end=mid=4。mid=0+(4-0)>>1=2。

在这里插入图片描述

<2>这个时候,我们依旧会发现0是小于2的,所以end=mid=2。mid=0+(2-0)>>1=1。

在这里插入图片描述

<3>0是还是小于1的,所以end=mid=1。mid=0+(1-0)>>1=0。

在这里插入图片描述

<4>最后,我们会发现a[mid] == 0,值返回。
(3)
<1>根据上面这个例子,我们会发现,最坏情况需要运行4次。
<2>所以,我们假设一个有序数组有N项,由于我们按照最坏的情况进行讨论,所以每次寻找,就排除了1/2的数据。
<3>第一次寻找,还剩下N/2项,
<4>第二次寻找,还剩下N/4项。
<5>依次类推,我们假设最坏的情况是找了X次。那么最终满足条件2^(X-1)<N<2 ^X,那么X就找到了。
<6>因此,时间复杂度满足如下公式,但是很少部分时候,有些数据结构的书中,写成后面这个式子。(注意,与数学的写法不同!不严谨但是很多时候当成是等于)
O ( l o g 2 N ) = O ( l g N ) O({log_{2}}^{N}) = O(lg_{N}) O(log2N)=O(lgN)

示例8—递归调用函数的时间复杂度

long long Factorial(size_t N)
{
	return N < 2 ? N : Factorial(N-1)*N;
}

(1)这个依旧无法直接看出结果,依旧套具体数值来寻找思路。假设N为10。那么他的函数调用关系如下。
(2)我们会发现,传入的N为多少,那么执行的次数就是多少。所以时间复杂度为O(N)。

在这里插入图片描述

空间复杂度计算

示例1—初级训练

(1)看了上面这么多例子之后,我们对时间复杂度的计算应该还是比较了解了。那么如何计算空间复杂度呢?拿下面这个例子开始计算。
(2)空间复杂度也是采用的大O计法,首先我们先数一下下面这个函数中,建立了多少个变量。
(3)我们看到,这个函数中就只建立了一个变量exchange ,但是这个exchange 是在for循环里面的,那么这个函数的空间复杂度就是O(N)吗?

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;
	}
}

(4)答案是错误的,因为数据结构中,时间是累积的,空间是不累积的。可能有些人看到这句话,就蒙圈了。什么意思呢?
(5)首先我们得知道,exchange 这个变量虽然是在for循环中,但是每次for循环不会都创建一个exchange 变量,而是重复利用同一个空间。如下图,因此,这里的空间复杂度为O(1)。

在这里插入图片描述

示例2—递归调用的空间复杂度计算

long long Factorial(size_t N)
{
	return N < 2 ? N : Factorial(N-1)*N;
}

(1)根据上面的知识之后,那么这个函数的空间复杂度是多少呢?
(2)答案很简单,为O(N)。为什么呢?因为在递归调用的时候,每一次函数调用,都会保留上一次的值。
在这里插入图片描述

(3)而最终调用到Factorial(1)的时候,结束函数调用,开始返回,就开始销毁之前的变量。

在这里插入图片描述

示例3—malloc函数的空间复杂度计算

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 ;
}

(1)这是一个斐波那契数列,这个空间复杂度是多少呢?
(2)其实对于这种空间复杂度和时间复杂度计算,最重要的是看这个函数与传入参数有没有关系。如果没有关系,那么大概率是O(1)。有关系,就只需要看有关系的那一部分。
(3)我们会发现,这个函数,只有malloc函数与传入参数n有关系,所以其实我们需要看malloc那一句话。因为malloc申请到的数据为n+1,所以这个函数的空间复杂度就是O(N),其他地方根本不需要看。

总结

(1)时间复杂度和空间复杂度都是采用的大O计法。复杂度计算只需要看与函数传入参数有关系的部分
(2)时间复杂度要点:
<1>只需要看最高项。
<2>最高项的常数可以忽略。
<3>时间复杂度一般只看最坏情况。
(3)空间复杂度要点:
<1>时间是累积的,空间是不累积的。
<2>一般情况,我们只考虑时间复杂度,不考虑空间复杂度。
(4)根据复杂度,我们可以很好的衡量一个算法的优缺点,不同时间复杂度的执行时间由小到大。

在这里插入图片描述

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

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

相关文章

Docker部署jar包运行

目录 场景 方法 运行 场景 springboot项目,打包成jar包&#xff0c;想要放到docker中运行 这种方式就是运行一个jdk的容器&#xff0c;然后挂载其中的目录到宿主机&#xff0c;然后运行之后&#xff0c;就可以将需要运行的jar放在宿主机的挂载目录下&#xff0c;然后每次…

匈牙利算法解决英雄配色问题

匈牙利算法解决英雄配色问题 最近在开发中遇到了这种情况&#xff0c;策划要为每个英雄去做配色&#xff0c;一个英雄在表中可能有多个配色&#xff08;可能重复&#xff09;&#xff0c;每次对局要选出五个英雄并让他们的颜色不同 (实际不是这个&#xff0c;简化成的这个需求)…

nacos面经详解

1 为什么要懂nacos的详细原理&#xff1f; 只要你简历写过你用过nacos&#xff0c;则大厂&#xff08;阿里&#xff09;&#xff0c;尤其是使用过这款产品的大中厂&#xff0c;基本必问其中的原理。里面的设计理念还有很多分布式系统的原理值得学习和深究。 2 分布式理论基础…

进制转换详解(解释原理简单易懂)

前言&#xff1a;在网上看了许多篇关于不同进制之间如何转换的文章&#xff0c;包括很多浏览量上万的博客。大多都只是把转换的规则罗列了出来&#xff0c;例如十进制转二进制&#xff0c;可能大家都知道方法&#xff0c;“除以2反向取余数&#xff0c;直到商为0”。应用该方法…

黎曼猜想的验证

11 for n 1 : 100000 sum sum cos(14.134725*log(n))/(n^(-0.5)) end sum -1.1151e06

IDEA设置自动导包功能

IDEA设置自动导包功能 选择File--Settings--Edotor-General-Auto Import&#xff0c;勾选上下图中的选项后点击 OK 即可。导包无忧~~ Add unambiguous imports on the fly&#xff1a;自动导入不明确的结构 Optimize imports on the fly&#xff1a;自动帮我们优化导入的包

vep加密视频破解转换翻录为mp4教程

可能有很多人都没有听说过这个视频格式&#xff0c;这是大黄蜂云课堂播放器所独有的格式&#xff0c;只有通过该播放器才能够打开这个加密的视频&#xff0c;安全系数很高&#xff0c;但也极大的限制了一个视频的传播和播放。如果我们在网络上下载了vep格式的视频&#xff0c;可…

消息队列总结(2)- RabbitMQ Kafka RocketMQ运行架构

目录 1 RabbitMQ 1.1 工作原理 1.2 名词解释 1.3 交换机的几种类型 2 Kafka 2.1 工作原理 2.2 基本概念 3 RocketMQ 3.1 工作原理 3.2 基本概念 4 RabbitMQ & Kafka & RocketMQ的差异 5 参考文档 1 RabbitMQ 1.1 工作原理 1.【消息生产】生产者&#xff0…

TMS FNC Core Crack

TMS FNC Core Crack 跨框架核心层&#xff0c;用于创建丰富的可视化和非可视化组件。 特点 JSON读、写和解析功能 Base64和URL编码和解码 组件持久性 撤消/恢复管理器 数字、字母数字字符集的验证 消息、文件打开和保存对话框 轻松访问应用程序和文档文件夹 打开文件和URL 位图…

非对称加密算法关于公钥和私钥

公钥和私钥成对出现 公开的密钥叫公钥&#xff0c;只有自己知道的叫私钥 用公钥加密的数据只有对应的私钥可以解密 用私钥加密的数据只有对应的公钥可以解密 如果可以用公钥解密&#xff0c;则必然是对应的私钥加的密 如果可以用私钥解密&#xff0c;则必然是对应的公钥加的密 …

leetcode 17 电话号码的字母组合

题目描述&#xff1a; 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按任意顺序返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 思路&#xff1a; 从示例上来说&#xff0c;输入"…

第十七章:Dual Attention Network for Scene Segmentation ——双重注意力网络用于场景分割

0.摘要 在本文中&#xff0c;我们通过基于自注意机制捕捉丰富的上下文依赖来解决场景分割任务。与以往通过多尺度特征融合来捕捉上下文的方法不同&#xff0c;我们提出了一种双重注意力网络&#xff08;DANet&#xff09;&#xff0c;以自适应地将局部特征与全局依赖性相结合。…

.net6中WPF的串口通信和USB通信

之前写过串口通信&#xff0c;不过是winform的。 c#使用串口进行通信_c# 串口通信_故里2130的博客-CSDN博客 今天说一下&#xff0c;.net6中wpf的串口通信和USB通信&#xff0c;在工控行业中&#xff0c;这2种的方式非常多&#xff0c;还有网口通信&#xff0c;它们都是用来和…

TypeScript基础教程

ts与js区别 TypeScript is a syntactic sugar for JavaScript. TypeScript syntax is a superset of ECMAScript 2015 (ES2015) syntax. Every JavaScript program is also a TypeScript program. 语言层面&#xff1a;JavaScript和TypeScript都是ECMAScript&#xff08;ECMA-2…

【布局优化】基于遗传算法的车间布局优化 车间设施布局优化【Matlab代码#50】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 车间布局优化2. 基于GA的布局优化模型3. 部分代码展示4. 仿真结果展示5. 资源获取 【获取资源请见文章第5节&#xff1a;资源获取】 1. 车间布局优化 车间设施布置的规划一直是工业工程领域不断研究和探索的内容&am…

【2023江西省研究生数学建模竞赛】第二题 国际“合作-冲突”的演化规律研究 80页论文及Python代码

【2023江西省研究生数学建模竞赛】第二题 国际“合作-冲突”的演化规律研究 80页论文及Python代码 相关链接 【江西省研究生数学建模竞赛】第一题 蒸汽发生器倒U型管内液体流动 70页论文及MATLAB代码 【江西省研究生数学建模竞赛】第一题 蒸汽发生器倒U型管内液体流动 70页论…

<数据结构>并查集

目录 并查集概念 合并 查找集合的数量 并查集类代码实现 并查集概念 并查集和堆一样&#xff0c;都是通过数组来实现树的节点映射&#xff0c;不过并查集作用是&#xff0c;把一堆数据分为不同的几个小集合 不过并查集是森林的概念&#xff0c;并查集的学习可以帮助我们去更…

(QGIS)在QGIS中加载星图地球数据云的数据

本文以“瓦片数据”为例进行操作示例说明&#xff0c;类似矢量数据、地形数据等&#xff0c;方法步骤一样&#xff0c;将URL换成对应数据的示例网址与本人的token即可。 1、星图地球数据云上注册用户 注册登录星图地球数据云网站&#xff1a;https://datacloud.geovisearth.c…

Python 函数(二)

Python 函数&#xff08;二&#xff09; 一、可变参数、不可变参数 ​ 什么是可变类型&#xff0c;什么又是不可变类型&#xff1f; 这里我们首先要了解一个东西&#xff0c;在计算机程序中我们定义一个变量&#xff0c;并对其存储一个数值。这里有两个关键概念&#xff1a;内…

数码管显示(静态与动态)

文章目录 一、数码管简介二、数码管连接方式2.1 共阴极2.2 共阳极2.3 数码管真值表 三、数码管驱动方式3.1 静态显示3.2 动态显示四、Cyclone IV数码管原理图 五、模块代码六、引脚分配七、动态显示八、引脚分配 一、数码管简介 数码管分七段数码管和八段数码管。七段和八段的…