- 算法效率的度量方法:
- 事后统计法:通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。缺陷:必须依据算法事先编制好测试程序,通常需要花费大量时间和精力。不同测试环境差别也很大。
- 事前分析估算法:在计算机程序编写签,依据统计方法对算法进行估算。
- 高级语言编写的程序在计算机上运行时所耗的时间取决于下列因素:
- 算法采用的策略,方案
- 编译产生的代码质量
- 问题的输入规模
- 机器执行指令的速度
- 研究算法的复杂度,侧重的是研究算法随着输入规模扩大增长量的一个抽象,而不是精确地定位需要执行多少次。在分析一个算法的运行时间时,重要的是把基本操作的数量和输入模式关联起来。
- 函数渐进增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐近快于g(n)。
- 判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高项)的阶数。
- 算法的时间复杂度:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记做:T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。(这里的时间指的是执行的次数)
- 用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。
- 一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法。
- 如何分析一个算法的时间复杂度:
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行次数函数中,只保留最高阶项
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数
- 得到最后的结果就是大O阶
-
int sum = 0, n = 100; printf("aaaaaaaaaaaa "); printf("aaaaaaaaaaaa "); printf("aaaaaaaaaaaa "); printf("aaaaaaaaaaaa "); printf("aaaaaaaaaaaa "); printf("aaaaaaaaaaaa "); sum = (1+n)*n/2 //这段代码的时间复杂度为O(1),因为这段代码执行了8次,没有与n相关,常数都简化为1
- 线性阶:一般含有非嵌套循环设计线性阶,线性阶就是随着问题规模n的扩大,对应计算次数呈直线增长。
-
int i, n = 100, sum = 0; for(i = 0; i<n; i++) { sum = sum + i; }
上面这段代码的循环复杂度为O(n),因为循环体中的代码需要执行n次
-
- 平方阶
-
int i,j,n = 100; for(i = 0; i < n; i++) { for(j = 0; j < n; j++) { printf("aaaaaaaaaaaa "); } }
上面这段代码的时间复杂度为O(n²)。
-
int i,j,n = 100; for(i = 0; i < n; i++) { for(j = i; j < n; j++) { pringtf("aaaaaaaaaaaaaa "); } }
分析,由于当i=0时,内循环执行了n次,当i=1时,内循环则执行n-1次。。。。。。当i=n-1时,内循环执行1次,所以总的执行次数应该是n+(n-1)+(n-2)+...+1=n(n+1)/2用上面的简化方法来简化½n²+½n,这个式子中没有常数项不用考虑第一条,根据第二条只保留最高项,去掉½n这一项,根据第三条去掉与最高项相乘的常数½,最终得到O(n²)
-
- 对数阶
-
int i = 1, n = 100; while(i < n) { i = i*2; }
由于每次i*2之后就距离n更近一步,假设有x个2相乘后大于或等于n,则会退出循环。于是由2x=n得到x=log2n,所以这个循环的时间复杂度为O(logn)。
-
-
int i,j; for(i = 0; i < b; i++) { function(i); } void function(int count) { printf("%d",count); }
function函数的时间复杂度是O(1),所以整体的时间复杂度就是循环的次数O(n)。
-
-
int i,j; for(i = 0; i < b; i++) { function(i); } void function(int count) { int j; for (j = count; j < n;j++) { printf("%d",j); } }
和平方阶中第二个例子一样,function内部的循环次数随count的增加而减少,所以根据方法简化后的时间复杂度为O(n²)
-
-
n++;-------------------------------------执行1次 function(n);-----------------------------执行n²次 for(i = 0;i < n; i++) {------------------执行n²次 function(i); } for(i = 0;i < n; i++) {------------------执行n²次 for(j = i;j < n; j++) { printf("%d",j); } } void function(int count) { int j; for (j = count; j < n;j++) { printf("%d",j); } }
上面几次操作是并列的,整体的执行次数应该是各个操作执行次数之和3n²+1,简化后得到时间复杂度为O(n²)
-
- 常见的时间复杂度
例子 | 时间复杂度 | 术语 |
5201314 | O(1) | 常数阶 |
3n+4 | O(n) | 线性阶 |
3n²+4n+5 | O(n²) | 平方阶 |
3log2n+4 | O(logn) | 对数阶 |
2n+3nlog2n+14 | O(nlogn) | nlogn阶 |
n3+2n2+4n+6 | O(n3) | 立方阶 |
2n | O(2n) | 指数阶 |
17. 常见的时间复杂度所耗费的时间从小到大依次是
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < (nn)
18. 平均运行时间是期望的运行时间
19. 最坏运行时间是一种保证。在应用中,这是一种最重要的需求,通常除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
20. 算法的空间复杂度通过计算算法所需的存储空间实现,算法的空间复杂度的计算公式记做S(n) = O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。
21. 通常,我们都是用“时间复杂度”来指运行时间的需求,用“空间复杂度”指空间需求。
22. 当直接让我们求“复杂度”时,通常指的是时间复杂度。