算法是为求解一个问题需要遵循的、被清楚地指定的简单指令集合。确定一个问题的算法,最重要的是确定该算法需要的时间空间等资源量。下面将介绍估计算法复杂度的方法。
目录
1. 一些定义
以下定义是在函数间建立一种相对的级别。由于给定两个函数,单纯的比较相对点上的数值没有意义,于是我们一般比较他们的相对增长率。
- 如果存在正常数
和
使得当
时,
,则记为
(也就是说T(N)的增长率小于等于f(N)的增长率,f(N)是T(N)的上界)
- 如果存在正常数
和
使得当
时,
,则记为
举个例子,如果,那么
虽然上界有很多,我们一般会选择一个最接近的上界。注意,一般不会将常数或低阶项放入大O。
常见的增长率排序:
2. 要分析的问题
首先需要一个计算模型,假设该模型有一个简单指令系统,可以计算加法、乘法、赋值等。模型做任意一个简单计算都需要花费一个单元的时间。在这个模型下分析在最坏的情况下算法的运行时间。
3. 运行时间计算
一个简单的例子:
Sum(int N)
{
int i, sum;
sum = 0; // 1
for(i = 1; i <= N; i++) // 2
sum += i * i * i; // 3
return sum; // 4
}
第一行和第四行各占一个时间单元,第三行每次执行占四个时间单元(2乘法,1加法,1赋值),执行N次占用4N时间单元。第二行占用2N+2个时间单元(1赋值,N+1测试,N自增)。所以得到总量为6N+4。该函数为O(N)的。
一般法则:
- for循环:循环内语句运行时间 × 迭代次数
- 嵌套for循环:从里向外分析,一组嵌套循环内部的一条语句的总运行时间为该语句运行时间 × 该组所有for循环大小乘积。
- 顺序语句:求和即可
- if/else:从不超过判断的时间加上if和else中运行较长的时间。
如果有递归过程,有些递归过程可以分析为for循环,有些不能,这时分析将设计求解递推过程。
运行时间中的对数:
分析算法最混乱的方面大概在对数上。
对数最常出现的规律可以概括为:如果一个算法用常数时间(O(1))将问题的大小消减为其一部分(通常为1/2),那么该算法就是O(log(N))的。
例如:折半查找
Search(const ElementType A[], ElementType X, int N)
{
int low, mid, high;
low = 0; high = N - 1;
while(low <= high)
{
mid = (low + high) / 2;
if(A[mid] < X):
low = mid + 1;
else if(A[mid] > X):
high = mid - 1;
else:
return mid;
}
return NotFound;
}
每次迭代在循环内所有花费为O(1),循环从high-low=N-1开始,>=-1结束。每次循环后,循环次数折半,于是循环次数最多为。因此运行时间为O(logN)。