数据结构与算法(一)
算法效率的度量方法:
1. 事后统计方法:通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
缺陷:是根据以及编制好的程序去测试,如果测试的是糟糕的算法,会功亏一篑。
2. 事前分析估算方法:在计算机程序编写前,依据统计方法对算法进行估算。
高级语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
- 算法采用的策略,方案。
- 编译产生的代码质量
- 问题的输入规模
- 机器执行指令的速度
所以:一个程序的运行时间依赖于算法的好坏和问题的输入规模。
在编写程序的时候,我们不关心语言、所用的计算机只关心它所实现的算法。
在分析程序的运行时间的时候,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤,把基本操作的数量和输入模式进行关联。
函数渐进增长:
n=1时,算法A1效率不如算法B1;当n=2时,两者效率相等;当n>2时,算法A1开始优于算法B1,随着n的继续增加,算法A1比算法B1逐渐拉大差距。所以总体上算法A1比算法B1优秀
定义:给定2个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐进快于g(n)
例如如下算法的增长率:
通过这组数据可以看出,当n的值非常大的时候,3n+1已经不能和2n^2的结果进行比较,最终几乎是可以忽略不计的,算法G在跟算法I基本已经重合了,
结论:判断一个算法的时候,函数中的常数和其他次要项常常可以忽略,关注主项(最高项)的阶数。
注意:不能通过少量的数据来判断一个算法的好坏。
算法时间复杂度:
定义:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级,算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))。他表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度,其中f(n)是问题规模n的某个函数。
用大O()来体现算法时间复杂度的记法,一般情况下随着输入规模n的增大,T(n)增长最慢的算法为最优算法。
推导大O阶的方法
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行函数中,只保留最高项。
- 如果最高项存在且不是1,则除这个项相乘的常数。
- 得到的最后的结果就是O()阶
例如:
1.常数阶
int sum=0,n=100; printf("hello word\n"); printf("hello word\n"); printf("hello word\n"); printf("hello word\n"); sum=(1+n)*n/2
所有常数项均可以看做是1,时间复杂度为O(1);
2.线性阶:
含有非嵌套循环涉及线性阶,就是随着问题规模n的扩大,对应计算次数呈直线增长。
int i,n=100,sum=0; for(i=0;i<n;i++){ sum=sum+i; }
循环中的代码需要执行n次,所以时间复杂度为O(n)
3.平方阶:
int i,j,n=100; for(i=0;i<n;i++){ for(j=0;j<n;j++){ printf("hello word") } }
外层执行一次,内层执行100次,需要执行100*100次,n的平方次,所以时间复杂度为O(n^2),
如果有三个这样的循环,则时间复杂度为O(n^3)
分析下,由于当i=0时,内循环执行了n次,当i=1时,内循环则执行n-1次.....当i=n-1时,内循环执行1次,所以总的执行次数应该是:- n+(n-1)+(n-2)+...+1 = n(n+1)/2,用我们推导大O的攻略,第一条忽略,因为没有常数相加。第二条只保留最高项,所以n/2这项去掉。第三条,去除与最高项相乘的常数,最终得O(n^2)。
4.对数阶
int i=1,n=100; while(i<n){ i=i*2; }
由于2^x=n得到x=log(2)n,所以这个循环的时间复杂度为O(log(2)n)
函数调用的时间复杂度分析:
例1:
int i,j; for(i=0;i<n;i++){ function(i); } void function(int count){ printf("%d",count); }
函数体是打印这个参数,function函数的时间复杂度是O(1),所以整体的时间复杂度就是循环的次数O(n)。
例2:
void function(int count){ int j; for(j=count;j<n;j++){ printf("%d",j); }count++;}
function内部的循环次数随count的增加而减少,所以它的时间复杂度为O(n^2)。
例3:
n++; ##1 function(n);##一个内部循环的函数 O(N^2)for(i=0;i<n;i++){ function(i);##内部循环的函数 } ## O(N^2) for(i=0;i<n;i++){ for(j=i;j<n;j++){ printf("%d",j); }}##O(n^2)
时间复杂度为O(n^2)
常见的时间复杂度:
常用时间复杂度所耗费的时间从小到大依次是:
O(1)<O(logn)<(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)
最坏情况与平均情况:
算法的分析也是类似,我们查找一个有n个随机数字数组中的某个数字,最好的情况是第一个数字就是,那么算法的时间复杂度为O(1),但也有可能这个数字就在最后一个位置,它的时间复杂度为O(n),平均运行时间是期望的运行时间,最坏的运行时间是一种保证,在应用中,这是一种最重要的需求,通常除非特别的指定,我们提到的运行时间都是最坏情况的运行时间。
算法的空间复杂度:
算法的空间复杂度通过计算算法所需的存储空间实现,算法的空间复杂度的计算公式为:S(n)=O(f(n)),其中n为问题的规模,f(n)为语句关于n所占空间的函数。
一般情况"复杂度"指的是时间复杂度