【数据结构】算法的时间复杂度和空间复杂度

什么是算法的复杂度

        算法在编写成可执行程序后,运行起来要消耗时间资源和空间资源。因此衡量一个算法的好坏,一般是从时间和空间两个角度来衡量的,即时间复杂度和空间复杂度。

        时间复杂度主要衡量一个算法运行的快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间值得一提的是,在计算机发展的早期,由于存储容量很小,所以对空间复杂度很看重。但是在如今的时代,计算机存储容量已经非常巨大,所以一般而言不会过多去关注空间复杂度。

时间复杂度

定义

        算法的时间复杂度是一个函数,它定量描述该算法的运行时间。(这里的“函数”不是指的C语言中的函数,而是数学上的带有未知数的函数表达式)。格式为 O(n) ,被称作大O的渐进表示法。

         但是,这里的运行时间并不是指一个程序跑了多少秒,程序运行的时间是没有一个方式去计算的。比如,一台搭载最新的芯片和拥有大内存的电脑,和数十年前的一台老版电脑,运行同样的一个算法,其运行时间肯定是不同的,那么同一个算法在不同的电脑上会跑出不同的时间,我们就不用具体时间来衡量时间复杂度。但是一个算法执行的时间是和算法的语句执行次数成正比例,算法中基本操作的执行次数,为算法的时间复杂度。

实例 

        我们来举一个例子,比如下方的 test() 函数,其中count++ 这个操作被执行的次数 F(N)=N,其时间复杂度就是O(N) ,这里的N就类似于数学的函数表达式里面的未知数,O( )是规定的写法:

void test(int N)
{
  intcount=0;
  for (int i=0;i<N;i++)
    {  
       count++;
    }
   return;
}

        又比如下方函数 test1() 其基本语句count1++,count2++,M--,这些被执行次数 F(N)=N*N+2*N+10 ,如下方,对比假设了F(N)=N*N 时候的情况,发现当N越大的时候, F(N)=N*N+2*N+10 这个函数表达式的后两项对F(N)的值影响越小。我们算时间复杂度的时候,只需要计算大概执行次数就行,这时候利用估算——先算出精确的表达式,再找出对F(N)影响最大的,取最大的,在test1() 里面就是 F(N)=N*N,故 时间复杂度为O(N*N)。

N=10,F(N)=130;                       当F(N)=N*N,N=10,F(N)=100

N=100,F(N)=10210;                                             N=100,F(N)=10000

N=1000,F(N)=1002010;                                       N=1000,F(N)=1000000

void test1(int N)
{
  int count1=0;
  int count2=0;
  for (int i=0;i<N;i++)
  {
     for( int i=0;i<N;i++)
     {
       count1++;
     }
  }
  
  for (int i=0;i<2*N;i++)
  {
    count2++;
  }

  int M=10;
  while(M)
  {
    M--;
  }
  return;
}

        又比如下方的例子,count++这个基本语句被执行次数F(N)=5*N+10,然后找到对F(N)影响最大的,无疑是5*N,那么其时间复杂度就是O(5*N)了吗? 并不是,在这里有规定,计算时间复杂度时,如果有和最高阶项相乘的常数,那么要去掉这个常数,这里的5*N,5就是常数,所以时间复杂度为 O(N):

void test2(int N)
{
  int count=0;
  for (int i=0;i<5;i++)
  {
    for(int j=0;j<N;j++)
    {
      count++;
    }
  }

  for(int i=0;i<10;i++)
  {
    count++;
  }
  return;
}

         上述例子都是有未知数的情况下,那么如果没有未知数呢,比如下方的例子,count++被执行了100次,但是时间复杂度却是O(1),这是因为,如果基本语句被执行次数是常数,那么时间复杂度统一写成O(1):

void test3()
{
   int count=0;
   for(int i=0;i<100;i++)
     count++;
   return;
}

         又比如下方例子,同时出现M、N两个未知数,其时间复杂度就是O(M+N)。但是如果给出限定条件比如M远大于N,此时时间复杂度就是O(M);如果N远大于M,那么就是O(N):

void test4(int M,int N)
{
  couont=0;
  for(int i=0;i<M;i++)
    count++;
  
  for (int i=0;i<N;i++)
    count++;
  
  return;
}

通过以上几个例子,不难得出大O渐进表示法的规律:

1、用常数1取代运行时间中的所有加法常数。

2、在修改后的运行次数函数中,只保留最高阶项。

3、如果最高阶项存在且不是1,则取出与这个项相乘的常数,得到的结果就是大O阶。

二分查找的时间复杂度

        二分查找的时间按复杂度是log2^N(2为底)的原因是(参考下图,黑色的为辅助理解还原折半过程的线),假设在长度为N(有N块空间)的内容里面查找a,现在已经找到a,逆向还原该查找过程,就是原本是一块a区间,第n次查找之前,是2块,第n-1次查找之前是4块,第n-2次查找之前是8块……  

 可以写成:
1*2*2*2*2*2……=N  (假设查找了n次,即要还原n次)

1*2^n=N

n=log2^N(2为底)

         在这里就不免有人提出疑问,如果我要查找的那个数,恰好就在这块空间的中间呢,那么是不是一次就找到了;如果在1/4空间处,那么就找两次;1/8空间处,就要找三次……这些情况下的时间复杂度都是O(1),可是为什么二分算法的时间复杂度是O(log 2^N)呢?

        这是因为,我们计算时间复杂度的时候,是按照最坏的情况计算的,即基本条件最多能被执行多少次。比如下方这段代码,假设src指向的字符串长度是N,则其时间复杂度是O(N):

 假设src指向的字符串是“hello world”

target='h' 时,      最好情况   

target='w'时,      平均情况

target=’d‘时,      最坏情况 ,此时的情况来计算时间复杂度

char* strchar(char* src,char taget)
{
   while(*src)
  {
    if (*src == target)
       return src;
    else
       ++src;
  }
}

算法的空间复杂度

定义

         算法的空间复杂度也是一个数学函数表达式,是对一个算法运行过程中临时额外占用空间大小的量度

        空间复杂度不是指程序占用了多少byte的空间,因为这个也没有太大意义,空间复杂度算的是变量个数,其方法也是大O渐进法。

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

实例 

冒泡排序的空间复杂度

         比如下方的BubbleSort()函数,它的空间复杂度是O(1),其过程是这样的:

        首先,这个函数已经确定好的变量是int* a,int n,这两个不是额外申请的空间,是已经确定的。

        其次,寻找出额外开辟的空间。在嵌套的循环里面,分别开辟了i、j两个变量,假设n=10,代入去看,i=0的时候,内层循环 j<9,执行九次内层循环,在这一轮循环里面,为了存储 i 开辟的空间一直不变,存储 j 开辟的空间还是那个空间,只是里面的值改变。但是,在这一轮循环执行完之后,为了存储 j 开辟的空间就会被销毁。

        当 i=1时,为了存储 i 开辟的空间依然是那个空间,上一轮循环中的 为了存储 j 开辟的空间 已然被销毁,这时内层循环又有一个变量 j ,又需要开辟一个空间来存储 这一轮的 j ,根据函数栈帧的知识,不难明白,此时开辟的空间和上一轮被销毁的空间是同一块空间,所以并没有额外申请空间,循环结束也只是申请了两个空间来存储 i 和 j。

        由于2是常数,所以其空间复杂度是O(1)。

void BubbleSort(int* a,int n)
{
   assert(a);
   for (int i=0;i<n-1;i++)
   {
     for (int j=0;j<n-i-1;j++)
     {
       if (a[j] > a[j+1])
          swap(&a[j],&a[j+1]);
     }
   }
   return 0;
}

斐波那契数列的空间复杂度

        如下,输入N,返回前N个斐波那契数列的函数,其空间复杂度为O(N)。

        首先,N是已经确定好的变量,不会额外申请空间。

        其次,arr开辟了N+1个存储int类型数据的空间,然后i又开辟了1个存储int类型数据的空间,所以一共开辟了N+2个额外的,存储int类型数据的空间。

        最后,和上面时间复杂度的规则类似,去掉常数,其空间复杂度为O(N)。

int * fib(int N)
{
    int *arr=(int*)malloc((N+1)*sizeof(int));
    arr[0]=1;
    arr[1]=1;
    for (int i=2;i<=n;i++)
    {
       arr[i]=arr[i-1]+arr[i-2];
    }
    return arr;
}

         关于算法的时间复杂度和空间复杂度就介绍到这里啦!!喜欢的话不要忘记一键三连哦!!!

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力努力再努力.xx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值