1. 算法的概念和作用 (Whats and Whys)
Informally, an algorithm is any well-defined computational procedure that takes some value, or set of values, as input and produces some value, or set of values, as output. An algorithm is thus a sequence of computational steps that transform the input into the output. [CLRS,3rd Edition]
算法就是定义好的计算步骤。通过这些计算步骤,我们可以将输入转化为某种形式的输出。
算法是当代计算机中用到的大部分技术的核心。
为什么要学习和研究算法:
(1) 即使是在计算机无限快,存储无限大的情况下,我们需要有某种手段来确定我们的解决方案的正确性。
(2) 现实中往往存在资源(计算速度和存储空间)的限制,我们必须有效地利用资源,这就需要我们找到高效的算法。
(3) 高效的算法能够用来实现高性能计算系统,能够带来好的用户体验。
(注:第3点是我自己加的,其实可以归入第2点。之所以单独列出来,是因为在应用软件开发中常常会出现对响应时间要求比较苛刻的情况,处理不好就会严重影响用户体验,高效算法在此时显得尤为重要。)
2. 循环不变式 (Loop Variant)
循环不变式可以帮助我们理解算法的正确性。循环不变式有以下三个性质:
(1) Initialization: 循环不变式在循环的第一轮迭代之前成立;
(2) Maintenance: 如果循环不变式在某一轮循环之前成立,那么要能够保证它在下一轮迭代之前也成立;
(3) Termination: 在循环结束后,循环不变式能够提供一个对表明算法的正确性的非常有用的性质。
说明循环不变式具有以上三个性质的过程也就是算法的正确性证明过程。这个过程和数学归纳法(mathematical induction)很相似。(1) 和 (2) 分别对应数学归纳法的 Base Case (基本情形) 和 Induction Step (归纳步骤)。
这里附上一段《算法导论》第三版中描述循环不变式的原文:
We use loop invariants to help us understand why an algorithm is correct. We must show three things about a loop invariant:
Initialization: It is true prior to the first iteration of the loop.
Maintenance: If it is true before an iteration of the loop, it remains true before the next iteration.
Termination: When the loop terminates, the invariant gives us a useful property that helps show that the algorithm is correct.
具体的例子可以帮助我们理解循环不变式,循环不变式的应用实例见下文。
3. 插入排序 (Insersion Sort)
1 void insertion_sort(int arr[], int n) { 2 int i, j; 3 for (i = 1; i < n; i++) { 4 int key = arr[i]; 5 for (j = i-1; j >= 0; j--) { 6 if (key < arr[j]) { 7 arr[j+1] = arr[j]; 8 } else break; 9 } 10 arr[j+1] = key; 11 } 12 }
最外层循环(i循环)的循环不变式:
arr[0 .. i-1] 是最初(算法未开始之前)的 arr[0 .. i-1] 的有序序列。
算法正确性形式化证明:
(1) 在循环的第 1 轮之前, i = 1, arr[0 .. i-1] = arr[0], 显然循环不变式成立。
(2) 设在第 k (k >= 1) 轮循环开始之前,循环不变式成立。即 arr[0 .. k-1] 为最初 arr[0 .. k-1] 的有序序列,我们要找到 arr[k] 的位置, 将在 arr[0 .. k-1] 中比 arr[k] 大的都后移一个位置,在不大于 arr[k] 的第一个元素后面插入 arr[k], 此轮循环结束后, arr[0 .. k] 为有序序列, 保证了循环不变式在下一轮循环开始之前成立。
(3) 在循环结束后,i = n, 由循环不变式 arr[0 .. i-1] = arr[0 .. n-1]有序,即完成了排序。
(1)-Initialization, (2)-Maintenance, (3)-Termination
上述插入排序是稳定的,即相同的元素在排序后,先后顺序保持不变。
算法时间复杂度分析:
(a) 最好情况(数组中元素已排好序的情况)算法复杂度: O(n); (可以说没什么参考价值)
(b) 最坏情况(数组中元素逆序的情况)算法复杂度: O(n^2);
(c) 平均情况算法复杂度: O(n^2).
算法的空间复杂度为 O(1),为原地排序(The algorithm sorts the input numbers in place)。
4. 线性搜索 (Linear Search)
1 int linear_search(int arr[], int n, int v) { 2 int i; 3 for (i = 0; i < n; i++) { 4 if (arr[i] == v) { 5 return i; 6 } 7 } 8 return -1; // v does not appear in arr[] 9 }
循环不变式:
对任意 j (0 <= j < i), arr[j] != v 成立。
(1) 在第1轮迭代之前, i = 0, 不存在 j 满足, 0 <= j < i, 不变式成立;
(2) 设在某一轮迭代之前循环不变式成立,在这次循环时,如果arr[i] == v成立,结束算法,否则在下一轮迭代之前循环不变式成立;
(3) 循环结束即算法结束时,要么(a) i < n && arr[i] == v 成立,要么(b)循环不变式成立, 对任意 j (0 =< j < n), arr[j] != v, 即在数组中不存在元素 v, 返回 -1。