数据结构——时间复杂度与空间复杂度

news2025/5/24 4:40:11

目录

一.什么是空间复杂度与时间复杂度

1.1算法效率

1.2时间复杂度的概念

1.3空间复杂度的概念

二.如何计算常见算法的时间复杂度

2.1大O的渐近表示法

 使用规则

三.如何计算常见算法的空间复杂度

3.1 大O渐近表示法

3.2 面试题——消失的数字

 3.3 面试题——旋转数组


一.什么是空间复杂度与时间复杂度

1.1算法效率

分为两种,一种是时间效率,又称时间复杂度,主要衡量算法的运行速度。另一种是空间效率,称空间复杂度,衡量算法所需要的额外空间。

1.2时间复杂度的概念

简单来说,算法中的基本操作的执行次数,就是算法的时间复杂度。

1.3空间复杂度的概念

空间复杂度是对一个算法运行过程中临时占用储存空间大小的量度。一般使用大O渐近表示法表示。


二.如何计算常见算法的时间复杂度

2.1大O的渐近表示法

//实例一.请计算一下Func1基本操作执行了多少次?
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);
}

我们可以知道准确的次数应该是N*N(每一次循环中嵌套循环都会循环N次,共N次循环所以是N*N次)+2*N(只循环2*N次)+10

这时候就会有关系:

令F(N)=N*N+2*N+10

N = 10 F(N) = 130

N = 100 F(N) = 10210

N = 1000 F(N) = 1002010

 注意点:

  • 随着N的增大,这个表达式中N^2对结果的影响是最大的。
  • 时间复杂度是一个估算,是去看表达式中影响最大的那一项。
  • 大O的渐进表示法,估算时间复杂度:O(N*2)。

 使用规则

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

//实例二,计算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);
}

 结果是O(N),准确是2*N+10,之所以忽略2是因为随着N无限增大,2已经无法过多影响N。

//实例三.计算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);
}

O(M+N)毕竟2个未知数 但如果有条件说M远大于N,那么结果就为O(M)

如果条件是M与N差不多,那么结果为O(N)或O(M)。

//实例四.计算Func4的时间复杂度
void Func4(int N)
{
	int count = 0;

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

O(1)  只要是确定的常数次,都是O(1)

//实例五.计算strchr的时间复杂度
const char* strchr(const char* str, char character)
{
	while (*str != '\0')
	{
		if (*str == character)
			return str;

		++str;
	}

	return NULL;
}

相当于在一个字符数组中查找字符 。假设字符串长度是N,在下面字符串中找到字符’s‘,'d','x'的运行次数都是不一样的。

 

 我们会发现有多种情况:

  • 最坏情况:任意输入规模的最大运行次数(上界)
  • 平均情况:任意输入规模的期望运行次数
  • 最好情况:任意输入规模的最小运行次数(下界)

例如:在一个长度为N数组中搜索一个数据x

最好情况:1次找到

最坏情况:N次找到

平均情况:N/2次找到

在实际中一般情况关注的是算法的最坏运行情况,所有数组中搜索数据时间复杂度为O(N)

//案例六.计算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;
	}
}

怎么说呢,有点抽象吧。毕竟这个不是像之前一样纯看代码,这一次是需要靠自己的理解把抽象代码具体化,就比如本质是冒泡排序,那我们就假想冒泡每一次执行的过程是怎么样的。

如数组arr[5]={0,1,2,3,4},0要换到4处最坏情况是4次(1,2,3,4,0),1换到3处最坏情况是3次,以此类推最后一个数为4时反而不用换了次数是0。

如下图所示。(为方便理解这里把N-1~0换成N~1)

可以看到在列出所以结果后发现这是一个等差数列,最终我们用求和方式求出准确次数。再进行估算后得出O(N^2)

//案例七.计算BinarySearch的时间复杂度
int BinarySearch(int* a, int n, int x)
{
	assert(a);

	int begin = 0;
	int end = n;
	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;
}

这是一个经典的二分查找案例,如果不懂其内核的友友们可以移步这篇文章:

二分查找详解

这里我们可以把这个数组想象成一张纸,里面的N个数想象成长度为N的纸,然后进行不断的折半,不断折半,这样折半X次后,要么找到了,要么找不到。最后我们再把它还原回去。

通过这样,我们得出了一个表达式:

//案例八.计算阶乘递归Factorial的时间复杂度
long long Factorial(size_t N)
{
	return N < 2 ? N : Factorial(N - 1) * N;
}

 

  每次递归运算都是常数次,又因为递归调用N次,所以就是O(N)了。

        

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

执行次数像一个金字塔一样,1变2,2变3,次数不断乘2. 

可以看出最终的时间复杂度是一个等比数列求和公式

 时间复杂度:O(2^N)

三.如何计算常见算法的空间复杂度

3.1 大O渐近表示法

//案例一.计算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;
	}
}

 

我们可以发现一共有5个变量,相当于开辟了5个空间,这样一来:O(1),因为5是常数。

在循环中走了N次,重复利用的是一个空间,只不过是变量出去会销毁,但空间不会。时间是累计的,空间不是累计。

//案例二.计算Fibonacc1的空间复杂度
long long* Fibonacc1(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;
}

 

我们可以知道准确的空间复杂度有O(N+6) 但实际是O(N)

其中malloc表达的含义是连续开辟了n+1的long long类型的数组。

//案例三.计算阶乘递归Factorial的空间复杂度
long long Factorial(size_t N)
{
	return N < 2 ? N : Factorial(N - 1) * N;
}

 

 虽然最后会销毁,但空间复杂度计算的是累计最多使用的空间大小。

  

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

   我们已经知道时间复杂度是O(2^N)

我们先来用栈帧图来表示:

一开始main开辟空间里面调用了Func1,Func1也会开辟空间存储变量a,结束后这个空间就销毁了。接着又有Func2的调用,之所以能够复用Func1的空间是因为它们都是类似的,都是创造一个int整型的变量(与值无关)。这就是地址相同的原因。

备注:两个函数的地址是不一样的,函数地址跟栈帧是没有关系的!

在开辟空间的时候并不会Fib(N-1)和Fib(N-2)同时调用开辟,而是优先顺着Fib(N-1)往下继续开辟空间,直到往下调用到Fib(2)时开始回归,这时候Fib(2)的空间虽然销毁了,但是只是把空间权限交还给了系统,当递归来到Fib(3)又会调用Fib(2)和Fib(1),而这时候Fib(2)并不会重新开辟出新的空间,而是前往之前已经销毁的空间,相当于系统又重新把该处空间的开放权限交给了Fib(2),所有顺着箭头我们会发现左侧都是已经开辟好的空间,一共有N处,这里的N处相当于一次性深入最多开辟的空间数,当递进完成需要销毁该处空间,回归调用遇到曾经已经开辟(销毁)过的空间时又重新使用这个空间。

换一种说法就是:酒店开了10间房,遇到Fib(2)递进结束开始往上回归时,相当于退房了。但房间还是在的,Fib(2)结束后就要调用Fib(1).而Fib(1)住进的房间就是Fib(2)退出的房间。

本质还是最多房间数代表一次递进所达到的最深程度,后面的都是重复利用罢了。不断的退房,开房,直到所有人都退房的时候也就代表着递归结束了。

时间是累积的,一去不复返,但空间是可以重复利用的。因此空间复杂度为O(N)

3.2 面试题——消失的数字

原题链接:力扣——消失的数字

排序+遍历:下一个数不等于下一个数据+1,这个下一个数就是消失的数字), 不过光排序(qsort)就花了很长时间了。

 

 时间复杂度为O(N)

int missingNumber(int* nums, int numsSize)
{
	int N = numsSize;
	int i = 0;
	int ret = N * (N + 1) / 2;
	for (i = 0; i < N; i++)
	{
		ret -= nums[i];
	}
	return ret;


}

 

异或特点:先把二进制表示出来,然后相同为0,相异为1。无论什么数,相同的数异或就没了。

 重点是不需要顺序。

int missingNumber(int* nums, int numsSize)
{
	int i = 0;
	int j = 0;
	int x = 0;
	int N = numsSize;
	for (i = 0; i < N; i++)
	{
		x ^= nums[i];
	}
	for (j = 0; j <= N; j++)
	{
		x ^= j;
	}
	return x;


}

 3.3 面试题——旋转数组

原题链接:力扣——旋转数组

把最后一个元素放在tmp中,前面数组元素全部往右移,再把tmp放到第一位,以此类推。

至于它的时间与空间复杂度是有说法的,右旋一次执行N次,那右旋K次应该是K*N次才对。

可是这是旋转数组,你右旋0次与右旋7次结果是一样的,所以k也分情况,最好的是只右旋了0次,最坏是右旋N-1次(第N次就又进入了轮回),所以时间复杂度也被分为O(1)与O (N^2),按照规则我们取最坏情况O (N^2)。

故该方法不符合条件,pass~。附上代码:

void rotate(int* nums, int sumsSize, int k)
{
	int N = sumsSize;
	if (k >= N)
	{
		k %= N;
	}
	while (k--)
	{
		int tmp = nums[N - 1];
		for (int end = N - 2; end >= 0; end--)
		{
			nums[end + 1] = nums[end];
		}
		nums[0] = tmp;
	}
}

 

void rotate(int* nums, int sumsSize, int k)
{
	int N = sumsSize;
	int* tmp = (int*)malloc(sizeof(int) * N);//开辟与原数组空间同样大小的新数组
	k %= N;
	memcpy(tmp, nums + N - k, sizeof(int) * k);//把原数组中从下标第N-k开始拷贝k个
	memcpy(tmp, nums, sizeof(int) * (N - k));//把原数组从下标为0开始拷贝N-k个
	memcpy(nums, tmp, sizeof(int) * N);//把tmp数组中所有的k个元素都拷贝回nums中去
	free(tmp);//释放空间
    tmp = NULL;//置空

}

 

就是一次性挪好几个,把后k个拷贝到新数组里,再把前N-k个2拷贝到新数组,最后再把新数组一起拷贝回去,以空间换时间,但还是老问题,如果数组太大,空间可能达不到要求。

 最妙的方法!!!

void Reverse(int* nums, int left, int right)
{
	while (left < right)
	{
		int tmp = nums[left];
		nums[left] = nums[right];
		nums[right] = tmp;
		left++;
		right--;

	}
}


void rotate(int* nums, int sumsSize, int k)
{
	int N = sumsSize;
	k %= N;

	Reverse(nums, 0, N - k - 1);
	Reverse(nums, N-k, N - 1);
	Reverse(nums, 0, N-1);

}

四.结语

关于复杂度的讲解就此结束,希望可以帮助到大家理解。另外由于学校的课程紧张,我会停更一段时间的C语言专栏,目前先把数据结构搞定~ 

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

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

相关文章

ChatGPT是留学生的论文神器还是学术不端的罪魁祸首?

当今时代&#xff0c;ChatGPT无疑是大数据和人工智能的完美结合&#xff0c;成为了搜索技术的革命性创新。几秒钟&#xff0c;一篇逻辑清晰、观点鲜明、有充分论据支持的文章即可生成。这种革命性的创新在学术界掀起了巨大的浪潮&#xff0c;甚至让全球的学校开始思考&#xff…

【GAN入门】生成 AI的概念

一、说明 GAN是生成对抗网络&#xff08;Generative Adversarial Network&#xff09;的缩写&#xff0c;是一种无监督学习算法&#xff0c;由Goodfellow等人于2014年提出。GAN由一个生成器网络和一个判别器网络组成&#xff0c;通过二者之间的对抗来训练生成器网络生成与真实样…

深入了解Python数据类型及应用

Python提供了一组丰富的内置数据类型&#xff0c;使您能够在程序中处理不同类型的数据。核心数值类型包括整数、浮点数和复数。整数表示整数&#xff0c;对于精确的计数和计算非常有用。 浮点数表示具有小数精度的实数&#xff0c;这对科学和统计计算非常重要。复数将数字扩展到…

C++系列赋值运算符重载

赋值运算符重载 类的默认函数拷贝构造函数和赋值运算符 重载赋值运算符相关注意事项 类的默认函数 一个类至少有4个默认函数&#xff1a; 默认构造函数拷贝构造函数析构函数赋值运算符重载函数 拷贝构造函数和赋值运算符 拷贝构造函数是在创建类的时候调用的&#xff0c;之…

利用PCA科学确定各个指标的权重系数

背景参考: 1、提取主成分 对样本进行PCA分析,查看不同变量贡献率,确定主要的指标。我们可以通过下列代码获取需要的所有数据: import numpy as np from sklearn.decomposition import PCA# 创建一个数据 np.random.seed(0) data = np.random.random((100,5)) y = np.ra…

深入理解CI/CD流程:改变你的开发生命周期

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

投后管理系统的主要功能及开发

投后管理系统是一种用于跟踪和管理投资组合中的投资的工具&#xff0c;通常由私募股权、风险投资公司、资产管理公司和投资者使用。其主要功能包括以下内容&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合…

全新跑分软件GeekRUN-7问世

实测非常好用CPU跑分神器点击下载 感兴趣的可以测测你的手机跑的多少。 我的峰值是7340&#xff0c;低值是4685&#xff0c;测试时后台不能有任何APP

丙烯酸共聚聚氯乙烯树脂

声明 本文是学习GB-T 42790-2023 丙烯酸共聚聚氯乙烯树脂. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件规定了丙烯酸共聚聚氯乙烯树脂的外观、物化性能等技术要求&#xff0c;描述了相应的采样、试验方 法、检验规则、标志、包装、…

2023年中国场馆产业研究报告

第一章 行业综述 1.1 定义与分类 场馆&#xff0c;作为一个多元化和充满活力的行业&#xff0c;为人们提供了一个为不同目的而聚集的空间。无论是为了活动、表演、展览还是聚会&#xff0c;场馆都在为社区的社会、文化和经济建设做出了不可或缺的贡献。 场馆是一个为举办各类…

深入思考redis面经

1 redission分布式锁 1.1 为了保证数据一致性&#xff0c;引入了redission的锁&#xff0c;你是为了抗住高并发而去为了引入这个锁还是说为了保证数据一致性去引入的 答&#xff1a;主要是为了抗住高并发问题&#xff0c;解决redis的缓存击穿问题&#xff0c;但是也能解决一定…

【算法|链表】环形链表Ⅱ

环形链表Ⅱ 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统…

原创 VTK 基础入门 ( 一 ) 贴纹理

纹理 这个示例先读入一幅JPEG的二维纹理图;然后定义一个纹理类vtkTexture对象&#xff0c;接着把读入的NPG图像输入到vtkTexture 里&#xff0c; 作为它即将“贴”到平面上的一个纹理图;再定义一个vtkPlaneSource对象&#xff0c;类vtkPlaneSource 可以生成一个…

Pytorch学习:torch.max(input,dim,keepdim=False)

文章目录 torch.max()dimkeepdimdim0dim1 out&#xff1a;返回命名元组 (values, indices) torch.max() torch.max(input) → Tensor&#xff1a;返回 input 张量中所有元素的最大值。 注意输入的必须是张量形式&#xff0c;输出的也为张量形式 当输入为tuple类型时&#xf…

Attention is all you need 论文笔记

该论文引入Transformer&#xff0c;主要核心是自注意力机制&#xff0c;自注意力&#xff08;Self-Attention&#xff09;机制是一种可以考虑输入序列中所有位置信息的机制。 RNN介绍 引入RNN为了更好的处理序列信息&#xff0c;比如我 吃 苹果&#xff0c;前后的输入之间是有…

计算机组成原理之计算机系统概论、计算机的发展史、系统总线,三章开篇讲

第一章-计算机系统概论 1计算机系统简介 现代计算机的多态性 把感应器嵌入和装备到电网、铁道、桥梁、隧道、公路、建筑、供水系统、大坝、油气管道等各种物体中&#xff0c;并且被普遍连接&#xff0c;形成“物联网”&#xff0c;然后将“物联网”与现有的网络整合起来&…

无涯教程-JavaScript - ACOT函数

描述 ACOT函数以0至π之间的弧度(以弧度为单位)返回数的反正切或反余切的主值。 语法 ACOT (number)争论 Argument描述Required/OptionalNumberNumber is the cotangent of the angle you want. This must be a real number.Required Notes 要将输出从弧度转换为度, 使用D…

【计算机视觉 | 目标检测】YOLO-NAS的介绍以及如何使用?(含源代码)

文章目录 一、介绍1.1 亮点1.2 方案简介1.3 训练简介 二、使用案例 一、介绍 Github 仓库&#xff1a; https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md1.1 亮点 参考QARepVGG&#xff0c;该方案引入了QSP与QCI模块以同时利用重参数与8-bit量化的优化&a…

C语言练习题解析(2)

&#x1f493;博客主页&#xff1a;江池俊的博客⏩收录专栏&#xff1a;C语言刷题专栏&#x1f449;专栏推荐&#xff1a;✅C语言初阶之路 ✅C语言进阶之路&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐ 文…

d3dx9_43.dll文件缺失的修复方法有哪些?4个方法快速修复d3dx9_43.dll

最近有很多小伙伴反映说他的电脑经常出现一个问题&#xff0c;那就是d3dx9_43.dll文件缺失了&#xff0c;然后一些程序都打不开&#xff0c;他们都是一脸懵逼&#xff0c;不知道怎么去处理这个问题&#xff0c;今天小编就要来给大家详细的说说这方面&#xff0c;d3dx9_43.dll文…