【Java数据结构】算法运行时间简析

说明:本文主要内容摘自《数据结构与算法分析——Java语言描述》

1.数学基础

定义1.1 如果存在正常数c和n使得当N>=n时T(N)<=cf(N),则记为T(N)=Ο(f(N))
=》表示增长率:T(N)<=f(N)
定义1.2如果存在正常数c和n使得当N>=n时T(N)>=cg(N),则记为T(N)=Ω(g(N))
=》表示增长率:T(N)>=g(N)
定义1.3 T(N)=Θ(h(N))当且仅当T(N)=Ο(h(N))和T(N)=Ω(h(N))
=》表示增长率:T(N)=h(N)
定义1.4 如果对每一个正常数c都存在常数n使得当N>n时T(N)<cp(N),则记为T(N)=ο(p(N))
=》表示增长率:T(N)<p(N)
当T(N)=Ο(f(N))时,我们是在保证函数T(N)是在以不快于f(N)的速度增长;因此f(N)是T(N)的一个上界(upper bound)。这意味着f(N)=Ω(T(N))时,T(N)是f(N)的一个下界(lower bound)。
我们需要掌握的重要结论为:
法则1.1
如果T1(N)=Ο(f(N)),且T2(N)=Ο(g(N)),则
(a)T1(N) + T2(N) =Ο(f(N)+g(N))(直观地和非正式地可以与成max(Ο(f(N),g(N)))。
(b)T1(N) * T2(N) =Ο(f(N)) *Ο(g(N)))。
法则1.2
如果T(N)是一个k次多项式,则T(N)=Θ(N^k)
法则1.3
对任意常数k,logkN=Ο(N),即对数增长得非常缓慢。
有几点需要注意:首先,将常数或低阶项放进大Ο是非常坏的习惯。不要写成T(N)=Ο(2N2)或T(N)=Ο(N+N2 ),正确的形式是T(N)=Ο(N2)。即,在需要大Ο表示的任何分析中,各种简化都是可能发生的。低阶项一般可以被忽略,而常数也可以舍弃掉。此时,要求的精度是很粗糙的。

2.模型

为了在正式的构架中分析算法,我们需要一个计算模型。我们的模型基本上是一台标准的计算机,在机器中指令被顺序地执行。该模型有一个标准的简单指令系统,如加法、乘法、比较和赋值等。但不同于实际计算机情况的是,模型机做任何一件简单的工作都恰好花费一个时间单位。为了合理起见,我们将假设模型像一台现代计算机那样有固定的大小(比如32位)的整数并且不存在如矩阵求逆或排序这种不能在一个时间单位内完成的操作。我们还假设模型机都是有无限内存的。

2.3要分析的问题

通常,要分析的最重要的资源就是运行时间。有几个因素影响着程序的运行时间。有些因素(如编译器和环境)显然超出了任何理论模型的范畴,因此,虽然它们是重要的,但我们在这里还是不考虑它们。剩下的主要因素则是所使用的算法以及对该算法的输入。
一般来说,我们需要计算的是算法最坏情况下的运行时间。其原因之一是它对所有的输入提供了一个界限,包括特别坏的输入,而平均情况分析不提供这样的界。另一个原因是平均情况的界计算起来通常要困难得多。某些情况下,“平均”的定义可能影响分析的结果。

2.4运行时间计算

先看一个简单的例子:

public static int sum(int N){
	int partialSum;
	partialSum = 0 ;
	for (int i=0; i<=N;i++)
  		partialSum+=i*i*i;
	return partisalSum;
}

对这个程序分析很简单,第3、6行各占用一个时间单元。第5行每执行一次占用4个时间单元(2次乘法,一次加法,一次赋值),而执行N次共占用4N个时间单元。第4行在初始化i、测试i<=N和对i做自增运算隐含着开销。所有这些的总开销是初始化1个时间单元,所有的测试为N+1个时间单元,而所有的自增运算为N个时间单元,共2N+2个时间单元。我们忽略调用方法和返回值的开销,得到总量是6N+4个时间单元。因此,我们说该方法是Ο(N)。
如果每次分析一个程序都要演示所有工作,那么这项任务很快就会变成负担。幸运的是,由于我们有大Ο的结果,因此就存在许多可以采取的捷径并且不影响最后的结果。例如,第5行(每次执行时)显然是Ο(1)语句,因此精确计算它空间是2、3、还是4个时间单元是愚蠢的,这无关紧要。第1行与for循环相比显然是不重要的,所以在这里花费时间也是不明智的。这使得我们得到若干一般法则。
法则2.1——for循环
一个for循环的运行时间至多是该for循环内部那些语句(包括测试)的运行时间乘以迭代的次数(循环次数)。

for(int i=0;i<=N;i+) {
	代码块,假设运行时间为Ο(1)
}

如上for循环运行时间为N*Ο(1)=Ο(N)
法则2.2——嵌套的for循环
从里向外分析这些循环。在一组嵌套循环内部的一条语句总的运行时间为该语句的运行时间 * 该组所有的for循环的大小的乘积。
例如,下列程序片段为Ο(N2):

for(int i=0;i<=n;i++) {
	for(int j =0;j<n;j++)
		k++;
}

法则2.3——顺序语句
将各个语句运行时间求各即可(这意味着,其中的最大值就是所得的运行时间,见法则1.1(a))。
例如,下面的程序片段先是花费Ο(N),接着是Ο(N2),因此总量也是Ο(N2):

for(int i=0;i<=N;i+) 
	a[i]=0;
for(int i=0;i<=n;i++) {
	for(int j =0;j<n;j++)
		k++;
}

法则2.4——if/else语句
对于程序片段

if(condition)
	S1
else
	S2

一个if/else语句的运行时间从不超过判断的运行时间再加上S1和S2中运行时间的长者的总的运行时间,即
T(if/esle)=Ο( max(condition) + max(Ο(S1+S2) )。

3.运行时间中的对数

分析算法最混乱的方面大概集中在对数上面。我们已经看到,某些分治算法将以Ο(N logN)时间运行。此外,对数最常出现的规律可概括为下列一般法则:
如果一个算法用常数时间(Ο(1))将问题的大小削减为其一部分(通常为1/2),那么该算法就是Ο(N logN)。另一方面,如果使用常数时间只是把问题减少一个常数的数量(如将问题减少1),那么这种算法就是Ο(N)的。
如下三个例子具有对数特点:
(1)折半查找
(2)欧几里得算法:计算最大公因数
(3)整数的幂运算

4.对增长效率的常见假设性总结

不同时间复杂度说明

描述增长的数量级说明举例
常数级别1普通语句将两个数相加
对数级别logN二分策略二分查找
线性级别N循环查找最大元素
线性对数级别NlogN分治归并排序
平方级别双层循环检查所有元素对
立方级别三层循环检查素有三元组
对数级别2ⁿ穷举查找检查所有子集

不同时间复杂度算法增长率曲线图
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值