第2章 函数的渐近增长---《大话数据结构》读书笔记

判断算法A B哪个更好

假设2个算法的输入规模都是n

算法A要做2n+3次操作

算法B要做3n+1次操作

次数

算法A(2n+3)

算法A’(2n)

算法B(3n+1)

算法B’(3n)

n=1

5

2

4

3

n=2

7

4

7

6

n=3

9

6

10

9

n=10

23

20

31

30

n=100

203

200

301

300

分析:

n=1时,算法A的效率不如算法B。而当n=2时,两者效率相同;当n>2时,算法A就开始优于算法B了。

随着n的增长,算法A比算法B越来越好(执行的次数比B要少)

结论:算法A总体上好过算法B

 

定义:输入规模n在没有限制的情况下,只要超过一个数值N,这个函数就总是大于另外一个函数,我们称函数是渐进增长的。

 

函数的渐近增长

给定两个函数f(n)g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐近快于g(n)

 

深入分析

1.       我们可以忽略加法常数。

2.       与最高次项相乘的常数并不重要。

3.       最高次项指数大的,函数随着n的增长,结果也会变得增长特别快。

结论:判断一个算法效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数。

某个算法,随着n的增大,它会越来越优于另外一算法,或者差于另一算法。

 

算法时间复杂度

在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)n的变化情况确定T(n)数量级。算法的时间复杂度,也就是算法的时间度量,记住:T(n)=O(f(n))

它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。

 

随着n的增长,T(n)增长最慢的算法最优。

前面的3个求和算法的时间复杂度分别为O(n),O(1),O(n^2),它们的非官方名称,O(1)叫常数阶,O(n)叫线性阶,O(n^2)叫平方阶。

 

算法导论中关于O的解释

O(g(n))={ f(n): 存在正常数cn0,使对所有n>=n0,有0<=f(n)<=cg(n) }

 

推导大O阶方法

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

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

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

得到的结果就是大O阶。

 

常数阶

Int32 sum = 0, n = 100;//执行了1

sum = (1 + n) * n / 2;//执行了1

Console.WriteLine("sum={0}", sum);//执行了1

这个算法的运行次数是f(n)=3,根据推倒大O阶的方法,第一步就是把常数阶项3改为1

所以这个算法的时间复杂度为O(1)

另外一个例子:

Int32 sum = 0, n = 100;//执行了1

sum = (1 + n) * n / 2;//执行了1

sum = (1 + n) * n / 2;//执行了2

sum = (1 + n) * n / 2;//执行了3

sum = (1 + n) * n / 2;//执行了4

sum = (1 + n) * n / 2;//执行了5

sum = (1 + n) * n / 2;//执行了6

sum = (1 + n) * n / 2;//执行了7

sum = (1 + n) * n / 2;//执行了8

sum = (1 + n) * n / 2;//执行了9

sum = (1 + n) * n / 2;//执行了10

Console.WriteLine("sum={0}", sum);//执行了1

这个算法的时间复杂度还是O(1),这种与问题大小无关(n的多少),执行时间恒定的算法,我们称为具有O(1)的时间复杂度,又叫常数阶。

 

线性阶

分析算法的复杂度,关键就是要分析循环结构的运行情况。

for (Int32 i = 0; i < n; i++)

{

    //时间复杂度为O(1)的程序步骤序列

}

上面代码的时间复杂度为O(n)

 

对数阶

Int32 count = 1, n = 100;

while (count < n)

{

count = count * 2;

//时间复杂度为O(1)的程序步骤序列

}

每次count乘以2以后,就距离n更近了一分,也就是说2要乘以多少个2后大于n,则退出循环,由2^x=n得到x=log2n。所以这个循环的时间复杂度为O(logn)

 

平方阶

for (Int32 i = 0; i < n; i++)

{

    for (Int32 j = 0; j < n; j++)

    {

//时间复杂度为O(1)的程序步骤序列

    }

}

这个算法的时间复杂度为O(n^2)

如果把外层循环的次数改成了m,时间复杂度就变为O(m*n)

总结:循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。

对于下面的循环

for (Int32 i = 0; i < n; i++)

{

    for (Int32 j = i; j < n; j++)

    {

   //时间复杂度为O(1)的程序步骤序列

    }

}

i=0时,内部循环执行了n次,当i=1时,执行了n-1次,1=n-1时,执行了1次。

所以总的执行次数为

N+(n-1)+(n-2)+…+1=

 

用推导大O的方法,保留最高项,以此留下n^2/2;去除这个想的常数项,最终这个算法的时间复杂度为O(n^2)

 

方法调用的时间复杂度

for (Int32 i = 0; i < n; i++)

{

    Test(i);

}

static void Test(Int32 i)

{

Console.WriteLine("i={0}", i); //时间复杂度为O(1)的程序步骤序列

}

这个代码的时间复杂度为O(n)

 

常见的时间复杂度

 

执行次数函数

非正式术语

12

O(1)

常数阶

2n+3

O(n)

线性阶

3n^2+2n+1

O(n^2)

平方阶

5log2n+20

O(logn)

对数阶

2n+3nlog2n+19

O(nlogn)

nlogn

6n^3+2n^2+3n+4

O(n^3)

立方阶

2^n

O(2^n)

指数阶

 

常用的时间复杂度从小到大排序:

O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)

 

最坏情况与平均情况

最坏情况运行时间是一种保证,那就是运行时间将不会再坏了。在应用中,这是一个最重要的需求,通常,除非特别指定,我们提到的运行时间都是最坏情况的运行时间。

平均运行时间是所有情况中最有意义的,因为它是期望的运行时间。

 

转载于:https://www.cnblogs.com/hailan2012/archive/2012/01/01/2309550.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值