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

前言

(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)根据复杂度,我们可以很好的衡量一个算法的优缺点,不同时间复杂度的执行时间由小到大。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Stable Diffusion (持续更新)

引言 本文的目的为记录stable diffusion的风格迁移&#xff0c;采用diffusers example中的text_to_image和textual_inversion目录 2023.7.11 收集了6张水墨画风格的图片&#xff0c;采用textual_inversion进行训练&#xff0c;以"The street of Paris, in the style of …

uniApp之同步资源失败,未得到同步资源的授权,请停止运行后重新运行,并注意手机上的授权提示、adb、shell、package、uninstall

文章目录 背景解决思路执行查找第三方应用的指令执行卸载指令 背景 一开始正常编译运行&#xff0c;由于应用页面有些许奇怪的错误&#xff0c;便想着卸载&#xff0c;重新运行安装调试基座。卸载后&#xff0c;运行还是会出现&#xff0c;明明已经把应用卸载了&#xff0c;还是…

基于深度学习的高精度Caltech行人检测系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度Caltech数据集行人检测识别系统可用于日常生活中或野外来检测与定位行人目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的行人目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv…

HTTP、HTTPS协议详解

文章目录 HTTP是什么报文结构请求头部响应头部 工作原理用户点击一个URL链接后&#xff0c;浏览器和web服务器会执行什么http的版本持久连接和非持久连接无状态与有状态Cookie和Sessionhttp方法&#xff1a;get和post的区别 状态码 HTTPS是什么ssl如何搞到证书nginx中的部署 加…

什么是人工智能大模型?

目录 1. 人工智能大模型的概述&#xff1a;2. 典型的人工智能大模型&#xff1a;3. 人工智能大模型的应用领域&#xff1a;4. 人工智能大模型的挑战与未来&#xff1a;5. 人工智能大模型的开发和应用&#xff1a;6. 人工智能大模型的学习资源&#xff1a; 人工智能大模型是指具…

计数排序

计数排序 排序步骤 1、以最大值和最小值的差值加一为长度创建一个新数组 2、将索引为0对应最小值&#xff0c;索引为1对应最小值1&#xff0c;索引为2对应最小值2&#xff0c;以此类推&#xff0c;将索引对应最小值到最大值之间所有的值 3、遍历一遍&#xff0c;遇到一个数字…

MyBatis学习笔记之首次开发及文件配置

文章目录 MyBatis概述框架特点 有关resources目录开发步骤从XML中构建SqlSessionFactoryMyBatis中有两个主要的配置文件编写MyBatis程序关于第一个程序的小细节MyBatis的事务管理机制JDBCMANAGED 编写一个较为完整的mybatisjunit测试mybatis集成日志组件 MyBatis概述 框架 在…

Excel VLOOKUP使用详解

VLOOKUP语法格式&#xff1a; VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) VLOOKUP&#xff08;要查找的值&#xff0c;查找区域&#xff0c;要返回的结果在查找区域的第几列&#xff0c;精确匹配或近似匹配&#xff09; 一、精确查找 根据姓名查找对应…

FPGA Verilog移位寄存器应用:边沿检测、信号同步、毛刺滤波

文章目录 1. 端口定义2. 边沿检测3. 信号同步4. 信号滤波5. 源码6. 总结 输入信号的边沿检测、打拍同步、毛刺滤波处理&#xff0c;是FPGA开发的基础知识&#xff0c;本文介绍基于移位寄存器的方式&#xff0c;实现以上全部功能&#xff1a;上升沿、下降沿、双边沿检测、输入信…

个人使用:Windows下 OpenCV 的下载安装(2021.12.4详细)

一、下载OpenCV   到OpenCV官网Release(发布)板块下载OpenCV-4.5.4 Windows。 下载后是这样的 然后双击他&#xff0c;解压&#xff0c;就是大佬们说的安装&#xff0c;实质就是解压一下&#xff0c;解压完出来一个文件夹&#xff0c;其他什么也没发生。你把这个文件夹放在哪…

STM32(HAL库)驱动SHT30温湿度传感器通过串口进行打印

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 软件IIC引脚配置 2.3 串口外设配置 2.4 项目生成 3、KEIL端程序整合 3.1 串口重映射 3.2 SHT30驱动添加 3.3 主函数代 3.4 效果展示 1、简介 本文通过STM32F103C8T6单片机通过HAL库…

uniapp uni实人认证

uni实人认证依赖 目前仅支持App平台。 h5端活体人脸检测&#xff0c;使用的是百度云的h5人脸实名认证 使用要求 1、app端 在使用前&#xff0c;请确保您已注册DCloud账号&#xff0c;并已完成实名认证。 然后需要按文档开通服务 业务开通 | uni-app官网 2、h5端 在使用前…

STM32 ws2812b 最快点灯cubemx

文章目录 前言一、cubemx配置二、代码1.ws2812b.c/ws2812b.h2.主函数 前言 吐槽 想用stm32控制一下ws2812b的灯珠&#xff0c;结果发下没有一个好用的。 emmm&#xff01;&#xff01;&#xff01; 自己来吧&#xff01;&#xff01;&#xff01;&#xff01; 本篇基本不讲原理…

Unity DOTS如何优雅地实现ECS框架下的定时器Timer系统(无对象池,零GC)

实现定时器并不复杂&#xff0c;就是写个类存放回调&#xff0c;再写个类来统一管理这些回调类。但是ECS写代码的方式变了&#xff0c;所以还是有些区别的。 实现过程中需要注意的几点&#xff1a; 1、由于IComponentData不能存放managed类型的数据&#xff0c;所以无法用常规…

C#使用DataGridView模拟绘图

接到一个需求&#xff0c;绘制一个水管线的图片&#xff0c;这种管线可以有12种分段方法&#xff0c;最后将这12种分段方法合并后在一条水管线上展示&#xff0c;要求&#xff1a; ⒈支持分段的属性展示&#xff1b; ⒉要求每个分段都能清晰展示&#xff0c;分段数在0&#xff…

从CPU缓存结构到原子操作

文章目录 一、CPU缓存结构1.1 CPU的多级缓存1.2 Cache Line 二、写回策略三、缓存一致性问题及解决方案3.1 缓存一致性问题3.2 解决方案3.2.1 总线嗅探3.2.2 事务的串行化3.2.3 MESI 四、原子操作4.1 什么是原子操作4.2 c 标准库的原子类型4.2.1 atomic<T\>4.2.2 is_lock…

Python(四):Pycharm的安装配置

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

【基于FPGA的芯片设计】32位RISC-V存储器

实验板卡&#xff1a;xc7a100tlc sg324-2L&#xff0c;共20个开关 实验要求

RabbitMQ常用工作模式+整合springboot

目录 1.MQ的相关概念 1.1 什么是MQ消息中间件 1.2 为什么使用MQ (1) 应用解耦 (2) 异步提速 (3)削峰填谷 1.3 使用MQ的劣势 1.4 常见的MQ组件​​​​​​​ 2. RabbitMQ的概述 2.1 RabbitMQ的概念 2.2 RabbitMQ的原理 2.3 安装RabbitMQ 3. RabbitMQ 的工作模式…

【NLP】Word2Vec原理和认识

一、介绍 Word2Vec是NLP领域的最新突破。Tomas Mikolov是捷克计算机科学家&#xff0c;目前是CIIRC&#xff08;捷克信息学&#xff0c;机器人和控制论研究所&#xff09;的研究员&#xff0c;是word2vec研究和实施的主要贡献者之一。词嵌入是解决NLP中许多问题不可或缺的一部分…