初阶数据结构(2):空间复杂度和复杂度算法题

news2024/11/24 19:46:39

在这里插入图片描述
Hello~,欢迎大家来到我的博客进行学习!

目录

  • 1.空间复杂度
  • 2. 常见复杂度对比
  • 3. 复杂度算法题
    • 3.1 旋转数组
      • 解法一
      • 解法二
      • 解法三

1.空间复杂度

根据摩尔定律,每隔一段时间晶体管的数量会增加一倍,即内存会增加,价格会降低。内存就不再是稀缺的东西,但是我们在写程序的时候仍要注意空间浪费的问题

空间复杂度是⼀个数学表达式,是对⼀个算法在运行过程中因为算法的需要额外临时开辟的空间。

空间复杂度不是程序占用了多少bytes的空间,因为常规情况每个对象大小差异不会很大,所以空间复杂度算的是变量的个数。

空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法

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

例如:

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

我们这里有一个冒泡排序的代码。在编译的时候,我们函数栈帧的创建和参数的申请等已经确定好了。我们函数体的部分是在运行时确定的,所以我们的空间复杂度用函数在运行时候显式申请的额外空间来表示。
理解以上内容之后,我们可以来看看这个冒泡排序代码空间复杂度的计算。首先我们看到了这个for循环,里面定义了一个局部变量end,在for循环结束后,这个end就会被释放。往下看还定义了一个整型变量exchange,内层的for循环里定义了一个无符号整型 i,之后就没有额外的申请空间了,这些局部变量都在栈上。

现在我们可以统计申请的空间:无符号整型end、int类型的exchange、无符号整型i。对于这些变量,我们不能精确的计算算出占据多少空间,如整型,一个整型的变量在不同的系统上占用的空间是不一样的,比如在32位的系统上一个整型占据4个字节,在64位系统上是8个字节。则对于空间复杂的计算,我们并不能准确的计算出占据的空间,这里就与我们的时间复杂度类似,我们都不能计算出准确的数字,现在我们用大O接着推理。我们现在看向我们的代码,定义了一个int类型的exchange,我们可以把它看为一个空间,同理无符号整型end和无符号整型i我们也可以用相同的办法。依照思路我们可能会认为该冒泡排序的空间复杂度是O(3),但是依据大O阶的规则,空间复杂度应该为O(1)。

示例:
计算阶乘递归Fac的空间复杂度

long long Fac(size_t N)
{
	if (N == 0)
		return 1;
	return Fac(N - 1) * N;
}

空间复杂度只关注函数体里面有没有额外申请空间。Fac(N)里面的N首先进行判断,如果N不为0,就会调用Fac(N -1),此时是在函数体里面调用的,我们需要将其统计在内。函数的调用需要创建函数栈帧,在N不为0之前会一直继续这个过程,注意在Fac(0)这里还会创建Fac(0)的函数栈帧。到这时会从Fac(0)一层层往上释放函数栈帧。
在这里插入图片描述
这里就会额外的申请N个空间,空间复杂度就为O(N)。我们可以看出这里递归的时间和空间复杂度部分相同。时间复杂度求解的是执行次数,空间复杂度求解的是申请的空间,在阶乘递归里面,递归需要不断的调用,调用就会涉及到执行次数,调用函数的话这里也会创建函数栈帧,所以在这里我们的空间和时间复杂度是一样的。

2. 常见复杂度对比

这里不分空间和时间,主要是看随着n的变化,复杂度趋势的对比。
在这里插入图片描述
在这里插入图片描述
根据表格和图像我们可以发现,随着n的增加,logn的变化趋势最小,同时我们可以得出变化快慢的排序(从慢到快):logn、n、nlogn、n2 、2n、n!。则logn复杂度最优往后依次递减。
之前我提到过时间只能在程序写好之后就行评估,不能写程序之前通过理论思想进行评估。现在我们有了复杂度这个概念,我们可以在编写程序之前就可以评估出来。当然我们还可以同过下面的题目来理解这一观点,给出思路之后我们可以快速的算出复杂度是多少。

3. 复杂度算法题

3.1 旋转数组

在这里插入图片描述

解法一

之前我们写的代码如下;

void rotate(int* nums, int numsSize, int k) {
    while (k--) {

        int endNum = nums[numsSize - 1];

        for (int i = numsSize - 1; i > 0; i--) {
            nums[i] = nums[i - 1];
        }
        nums[0] = endNum;
    }
}

时间复杂度为O(n2),这里有一个可输入条件k- -,k是会影响复杂度的;内层有一个i,受到数据个数的影响,所以最终时间复杂度为O(n2)。
空间复杂度为O(1),因为这里只了申请常数个空间,这里的申请并没有受到我们数组长度和k的影响。
上次我们提交的时候显示超出了时间限制,我们现在可以理解为什么了,这里的时间复杂度是O(n2),太大了。

解法二

现在我们需要将数据复杂度降下来,我们上面的代码用了循环的嵌套,n*n =n2 ,我们会想能不能把这个循环拆开,n+n = 2n,把时间复杂度降为O(n),此时我们的思路就出来了,所以在上一个知识点中,我说可以在编写程序之前就可以评估出来
在这里插入图片描述
我们需要将数组进行轮转3次,这里我们并不在原数组中进行操作,而是在一个新的数组中进行操作。我们定义一个整型变量 i 作为下标,放在数组长度减k的位置,这里计算之后是放在5这里,通过i++不断的拿到后面的数字并放到新数组里面,我们此时完成了对5 、6、7的操作,我们再将1、2、3、4拿下来放置后面就行。

我们首先创建一个新的数组newArr,它的大小与原数组一致就行。借助for循环将原数组中后k个数据放到新数组前面的位置,i 从 numsSize - k 开始,i不断的++,但是 i 要小于 numsSize。我们将原数组 i 中国位置的数据给新数组 j 这个位置,j也和i一样不断的++,这样我们就把5、6、7放到了前面。
此时的代码:

void rotate(int* nums, int numsSize, int k)
{
	int newArr[numsSize] = { 0 };
	int j = 0;
	for (int i = numsSize - k; i < numsSize; i++)
	{
		newArr[j++] = nums[i];
	}
}

下一步需要将原数组size-k个数据放到newArr剩下的位置,这里我们可以再写一个循环数据,但是也可以把两步合在一起。代码如下:

void rotate(int* nums, int numsSize, int k)
{
	int newArr[numsSize] ;
	
	for (int i = 0; i < numsSize; i++)
	{
		newArr[(i + k) % numsSize] = nums[i];
	}
}

理解:
主要的疑点就是(i + k) % numsSize这一部分,可以代值进行理解。一开始i=0、k=3,3%7=3,此时我们就将原数组位置为0的数据赋值给了新数组位置为3的位置。之后依次写一下,我们就会发现我们先将1、2、3、4放到了新数组的后面,而5、6、7置于前面。

此时我们的新数组里面的数据是满足题目要求的,但是原数组并没有发生变化。函数的返回类型是void,所以我们需要将新数组里面的值给到原数组里面去,我们的代码就可以完成。
代码:

void rotate(int* nums, int numsSize, int k) {
    int newArr[numsSize];

    for (int i = 0; i < numsSize; i++) {
        newArr[(i + k) % numsSize] = nums[i];
    }


    for (int i = 0; i < numsSize; i++) {
        nums[i] = newArr[i];
    }
}

在这里插入图片描述
此时我们的代码的时间复杂度为O(n),空间复杂度为O(n)。时间复杂度相较于前一篇文章已经降下来了,但是空间复杂度提高了,这里我们采用了空间换时间的方式来提高算法性能

解法三

虽然空间多,也不能进行随意的浪费。我们现在要求时间复杂度为O(N),空间复杂度度为O(1) 。
该方法需要逆置3次,如图:
在这里插入图片描述
从图中我们可以看出第一次我们访问0到3下标的数据,第二次我们访问4到6位置的数据,第三次我们访问0到6下标的数据。每一次访问的数据位置都不同,我们就需要begin和end来记住这些位置,并进行交换,这里设置一个int类型的tmp来交换数据。当然begin需要++,end需要 - - ,同时借助while来进行不断的访问,当begin < end
进入循环。此时我们就完成了可以进行逆置的函数reverse。

void reverse(int* arr, int begin, int end)
{
	while (begin < end)
	{
		int tmp = arr[begin];
		arr[begin] = arr[end];
		arr[end] = tmp;

		begin++;
		end--;
	}
}

我们对reverse调用3次就可以完成我们需要的结果。
第一次调用,我们需要将nums,0,numSize-k-1这些参数传过去,目的是完成前n-k个数据的逆置。
第二次调用,我们需要将nums,numSize-k,numSize-1这些参数传过去,目的是完成后k个数据的逆置。
第三次调用,我们需要将nums,0,numSize-1这些参数传过去,目的是完成整体的逆置。
完成以后的代码:

void reverse(int* arr, int begin, int end)
{
	while (begin < end)
	{
		int tmp = arr[begin];
		arr[begin] = arr[end];
		arr[end] = tmp;

		begin++;
		end--;
	}
}

void rotate(int* nums, int numsSize, int k)
{
	reverse(nums, 0, numsSize - k - 1);
	reverse(nums, numsSize - k, numsSize - 1);
	reverse(nums, 0, numsSize - 1);
}

在这里插入图片描述
这里提示我们越界了,当nums=-1时要逆置2次 ,虽然还是-1,但是我们的代码就有点问题了。数组里面只有一个数据-1,下标为0,此时numsSize-k-1=1-2-1=-2,我们的代码效果为逆置0到-2这个区间的数据,这不是一个有效的范围。
我们现在对k进行处理,当我们的k大于我们的numsSize时,我们使k = k%numsSize。如果为上图的情况,我们的k = 2 %1 =0,begin和end都为0就不进行处理。
最后的代码:

void reverse(int* arr, int begin, int end)
{
	while (begin < end)
	{
		int tmp = arr[begin];
		arr[begin] = arr[end];
		arr[end] = tmp;

		begin++;
		end--;
	}
}

void rotate(int* nums, int numsSize, int k)
{
	k = k % numsSize;
	reverse(nums, 0, numsSize - k - 1);
	reverse(nums, numsSize - k, numsSize - 1);
	reverse(nums, 0, numsSize - 1);
}

在这里插入图片描述
现在我们已经学会了时间复杂度为O(N),空间复杂度度为O(1)的代码了。

好了,我们的数据结构知识就讲到这里。如果文章内容有误,请大佬在评论区斧正!谢谢大家!
在这里插入图片描述

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

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

相关文章

Windows CSC服务权限提升漏洞(CVE-2024-26229)

一、漏洞描述 csc.sys驱动程序中带有METHOD_NEITHER I/O控制代码的IOCTL地址验证不正确&#xff0c;导致任意地址写零漏洞。攻击者在Windows上获得较低权限的任意代码执行后&#xff0c;可以利用该漏洞将低权限提升至system权限。 二、漏洞详情 该漏洞源于 csc.sys 驱动程序…

Zilliz获Forrester报告全球第一;OB支持向量能力;Azure发布DiskANN;阿里云PG发布内置分析引擎

重要更新 1. Azure发布PostgreSQL向量索引扩展DiskANN&#xff0c;声称在构建HNSW/IVFFlat索引上&#xff0c;速度、精准度都超越pg_vector&#xff0c;并解决了pg_vector长期存在的偶发性返回错误结果的问题( [1] )。 2. 阿里云RDS PostgreSQL 发布AP加速引擎&#xff08;rds…

【python实操】python小程序之继承

引言 python小程序之继承 文章目录 引言一、继承1.1 概念1.1.1 基本语法1.1.2 关键点解释1.1.3 示例1.1.4 总结 1.2 题目1.3 代码1.4 代码解释1.4.1 类定义1.4.2 对象创建与方法调用1.4.3 总结 二、思考 一、继承 1.1 概念 python 中的继承是一种强大的机制&#xff0c;它允许…

如何防止webpack打包被逆向?

webpack打包后的js代码&#xff0c;看起来很混乱&#xff0c;似乎源码得到了保护&#xff1f; 不然&#xff0c;因为webpack只是将多个文件合并到了一起&#xff0c;并没有多少保护代码的功能。 比如下面这个例子&#xff0c;该网站的js文件是经webpack打包编译后生成的&…

TextView把其它控件挤出屏幕的处理办法

1.如果TextView后面的控件是紧挨着TextView的&#xff0c;可以给TextView添加maxWidth限制其最大长度 上有问题的布局代码 <?xml version"1.0" encoding"utf-8"?> <layout xmlns:android"http://schemas.android.com/apk/res/android&qu…

动态爬虫管理平台构建与实现(论文+源码)_kaic

摘 要 随着互联网的迅速发展&#xff0c;Web的信息量越来越大。人们往往通过搜索引擎去从互联网上搜索想要的信息&#xff0c;比如:百度&#xff0c;谷歌&#xff0c;搜狗等。这类搜索引擎称之为通用搜索引擎&#xff0c;其为所有的用户所需的内容&#xff0c;但目前互联网上的…

【网络原理】TCP协议提高效率的秘密-滑动窗口机制

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;计算机网络那些事 如果我们严格依照“确认应答”机制&#xff0c;针对每一个发送的数据段&#xff0c;都需要一个ACK确认应答&#xff0c;当收到ACK应答报文后&#xff0c;才继续发下一个报文。这样…

2025届计算机保研经验贴(末九→浙江大学软件学院)

燕园再美美不过宁波港&#xff0c;没到过浙软的人不会明了 软微已死&#xff0c;浙软当立&#xff01; 文章目录 一、个人情况二、保研历程1、去年今日2、前期准备3、夏令营天大智算软件所西交软本校浙江大学软件学院 4、预推免 三、后记链式反应9.28下午冥场面9.29博弈 浙软当…

ClickHouse 24.9 版本发布说明

本文字数&#xff1a;7295&#xff1b;估计阅读时间&#xff1a;19 分钟 作者&#xff1a;ClickHouse Team 本文在公众号【ClickHouseInc】首发 又到新版本发布的时间了&#xff01; 发布概要 本次ClickHouse 24.9 版本包含了23个新功能&#x1f381;、14项性能优化&#x1f6f…

[已解决] HttpMessageNotReadableException: JSON parse error: Unexpected character

[已解决] HttpMessageNotReadableException: JSON parse error: Unexpected character 文章目录 写在前面问题描述报错原因分析&#xff1a; 解决思路解决办法1. 检查并修复客户端的 JSON 数据格式2. 确认请求头的 Content-Type 设置正确3. 捕获并处理 HttpMessageNotReadableE…

三层b+树估算存储多少行数据

文章目录 B树结构图示估算方法(这里要以聚簇索引来看) B树结构图示 估算方法(这里要以聚簇索引来看) 非叶子节点数* 每个叶子结点记录总数 假设mysql 数据页&#xff0c;16kb&#xff0c;刚好对应B树的一个节点 每个叶子结点记录数&#xff0c; 叶子结点存储的是对应的原始数据…

项目常用版本控制管理工具

不仅仅是代码管理工具 gitHubgitcodeSVN gitHub https://github.com/ github gitcode https://gitcode.com/ gitcode SVN 图片: 带尺寸的图片: 居中的图片: 居中并且带尺寸的图片:

git--git reset

HEAD 单独一个HEAD eg:git diff HEAD 表示当前结点。 HEAD~ HEAD~只处理当前分支。 注意&#xff1a;master分支的上一个结点是tmp分支的所在的结点fc11b74, 79f109e才是master的第二个父节点。 HEAD~ 当前结点的父节点。 HEAD~1 当前结点的父节点。 HEAD~n 当前结点索…

Python 工具库每日推荐 【easyocr】

文章目录 引言Python OCR 工具库的重要性今日推荐:EasyOCR 工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例案例:多语言名片信息提取案例分析高级特性自定义模型训练处理倾斜文本扩展阅读与资源优缺点分析优点:缺点:总结【 已更新完 TypeScript…

Qt实现侧边栏功能

本文介绍Qt实现侧边栏功能。 采用Qt进行界面应用程序开发时&#xff0c;经常遇到侧边栏功能实现&#xff0c;采用侧边栏可以将一些暂时不用到的功能隐藏&#xff0c;使用的时候点击一下相应的按钮即可弹出&#xff08;动画方式&#xff09;功能菜单。减少主界面控件数量&#…

JS | JS中类的 prototype 属性和__proto__属性

大多数浏览器的 ES5 实现之中&#xff0c;每一个对象都有__proto__属性&#xff0c;指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖&#xff0c;同时有prototype属性和__proto__属性&#xff0c;因此同时存在两条继承链。 构造函数的子类有prototype属性。‌ …

搭建知识库:助力大健康零售电商的快速发展

一、大健康零售电商行业的快速发展及其对知识库的需求 随着互联网技术的飞速发展和人们对健康意识的显著提升&#xff0c;大健康零售电商行业迎来了前所未有的发展机遇。这一行业不仅涵盖了传统零售业的商品销售&#xff0c;还融入了健康管理、健康咨询、健康数据分析等多元化…

『网络游戏』数据库表格转储【25】

避免勿删数据库表格&#xff0c;可以将表格存储 放到桌面即可 现在将表格删除后点击 浏览桌面表格保存即可 修改客户端脚本&#xff1a;NetSvc.cs 目的是在数据库更新异常时弹出提示以便修改 本章结束

使用 Helsinki-NLP 中英文翻译本地部署 - python 实现

通过 Helsinki-NLP 本地部署中英文翻译功能。该开源模型性价比相对高&#xff0c;资源占用少&#xff0c;对于翻译要求不高的应用场景可以使用&#xff0c;比如单词&#xff0c;简单句式的中英文翻译。 该示例使用的模型下载地址&#xff1a;【免费】Helsinki-NLP中英文翻译本…

Pura 70系列和Pocket 2已支持升级尝鲜鸿蒙NEXT,报名教程在这里

相信不少关注鸿蒙 NEXT 的人都知道&#xff0c;10月8日起&#xff0c;华为开启了鸿蒙 NEXT 系统的公测&#xff0c;但有不少人不知道的是&#xff0c;除了公测的 Mate 60 和 Mate X5 两个系列的机型&#xff0c;还有两个系列的手机其实也可以提前升级体验鸿蒙 NEXT 系统。 Pur…