算法(algorithm)是为求解一个问题需要遵循的、被清楚地指定的简单指令的集合。
2.1 数学基础
定义:
如果存在正常数 和 使得当 时 ,则记为 。(上界,小于等于)
如果存在正常数 和 使得当 时 ,则记为 。 (下界,大于等于)
当且仅当 且 。(等于)
如果 且 ,则 。 (上界,小于)
以上定义的目的是要在函数间建立一种相对的级别。
重要结论:
法则 1:
如果 且 ,那么
(a) ,
(b)
法则 2:
如果 是一个 次多项式,则 。
法则 3:
对任意常数 , 。它告诉我们对数增长地非常缓慢。
注意事项:
1. 在需要大 O 表示的任何分析中,各种简化都是可能发生的。低阶项一般可以被忽略,而常数也可以弃掉。
2. 总能够通过计算极限 来确定两个函数的相对增长率时,必要的话可以使用洛必达法则:
- 极限是 0:即 。
- 极限是 :即 。
- 极限是 :即 。
- 极限摆动:二者无关。
2.2 模型
模型机是一台标准的计算机,指令被顺序地执行。
做任何简单的工作都恰好话费一个时间单元。
有固定范围的整数并且不存在诸如矩阵求逆或排序等运算。
且具有无限的内存。
2.3 要分析的问题
考虑输入规模对时间的影响。当输入为 N 时,用 表示算法所花费的平均运行时间,用 表示算法在最坏情况下的运行时间。
一般来说,若无相反的指定,则所需的量是最坏情况下的运行时间。
最大的子序列和问题:
给定整数 , , , (可能有负数),求 的最大值(为方便起见,如果所有整数均为负数,则最大子序列和为 0)。
这个问题所以有吸引力,主要是因为存在求解它的很多算法,而这些算法的性能又差异很大。
因此只要可能,使得算法足够有效而不致成为问题的瓶颈是非常重要的。
2.4 运行时间计算
2.4.1 一个简单例子
int Sum(int N)
{
int i, PartialSum;
/* Time : */
PartialSum = 0; /* 1 */
for (i = 1; i <= N; i++) /* 2N + 2 */
PartialSum += i * i * i; /* 4N */
return PartialSum; /* 1 */
/* Total: 6N + 4 */
}
因此,我们说该函数是 。
2.4.2 一般法则
法则 1 —— FOR 循环:一次 for 循环的运行时间至多是该 for 循环内语句 (包括测试)的运行时间乘以迭代的次数。
法则 2 —— 嵌套 FOR 循环:从里向外分析这些循环。在一组嵌套循环内部的一条语句总的运行时间为该语句的运行时间乘以该组所有的 for 循环的大小的乘积。
法则 3 —— 顺序语句:将各个语句的运行时间求和即可。
法则 4 —— IF/ELSE 语句:一个 if/else 语句的运行时间从不超过判断再加上各个 分支中运行时间长者的总的运行时间。
递归情况的分析。
2.4.3 最大子序列和问题的解
算法 1:
int MaxSubsequenceSum(const int A[], int N)
{
int ThisSum, MaxSum, i, j, k;
MaxSum = 0;
for (i = 0; i < N; i++) {
for (j = i; j < N; j++) {
ThisSum = 0;
for (k = i; k <= j; k++)
ThisSum += A[k];
if (ThisSum > MaxSum)
MaxSum = ThisSum;
}
}
return MaxSum;
}
算法 2:
int MaxSubsequenceSum(const int A[], int N)
{
int ThisSum, MaxSum, i, j;
MaxSum = 0;
for (i = 0; i < N; i++) {
ThisSum = 0;
for (j = i; j < N; j++) {
ThisSum += A[j];
if (ThisSum > MaxSum)
MaxSum = ThisSum;
}
}
return MaxSum;
}
算法 3:
算法 4: