数据结构-时间复杂度计算详解--向李红老师的数据结构低头 :)

来源:互联网 发布:穿越火线切屏跳软件 编辑:程序博客网 时间:2024/05/16 10:41

今天早上突然想总结一下数据结构的时间复杂度的知识。
之前学了很多遍,但是一直没有总结,所以之前参考了Algorithm还有清华大学出版的那个数据结构书,今天早上花了几个小时好好的总结一下,也送给三班的同学们。
算法的时间复杂度定义为:
在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n}=0(f(n))。它表示随问题规模n的增大,算法执行时间的埔长率和 f(n)的埔长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f( n)是问题规横n的某个函数。

根据定义,求解算法的时间复杂度的具体步骤是:
  ⑴ 找出算法中的基本语句;

  算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。

  ⑵ 计算基本语句的执行次数的数量级;

  只需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。

  ⑶ 用大Ο记号表示算法的时间性能。

  将基本语句执行次数的数量级放入大Ο记号中。
下面是推导大O的方法。

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

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

3.如果最高阶项存在且不是1,则去除与这个项相乘的常数。

简单的说,就是保留求出次数的最高次幂,并且把系数去掉。 如T(n)=2n^2+n+1 =O(n^2)
举个例子。
[cpp] view plain copy

#include "stdio.h"  int main()  {      int i, j, x = 0, sum = 0, n = 100;  /* 执行1次 */      for( i = 1; i <= n; i++)    /* 执行n+1次 */      {          sum = sum + i;               /* 执行n次 */             for( j = 1; j <= n; j++)    /* 执行n*(n+1)次 */          {              x++;                /* 执行n*n次 */              sum = sum + x;      /* 执行n*n次 */          }      }      printf("%d", sum);          /* 执行1次 */  }  

按照上面推导“大O阶”的步骤,我们来看
第一步:“用常数 1 取代运行时间中的所有加法常数”,
则上面的算式变为:执行总次数 =3n^2 + 3n + 1
(直接相加的话,应该是T(n) = 1 + n+1 + n + n*(n+1) + n*n + n*n + 1 = 3n^2 + 3n + 3。现在用常数 1 取代运行时间中的所有加法常数,就是把T(n) = 3n^2 + 3n + 3中的最后一个3改为1. 就得到了 T(n) = 3n^2 + 3n + 1)

第二步:“在修改后的运行次数函数中,只保留最高阶项”。
这里的最高阶是 n 的二次方,所以算式变为:执行总次数 = 3n^2

第三步:“如果最高阶项存在且不是 1 ,则去除与这个项相乘的常数”。
这里 n 的二次方不是 1 所以要去除这个项的相乘常数,算式变为:执行总次数 = n^2

因此最后我们得到上面那段代码的算法时间复杂度表示为: O( n^2 )

下面我把常见的算法时间复杂度以及他们在效率上的高低顺序记录在这里,使大家对算法的效率有个直观的认识。
O(1) 常数阶 < O(logn) 对数阶 < O(n) 线性阶 < O(nlogn) < O(n^2) 平方阶 < O(n^3) < { O(2^n) < O(n!) < O(n^n) }

最后三项用大括号把他们括起来是想要告诉大家,如果日后大家设计的算法推导出的“大O阶”是大括号中的这几位,那么趁早放弃这个算法,在去研究新的算法出来吧。因为大括号中的这几位即便是在 n 的规模比较小的情况下仍然要耗费大量的时间,算法的时间复杂度大的离谱,基本上就是“不可用状态”。

下面通过几个例子具体分析下时间复杂度计算过程。
一。计算 1 + 2 + 3 + 4 + …… + 100。
常规算法,代码如下:
[cpp] view plain copy

#include "stdio.h"  int main()  {      int i, sum = 0, n = 100;    /* 执行1次 */      for( i = 1; i <= n; i++) /* 执行 n+1 次 */      {          sum = sum + i;          /* 执行n次 */          //printf("%d \n", sum);      }      printf("%d", sum);          /* 执行1次 */  }  

从代码附加的注释可以看到所有代码都执行了多少次。那么这写代码语句执行次数的总和就可以理解为是该算法计算出结果所需要的时间。该算法所用的时间(算法语句执行的总次数)为: 1 + ( n + 1 ) + n + 1 = 2n + 3

而当 n 不断增大,比如我们这次所要计算的不是 1 + 2 + 3 + 4 + …… + 100 = ? 而是 1 + 2 + 3 + 4 + …… + n = ?其中 n 是一个十分大的数字,那么由此可见,上述算法的执行总次数(所需时间)会随着 n 的增大而增加,但是在 for 循环以外的语句并不受 n 的规模影响(永远都只执行一次)。所以我们可以将上述算法的执行总次数简单的记做: 2n 或者简记 n
这样我们就得到了我们设计的算法的时间复杂度,我们把它记作: O(n)

然后下面就是我看了很多本算法的书,上面绝对会提到的一位大神高斯小哥哥的事迹,我们来看看高斯的算法,我每次都喊Gauss-Algorithm,代码如下:

#include "stdio.h"  int main()  {      int sum = 0, n = 100;   /* 执行1次 */      sum = (1 + n) * n/2;    /* 执行1次 */      printf("%d", sum);      /* 执行1次 */  }  

这个算法的时间复杂度: O(3),但一般记作 O(1)。
从感官上我们就不难看出,从算法的效率上看,O(1) < O(n) 的,所以高斯的算法更快,更优秀。
这就是为什么很多算法书上都能看到高斯的影子,没办法,人家小学就间接的拿出来了等差数列的前n和表达式。而我呢,大二了,还坐在图书馆敲博客整理他小时候玩的东西。好了….不散扯了。

二。求两个n阶方阵C=A*B的乘积其算法如下:
这个矩阵乘法应该是打算法竞赛时经常用到的了,但是时间复杂度。。。。所以很容易Time Limited Error.
但是平常的一些编程中经常用到,时间复杂度分析如下。

//右边列为语句执行的频度     void MatrixMultiply(int A[n][n],int B [n][n],int C[n][n])     {  for(int i=0; i <n; i++)                       //n+1        {        for (j=0;j < n; j++)                       //n*(n+1)             {             C[i][j]=0;                                  //n^2            for (k=0; k<n; k++)                 //n^2*(n+1)                 {                C[i][j]=C[i][j]+A[i][k]*B[k][j]; //n^3                }            }        }    }  

则该算法所有语句的频度之和为:

T(n) = 2n^3+3n^2+2n+1; 利用大O表示法,该算法的时间复杂度为O(n^3)。


void test_(int n)  {      i = 1, k = 100;      while (i<n)      {          k = k + 1;          i += 2;      }  }  

设for循环语句执行次数为T(n),则 i = 2T(n) + 1 <= n - 1, 即T(n) <= n/2 - 1 = O(n)

分析下列时间复杂度
这题要注意,时间复杂度是O(n^1/2);
和之前李红老师布置的题目里面的第三小题是一种类型的题目
他的循环条件不是我们之前看的固定的,而是随着循环语句的改变而改变的
我们设while的循环次数为T(n)
那么循环语句里面的基本语句执行:
//s

void test_3(int n)  {      int i = 0, s = 0;      while (s<n)      {          i++;          s = s + i;      }  }  

和第三小问的O(log3n)是一个道理,只是这题根据之前我们说的原则只保留最高次幂的所以是T^2 然后就是O(n^1/2);
这题刚开始看确实是有点不懂….慢慢就好了。


六。Hanoi(递归算法)时间复杂度分析
我们都知道递归函数其实是在栈中运行的,我们如果单看空间复杂度的话,数据规模一旦很大的话就会造成栈溢出,stack overflow.所以在用递归的时候要谨慎,反正我是除了用递归处理一些阶乘问题还有汉诺塔问题之外,我也不怎么敢用递归,mmp,因为不会用也不敢用啊,竞赛的时候对时间限制的很紧,用不好递归肯定time limited error。然后分析递归的时间复杂度也是很重要的,真的很重要,但是有点麻烦,然后拿一个汉诺塔的例子来分析,太难的递归我不会写。所以拿个简单的来看。

void hanoi(int n, char a, char b, char c)  {      if (n==1)      {          printf("move %d disk from %c to %c \n", n, a, c);  //执行一次      }      else      {          hanoi(n-1, a, c, b);    //递归n-1次          printf("move %d disk from %c to %c \n", n, a, c);  //执行一次          hanoi(n-1, b, a, c);    //递归n-1次      }  }  

对于递归函数的分析,跟设计递归函数一样,要先考虑基情况(比如hanoi中n==1时候),这样把一个大问题划分为多个子问题的求解。
故此上述算法的时间复杂度的递归关系如下:
这个是一个写出来分段函数 -
昨天java实验课电脑被同学给弄坏了,今天用的他的电脑,我不太会用,所以就把过程写纸上拍下来上传
这里写图片描述
图片倒了,等我下课了再来弄,我要去上课了,等下上李红大大的课,好了,写了一早上的博客,先这么多了,这个博客希望可以帮助到我的朋友们,谢谢,因为时间比较匆促,所以如果有错误,请联系我,谢谢。去上课了,拜拜…….

阅读全文
2 0