题解参考网站:https://walkccc.github.io/CLRS/
第一部分 基础知识
第1章 算法在计算中的作用
- 算法简单来说就是能够正确将某一输入转换成特定输出的一系列计算步骤。
排序问题形式定义:
输入: n个数的一个序列<a1, a2, …, an>。
输出: 输入序列的一个排列<a1’, a2’, …, an’>,满足a1’ <= a2’ <= … <= an’。
影响因素: 项数n、预先排序的程度、项值的限制、计算机的体系结构、存储设备的种类(主存、磁盘或者磁带)
第2章 算法基础
插入排序
问题类型如上面引用的排序问题的形式定义,将待排序的数称为关键词。
- 排序算法基本思想: 从待排序数组中拿出一个数(关键词),将该关键词跟已排序数组中的数进行比较,将其插入适当的位置。
伪代码
//按非降序排序
INSERTION-SORT(A)
for j = 2 to A.length
key = A[j]
//Insert A[j] into the sorted sequence A[1..j-1]
i = j-1
while i>0 and A[i]>key
//后移
A[i+1] = A[i]
i = i - 1
//the key 入位
A[i+1] = key
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
void InsertionSort(vector<int> &A)
{
int n = A.size();
int key;
for(int j=1; j<n; ++j)
{
key = A[j];
int i = j-1;
while(i>=0 && A[i]>key)
{
A[i+1] = A[i];
--i;
}
A[i+1] = key;
}
}
int main()
{
int n;
cout << "Input the size of A:\n";
cin >> n;
vector<int> A(n);
cout << "Input the n numbers of A:\n";
for(int i=0; i<n; ++i)
cin >> A[i];
InsertionSort(A);
cout << "After InsertionSort, the sequence of A:\n";
for(int i=0; i<n; ++i)
cout << A[i] << " ";
return 0;
}
运行结果
插入排序分析——Θ(n2)
循环不变式与插入排序的正确性
循环不变式的证明
- 初始化: 循环的第一次迭代前,循环不变式为真。
- 保持: 如果循环的某次迭代前循环不变式为真,那么下次迭代前循环不变式仍为真。
- 终止: 在循环终止时,不变式为我们提供一个有助于证明算法正确性的性质。
- 插入排序的证明:
初始化: 证明第一次迭代(j=2)前,循环不变式成立。此时已排序数组A[1…j-1]仅由A[1]组成,该数组当然满足已排序的性质,即第一次循环迭代前循环不变式成立。
保持: 证明每次迭代保持循环不变式。在第j次循环迭代前,A[1…j-1]为已排序序列,此为真,当第j次循环迭代后(第j+1次循环迭代前)A[j]已经通过while语句找到了在A[1…j-1]中的恰当位置,并通过后移插入到合适位置,即A[1…j]为已排序序列,循环不变式成立。
终止: for循环终止条件是 j>A.length 即 j = n+1, 由循环不变式得,第n+1次循环迭代前,A[1…n]为已排序序列,即整个数组已被排序,插入排序算法正确。
2.1-4 考虑把两个n位二进制整数加起来的问题,A+B=C, C为n+1元数组。
//伪代码实现
ADD_BINARY(A, B)
C = new int[A.length + 1]
carry = 0
for i = 1 to A.length
C[i] = (A[i] + B[i] + carry) % 2
carry = (A[i] + B[i] + carry) / 2
C[i] = carry
return C
分析算法
RAM模型 —— 包含计算机中的常见指令:算术指令(如加法、减法、乘法、除法、取余、向下取整、向上取整)、数据移动指令(装入、存储、复制)和控制指令(条件与无条件转换、子程序调用与返回。每个这种指令所需时间为常量。
一般考虑最坏时间复杂度Θ记号。
设计算法
- 插入排序算法使用了增量方法:在排序子数组A[1…j-1]后,将单个元素A[j]插入子数组的适当位置,产生排序号的子数组A[1…j]
分治方法
分治法的思想:将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。
分治模式再每层递归的三个步骤:
- 分解原问题为若干子问题,为原问题规模较小的实例。
- 解决这些子问题,递归地求解各个子问题。
- 合并这些子问题的解成原问题的解。
归并排序
伪代码
//合并操作,p<=q<r, A[p..q]与A[q+1..r]均已排序
MERGE(A, p, q, r)
n1 = q - p + 1
n2 = r - q
let L[1..n1+1] and R[1..n2+1] be new arrays
for i = 1 to n1
L[i] = A[p+i-1]
for j = 1 to n2
R[j] = A[q+j]
//哨兵
L[n1+1] = ∞
R[n2+1] = ∞
i = 1
j = 1
for k = p to r
if L[i] <= R[j]
A[k] = L[i]
i = i + 1
else
A[k] = R[j]
j = j + 1
MERGE-SORT(A, p, r)
//若p >= r 表示子数组中最多有一个元素,即已经排序好
if p < r
q = floor( (p+r) / 2 )//向下取整
MERGE-SORT(A, p, q)
MERGE-SORT(A, q+1, r)
MERGE(A, p, q, r)
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
const int MAX = 1e6;
void Merge(vector<int> &A, int p, int q, int r)
{
int n1 = q-p+1, n2 = r-q;
vector<int> L(n1+1);
vector<int> R(n2+1);
for(int i=0; i<n1; ++i)
L[i] = A[p+i];
for(int j=0; j<n2; ++j)
R[j] = A[q+j+1];
L[n1] = R[n2] = MAX;
int i = 0, j = 0;
for(int k=p; k<=r; ++k)
{
if(L[i] <= R[j])
A[k] = L[i++]<