时间复杂度和空间复杂度

本文详细解释了时间复杂度和空间复杂度的概念,通过示例说明如何计算算法的执行时间和所需存储空间,以及不同情况下的复杂度,如线性、平方、对数和常量级,同时讨论了递归调用对空间复杂度的影响。
摘要由CSDN通过智能技术生成

时间复杂度

作用:定性的描述一个算法的运行时间
更具体的说,就是算法的执行时间是随着数据规模线性增长,或者是平方增长,又或者是对数增长
数据规模 :一般体现在算法输入的参数中
方法的参数可以是一个整数 int n, n的值可以是1、2、3、4,也可以是10^2,10^5,10^8.....,表示这个方法将来输入的数据量
方法的参数也可以是一个数组 int[] arr,那么数据规模就是数组的长度
方法的参数也可以是一个链表LinkedList,那么数据规模就是链表节点的数量

理解时间复杂度之前,先要明白
同一个算法,在不同的机器上,执行的时间是不同的,但是在不同的机器上,算法语句执行次数是一样的
所以,可以用语句的执行次数来衡量算法的执行时间

示例1:
public static long sum(int n){
long res = 0; //(1) 1
int i = 0; //(2) 1
for(; i<= n;i++){ //(3) 2n两个语句,每个语句是n次
res += i; //(4) n
}
return res; //(5) 1
}
以上代码,一共执行了3n+3次,那么时间复杂度就是O(3n+3)
计算的时候可以把系数跟常量去掉,得到结果是O(n)
为什么可以去掉常量跟系数 ?
随着数据规模n的不断增加,系数、常量对执行时间的增长趋势影响就比较小了,所以可以去掉
比如 , n = 1 , n+3 = 4
n=100 ,n +3 = 103
n=1000,n+3 = 1003
可以看到,常量对于最终数据次的影响随着数据规模变大,影响比例变小

示例2 :
public static long sum2(int n) {
long res = 0; //(1) =>1
int i=0; //(2) =>1
for(; i<=n; 1++){ //(3) =>2n
int j=0; //(4) =>n
for( ;j<=n;j++){ //(5)=>2 n^2
res=res+(i*j); //(6) => n^2
}
}
return res; //(7)=>1
}
以上代码一共执行了3 n^2 + 3n +3 次 ,那么去掉系数常量低阶之后
得到这个代码的时间复杂度是O(n^2)

结论 :
只要找到执行次数最多的语句,看看执行多少次,那么时间复杂度就是多少
比如,示例1中,执行次数最多的是第三第四行,n次,时间复杂度就是0(n),示例2中,执行次数最多的是第五第六行,n^2次,时间复杂度就是O(n^2)

示例3:
for (int i=1;i<=n; itt) { // (1)
for (int j=1; j<=n; j++){ // (2)
for (int k=1;k<=n; k++){ // (3)
Sytenuout.println(i+j+k); //(4)
}
}
}
这个示例中,执行次数最多的应该是第四行代码n^3,所以,时间复杂度是O(n^3)

但是以上的示例,并不能说明时间复杂度是按照循环的次数来决定就是n的多少次方

示例4:
int i=1; // (1)
for (;i<=n;i += i) { // (2)
System.out .println(i); //(3)
}
这个示例是一个单循环语句,但是它的时间复杂度不是O(n)
上面的语句执行最多的行数应该是第三行,执行的次数并不是由n直接决定的,而是跟i挂钩
比如,n = 10
第一次: i = 1 i = 2^0 = 2^1-1
第二次: i = 2 i = 2^1 = 2^2-1
第三次: i = 4 i = 2^2 = 2^3-1
第四次: i = 8 i = 2^3 = 2^4-1
第五次 : i = 16 i = 2^4 = 2^5-1
第五次的时候循环已经结束了,虽然输入的n为10,但是循环次数只有4次
按照以上规律,
第x次,i = 2(x-1) ,因为i <=n ,所以 2(x-1) <= n
所以,得出x = 1+log2n,去掉常量和系数,时间复杂度就是O(logn)

示例5:
int sum = 0;
for(int i=0;i<=10000;i++){
sum += i;
}
以上示例中,执行次数最多是1万次,跟数据规模n没有关系
那么这种数据规模的时间复杂度就是O(1),常量级别的

以上几个时间复杂度类型中
按照数据规模(执行次数)排序 : logn < n < n^2
按照算法性能 : O(1) > O(logn) > O(n) >O(n^2)

空间复杂度

定义 :算法的存储空间与数据规模之间的成长关系
数据规模的概念同时间复杂度中的概念一样
存储空间, 包含两部分内容,
一部分是代码编译之后的指令,
另一部分是代码运行时候产生的数据
一般来说,指令在编译后占用的空间是不变的
数据中则可以再分为
1,输入数据,比如一下代码中的nums这个数组
2,临时数据,比如算法运行时定义的n、i、tmp、j这些变量
所以空间复杂度这里的空间其实主要就是指算法运行时,临时产生数据占用的内存空间
常见的几种空间复杂度解析 :
示例1:
public void func(int[ ] nums) {
int n = nums.length;
int i=0;
int j=0;
//.....
int k=0;
double d = 0;
}
以上的示例中,不管定义了多少个变量,只要变量占用的内存空间大小和输入规模n没有关系,那么空间复杂度就是O(1)

示例2:
public void func(int[ ] nums) {
int n = nums.length;
int i=0;
int j=0;
int[] arr = new int[n];
//.....
int k=0;
double d = 0;
}
如果某个算法中,声明的变量空间跟输入数据规模n成线性正比关系,那么空间复杂度就是O(n)
比如以上的示例中,声明了一个长度为n的数组,不管是2n还是3n,空间复杂度都是O(n)

示例3:
public void func(int[ ] nums) {
int n = nums.length;
int i=0;
int j=0;
int[][] arr = new int[n][n];
//.....
int k=0;
double d = 0;
}
如果某个算法中,声明的变量空间使用输入规模的n * n
那么空间复杂度就是O(n^2)
以上的示例中,声明了一个长度为n*n的二维数组,空间复杂度都是O(n^2)

一般来说,了解以上三种O(1),O(n),O(n^2)就可以了

那么再来看看,上面一个插入排序的空间复杂度是多少
public void insertSort(int[] nums) {
int n = nums .Length;
for(int i=0;i<n;1++){
int tmp = nums[i];
int j=i;
for(;i>0&tmp<nums[j-1];j--){
nums[j] = nums[j - 1];
}
nums[j] = tmp;
}
}
讲解之前,再复习一下方法再内存中的调用
以上代码,
main方法先执行,执行的时候,调用了add方法,add方法中又调用了mod方法
所以,他们栈里的结构如下
main先进栈 ,然后add进栈,再然后mod进栈
mod执行完了,mod出栈,add执行完,add出栈,main执行完,main出栈,程序结束
 


那么在栈中,每个方法其实都可以看成是一个栈中的栈帧
每个栈帧中,又包含函数的参数、局部变量、返回地址等信息

那么当我们执行一个插入排序的时候,栈空间中的变化是什么样子的 ?
 


当程序开始执行,这个时候,在栈中,存在一个插入排序的栈帧
栈帧中nums在最下面,指向一个堆引用,然后n、i、tmp、j一次进入,一次循环后,j和tmp使用结束了就会出栈,下一次循环tmp和j再次进入这个栈帧
所以,不管tmp和j定义了多少次,代码在运行过程中,只占用了两个空间,一个栈空间,一个堆空间
所以,这个算法中,产生的临时变量就是 n、i、tmp、j这几个变量,空间复杂度就是O(1)

再来看看,递归中的空间复杂度是多少
//递归求阶乘
public Long f(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
return n * f(n - 1);
}
 


每一次递归调用,都是再将方法拉进栈,所以每调用一次,都会有一个栈帧的存在,每个栈帧都是一个内存空间
所以,最终的栈的结构如上图,调用的次数跟数据n是成正比关系,这里的空间复杂度就是O(n)
所以,在递归中,递归深度有多大,空间复杂度就有多大

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值