王道数据结构笔记01—深入了解算法的时间复杂度与空间复杂度

算法的时间复杂度

时间复杂度的概念

代码执行前预估算法时间开销T(n)问题规模n的关系(T表示时间’time’),T = o(n)

栗子1

分析以下代码的空间复杂度

int main (){
  loveYou(3000);
}

void loveYou(int n){
  int i = 1;//编号1
  while(i<=n){//编号2
    i++;//编号3
    printf("I Love You More Than %d/n Times",&i);//编号4
  }
  printf("I Love You More Than %d/n Times",&n);//编号5
}

部分输出结果

I Love You 2996 Times
I Love You 2997 Times
I Love You 2998 Times
I Love You 2999 Times
I Love You 3000 Times
I Love You 3001 Times
I Love You More Than 3000 Times

下面我们来分析栗子1中的五条语句各自的语句频度

1号–1次
2号–3001次
3号4号–3000次
5号–1次

得出结论T(3000) = 1 + 3001 + 2*3000 + 1
时间开销与问题规模n的关系:
T(n) = 3n+3

问题1:是否可以忽略表达式的某些部分

假设有如下时间开销与问题规模n的关系:

T1(n) =o(3n)+o(3)
T2(n) =o(3n²)+o(3n)+o(1000)
T3(n) =o(3n³)+o(n²)+o(1000)

根据我们所学过的高数知识,得出结论:
可以只考虑阶数高的部分,其前面的常数可以省略
式子中的常数项也可以忽略
以上表达式化简为:

T1(n) =o(n)
T2(n) =o(n²)
T3(n) =o(n³)

常见公式的阶数排名

口诀:常对幂指阶

o(1)<o(log2n)<o(n)<o(nlog2n)<o(n²)<o(n³)<o(2n)<o(n!)<o(nn)

问题2:如果有几千行代码,是否需要一行行数

结论一:顺序执行的代码只会影响常数项,可以忽略。
结论二:只需要挑选一个循环中的基本操作分析他的执行次数与n的关系即可。

栗子2(嵌套循环)

下面看栗子的代码

void loveYou(int n){
  int i = 1;
  while(i<=n){
    i++;
    printf("I Love You  %d Times\n",i);
    int j;
    for(j=1;j<=i;j++){
        printf("I am Icon Man\n");
    }
  }

  printf("I love you more than %d times\n",n);
}

该嵌套循环的特点:
1.外层循环执行n次
2.内层循环共执行n²次

所以按照上文所述时间复杂度为:
T(n)=O(n)+O(n²)=O(n²)
结论3:多层嵌套循环,只需关注最深层循环了几次

练习1

分析下列代码的时间复杂度

void loveYou(int n){
  int i = 1;
  while(i<=n){
    i=i*2;
    printf("I Love You  %d Times\n",i);
  }
  printf("I love you more than %d times\n",n);
}

有循环条件得,循环结束条件为 2x>n
所以循环次数为 log2n +1
T(n) = O(x)= O( log2n )

练习2

分析下列代码的时间复杂度

void loveYou(int flag[],int n){
    printf("I am Icon Man\n");
    int i;
    for(i=0;i<n;i++){
        if(flag[i]==n){
            printf("I love you");
            break;
        }
    }
}

//flag数组中乱序存放了1~n这些数字

根据题目条件。输入的flag数组不同,复杂度也不同
最好的情况:n在第一位
T(n)=O(1)
最坏的情况:n在最后一位
T(n)=O(n)

算法的空间复杂度

程序运行时的内存需求

在这里插入图片描述

在这段代码中,无论问题规模怎么变,算法运行所需的空间都是固定的常量(只需要n和i两个变量),所以算法的空间复杂度为:
S(n)=O(1)
S表示’Space’

栗子1

void test(int n){
  int flag[n];
  int i;
  //省略后面的代码
}

设一个int变量占四个字节
所需内存空间会随问题的规模n变化,为4+4n+4 = 4n+8
和时间复杂度省略的项类似
本栗子空间复杂度为
S(n)=O(n)

栗子2

void test(int n){
  int flag[n][n];
  int other[n];
  int i;
}

本栗子的空间复杂度为:
T(n)=O(n²)+O(n)+O(1)=O(n²)

T(n)=O(n²)+O(n)+O(1)=O(n²)
这个式子同样适用于空间复杂度!

函数递归调用时带来的内存开销

void loveyou(int n){
  int a,b,c;
  if(n>1){
    loveyou(n-1);
  }
  printf("I love you");
}

改段代码定义了4个变量nabc,并不停递归调用,并且每次调用时都会开辟四个变量的空间,16个字节。
所以问题规模为n时,递归n’次,需要16n的空间

S(n)=O(16n)=O(n)

结论:空间复杂度 = 递归调用的深度(大多数情况)

栗子3

void loveyou(int n){
  int flag[n];
  if(n>1){
    loveyou(n-1);
  }
}

在上述代码中,每次递归都会生成一个长度为n的数组,所以所需空间为:1+3+3+……[n(1+n)]/2

1 2 \frac{1}{2} 21n²+ 1 2 \frac{1}{2} 21n
T(n)=O( 1 2 \frac{1}{2} 21n²+ 1 2 \frac{1}{2} 21n)=O(n²)

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值