当前位置:   article > 正文

数据结构之——时间复杂度与空间复杂度_空间复杂度和时间复杂度的关系

空间复杂度和时间复杂度的关系

目录

时间复杂度与空间复杂度的意义

空间复杂度的计算

时间复杂度的计算

有多种情况下的时间复杂度的计算

冒泡排序时间复杂度的计算

二分查找时间复杂度的计算

阶乘递归、斐波那契数时间复杂度的计算

LeetCode时间复杂度相关例题讲解


时间复杂度与空间复杂度的意义

我们在刷LeetCode上的题目时有时候会碰到有关时间复杂度和空间复杂度的概念,比如类似这种题:

 这种题目提出了对时间复杂度和空间复杂度的要求,那么他两究竟什么是意思呢?下面我来为你解答。

在计算机的发展过程中,由于早期条件的限制以及技术的不成熟,技术人员往往将更多的注意力放在空间上,因为内存大小有限,所以写的代码必须有严格的空间占用限制。空间复杂度的概念也由此而来。

随着技术的发展,内存越来越大,CPU速度也越来越快,对空间复杂度的重视逐渐下降,转而集中到程序执行时间速度上去了。时间复杂度就是程序算法的执行次数。注意:时间复杂度不能简单理解为程序在计算机上的运行时间,因为在不同计算机上同一条指令执行的时间可能是不同的,所以用次数来表示时间复杂度。

空间复杂度的计算

空间复杂度相对简单,并且随着技术的发展,对时间复杂度的重视超过了空间,但很多地方还是对空间复杂度有所要求的,尤其是刷题时经常会碰到这种限制。

空间复杂度怎么计算呢?有一种专门的计算方法叫大O渐进法

 大O渐进法实际上是一种估算,它不取精确的表达式和计算结果,而是取大概的结果。

如常数就直接算作1, 表达式只保留最高阶数,并且去除系数。(如:2N*(N-1)用N^2来表示)

空间复杂度计算的是算法里边变量使用个数,一般来说就只有O(1)和O(N),相对简单。

注意:一定是算法里面使用变量的个数,和给的数组、变量没有关系

 在冒泡排序里,空间复杂度是O(1),为什么不是O(N)?别看这里给了数组,元素是N个,但是我们关注的是算法里面使用变量的个数,这里面创建了end,i,exchange,也就是常数个变量,所以空间复杂度是O(1)。像这里的i,其实只创建了一次,每一次循环用的其实都是同一块空间,编译器没有开辟新的空间。

 注意:这里求的是0~n的斐波那契数组,开辟的是n个空间,所以空间复杂度是O(N)。

像O(N),它是线性的,空间复杂度随着N变化而线性变化,而O(1)是非线性的,空间复杂度不随N变化而变化。

 

 阶乘递归里面空间复杂度是O(N),试想一下,递归一次创建一块空间,层层递归,有多少层就创建了多少空间。

 比如说递归一层创建一块空间是3,N层递归就是3N,还要返回上去,那就是6N,系数可以忽略,空间复杂度就是O(N)。

所以,递归的空间复杂度就是递归深度。

时间复杂度的计算

时间复杂度计算的计算方法,也同样有大O渐进法

来看几个例子来帮助理解:

 

 

 再解释一下:像上面的实例一、二,O(N) ,  程序算法的执行次数是随着N改变而改变的,是呈线性变化的,而O(1)不是线性变化的,是固定的。

 我用编译器编个代码再给大家看一下:

 可以看到,这里循环次数设定为一千万次时,执行程序也就花了9毫秒,改为一亿次后也仅仅98毫秒,非常之快而且相差微乎其微,这也就是为什么说常数可以用1来代替。

有多种情况下的时间复杂度的计算

 这里的时间复杂度要怎么计算呢?

其实这里查找字符需要分情况(最好、最坏、平均)

1、最好情况:第一次查找就找到了O(1)

2、最坏情况:直到最后一次查找才找到,或者没找到  O(N)

3、平均情况:在差不多中间位置找到了,O(N/2)

我们时间复杂度体现的是最坏情况,所以是O(N)

冒泡排序时间复杂度的计算

二分查找时间复杂度的计算

 

 这是二分查找的一个大概流程,如果要查找的数比中间值大,那么说明数在mid的右边,下次从mid+1的位置开始查找。如果要查找的数比中间值小,那么说明数在mid的左边,下次从mid-1的位置开始查找。

知道了二分查找的原理,那么时间复杂度如何计算呢?

试想一下,一次查找就去了一半的数字,每次去一半,也就是说:2*2*2*.....2=N

运用高中数学对数的知识,时间复杂度可表示为:O(logN) ,也就是以2为底N的对数。

因为时间复杂度里面对数基本都是以2为底的,像这里的二分查找、二叉树....都是2为底的对数,所以规定以2为底的对数写成logN;

二分查找的效率确实是非常高的,从时间复杂度O(logN)就能看出来。

如果N取2^10,O(N)自然就是2^10,也就是 遍历 算法执行1024次,而二分查找算法只执行10次。把数字放大一点,如果N取2^30,遍历 算法执行的次数非常非常多,而二分查找只要30次。所以说二分查找的效率很高。

但二分查找的前提是要是个有序数组,所以对无序数组使用二分查找之前要先排序。

而快速排序的时间复杂度是O(N*logN),因此整个的时间复杂度是O(N*logN+logN)

阶乘递归、斐波那契数时间复杂度的计算

 递归的时间复杂度计算法:递归次数*每次递归函数中次数

像这里,每次递归函数中次数为O(1),因为这里递归函数里面只执行了一个三目操作符,也就是只执行了一次,虽然有个*N,但可以视为常数次,也就是O(1)。

递归次数可以画图理解:

 递归下去是N次,回上来也是N次,一共是2*N次,系数也可省略,所以时间复杂度是:O(N)。

 运用上面递归计算的公式,每次递归函数中次数还是一样,为O(1)。

递归次数也依然画图理解一下:

 可以看到,第一层是2^0 , 第二层是2^1 ......一次递归下去(返回不看了)

这样执行次数就是2^0+2^1+....+2^(N-1)-(常数),根据等比数列求和公式得出

O(2^N-1-(常数)),这里减的常数是上图里类二叉树右边缺少的式子 (    如:Fib(N-2)分不出Fib(N-1)   )。

LeetCode时间复杂度相关例题讲解

一、消失的数字

力扣https://leetcode.cn/problems/missing-number-lcci/

 这是一道面试题,如果不加在O(n)时间内完成这一限制,有很多解法,现在加了时间复杂度的条件,我们来分析一下思路:

思路1、0~n的数,缺了一个,要找出来,可以用冒泡排序的思想,两个相邻的数两两相比,后一个比前一个大1,如果不是,那说明这个位置缺了一个数,就找到了。

思路没问题,看时间复杂度符合条件吗?很遗憾,冒泡排序O(N^2),不符合。

思路2、将0~n的数加起来,减去这个缺一个数的数组,差为多少,就是缺的那个数。0~n的数相加(常数运算),时间复杂度O(1), 后面数组相加,可以用循环表示,所以O(N),符合条件。

代码如下:

思路 3、“异或^”   我们知道:a^0=a;  a^a=0,两个相同的数异或就为0且不考虑顺序(如:a^b^c^a^b=c)

所以这里只需要用0~n的数异或上数组中每一个数,就可以得到缺的那个数。时间复杂度是O(N),因为这里依次异或用的是一个循环。

代码如下:

  1. int missingNumber(int* nums, int numsSize){
  2. int x=0;
  3. for(int i=0;i<numsSize+1;++i)
  4. {
  5. x^=i;
  6. if(i<numsSize)
  7. {
  8. x^=*(nums+i);
  9. }
  10. }
  11. return x;
  12. }

 

二、只出现一次的数字力扣icon-default.png?t=M5H6https://leetcode.cn/problems/single-number/

和上一题思路三类似,可以使用异或去解。数组里只有一个元素只出现一次,其余都出现了两次,将他们依次异或就得到了单独的那个元素。

代码如下:

  1. int singleNumber(int* nums, int numsSize){
  2. int x=0;
  3. for(int i=0;i<numsSize;++i)
  4. {
  5. x^=*(nums+i);
  6. }
  7. return x;
  8. }

 

三、只出现一次的数字力扣icon-default.png?t=M5H6https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/

 这个题目就有难度了,首先两个数字不太好找,而且时间复杂度和空间复杂度都有限制。

还是从异或的角度思考解决方法。

还是和上面两题一样,先设一个ret=0,将数组里的数依次与它异或,出现两次的数就没了,只剩下出现一次的两个数x,y ,所以,ret=x^y;

到了这里,要想分别得出x,y有难度,我们找不出其中的规律。

从其他角度思考:首先,ret不为0,因为x !=y。 那么x^y就必定有1的位,那么x,y就必定有位数是不同的,即一个为0,一个为1。

 假设ret第n位为1,那么x,y第n位一个为0,一个为1。把原数组中的数按照第n位划分,为0的分1组,为1的分另一组。例如下图:

 我们可以得出:出现两次的数不一定进哪一组(也不关心),出现一次的数(3,5)一定进不同的组,于是,我们在两组分别再异或,就可以得出x,y了。

思路有了,下面写代码:

 刚接触LeetCode的小伙伴可能不清楚这样的怎么写,怎么返回值。

在这里提示了要返回一个数组arry,并且这个数组是要被malloc的,也就是说将这两个数放到数组arry里面返回,注意返回arry只是返回数组首元素,还得返回数组长度2,所以题中给了*returnSize,这是输出型参数,不是给你使用的,是从函数外部传进来的长度,让你解引用改变它的值,返回数组长度用的。

代码如下:

  1. int* singleNumbers(int* nums, int numsSize, int* returnSize){
  2. int *arry=(int*)malloc(sizeof(int)*2);
  3. *returnSize =2;
  4. int x=0,y=0;
  5. int ret=0;
  6. for(int i=0;i<numsSize;++i){
  7. ret^=*(nums+i);
  8. }
  9. int j=0;
  10. for(;j<32;++j){
  11. if( (ret>>j)&1)
  12. break;
  13. }
  14. for(int k=0;k<numsSize;++k){
  15. if( (*(nums+k)>>j)&1)
  16. {
  17. x^=*(nums+k);
  18. }
  19. else
  20. {
  21. y^=*(nums+k);
  22. }
  23. }
  24. arry[0]=x;
  25. arry[1]=y;
  26. return arry;
  27. }

关于时间复杂度和空间复杂度的讲解就这么多了,感谢大家!!!

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/632258
推荐阅读
相关标签
  

闽ICP备14008679号