📘 分析算法
介绍
分析算法的结果主要用于预测算法需要的资源,例如内存、通信带宽、计算机硬件,但是通常最需要度量的是计算时间。为了分析算法的运行时间,需要有一个实现技术的模型,用于描述所用资源及其代价的模型。这里假定通用的单处理器计算模型RAM(random-access machine)作为实现技术,在RAM模型中,指令一条一条地执行,没有并发操作。
说明
通用的单处理器计算模型RAM在这里不指代计算机中的高速缓存或虚拟内存等,这些现代存储技术可能对于真实计算机上运行的真实程序影响重大。
RAM模型包含计算机常见的指令:算术指令(+
、-
、*
、/
、%
等)、数据移动指令(存储、复制、装入等)、控制指令(条件、子程序调用与返回)等,每条这样的指令所需的时间都为常量。RAM模型中存在一些灰色区域(包含了一些未列出的指令),例如计算
采用RAM模型分析算法的运行时间,需要的数学工具可能包括组合学、概率论、代数技巧以及识别公式中最有意义的项。
说明
识别公式中最有意义的项,个人理解为识别公式中占主导地位的项,例如当
插入排序算法的分析
一般来说,算法需要的运行时间
说明
输入规模视情况而定,例如排序或计算离散傅里叶变换,那么量度是输入的项数
在程序运行中,运行每一行代码需要常量时间(这里包括子程序调用,只是子程序调用需要更大的常量时间而已),注释是不可执行的语句,因此不需要花费时间。对插入排序进行运行总时间统计:
function insertionSort (originalArray, customComparator) {
// 1
const array = [...originalArray]
// 2
let currentElement, preIndex
// 3
for (let i = 1; i < array.length; i++) {
// 4
preIndex = i - 1
// 当前需要排序的元素(第一个元素默认已经排好序)
// 5
currentElement = array[i]
// 判断前一个元素是否大于当前元素,如果是则前一个元素往后移
// 直到不存在前一个元素或者前一个元素小于当前元素,则停止移动
// 6
while (preIndex >= 0 && array[preIndex] > currentElement) {
// 7
array[preIndex + 1] = array[preIndex--]
}
// 将当前排序元素插入到移动后的位置
// 8
array[preIndex + 1] = currentElement
}
// 9
return array
}
以上标注的数字代表程序运行指令对应的行(需要注意去除了自定义比较功能),假定第
指令序号 | 运行代价 | 运行次数 |
---|---|---|
1 | 1 | |
2 | 1 | |
3 | n | |
4 | n-1 | |
5 | n-1 | |
6 | ||
7 | ||
8 | n-1 | |
9 | 1 |
说明
注意循环头的执行次数比循环体多一次,用于判断循环退出条件。图中的while
循环所需要执行的次数,例如考虑最佳情况,待插入的元素在循环不变式中是最大的,那么while
循环只需要执行一次(循环一次都不满足),如果考虑最坏情况,待插入的元素在循环不变式中是最小的,那么运行while
循环需要执行
插入排序算法所需要的总代价如下:
考虑最佳情况,当
可以把上述公式表示为:
考虑最坏情况,当
可以把上述公式表示为:
最坏情况分析
在上面的分析中,我们考虑了最佳情况和最坏情况,但是在实际中我们往往更应该考虑最坏情况:
- 最坏情况给出了输入规模对应的运行时间上界,可以期望它不会变的更差。
- 最坏情况经常出现。
- 平均情况往往与最坏情况一样差,例如考虑上述插入排序,当
时, 仍然是 的二次函数。
增长量级
对于
小结
主要分析了插入排序的运行时间。分析运行时间引入了记号