《算法导论》学习笔记(一)

  今日开始学习《算法导论》这本巨著,记录下一些心得。

  以下是第二章:算法基础的内容。

  如果从理论上分析一个算法的正确性,其实是一件不容易的事,因为你无法通过穷举所以情况来验证正确性,所以就需要一套理论可以通过合理的逻辑以及使用递推和归纳的手段来验证其正确性。这里有点像高中数学中数列的证明题一样,递推和数学归纳是一种重要的数学(骗分)手段。

2.1插入排序

先实现一个经典的排序算法,插入排序:

1void insert_sort(int a[],int len){ 2 int i,j; 3 int key; 4 for(i = 1;i<len;i++){ 5 key = a[i]; 6 /*insert A[i] into the sequence A[1,...i-1]*/ 7 j=i-1; 8 while(j >= 0 && a[j] > key){ 9 a[j+1] = a[j]; 10 j = j-1; 11 } 12 a[j+1] = key; 13 } 14} 15

书中举了很生动的例子,就像我们平时打扑克一样,右手从牌堆里拿牌并按照一定顺序(规则里的大小)来插入左手的牌里,其实这个过程我们暗中对左右的牌与右手拿起的牌进行了比较,人的眼睛算是并行处理,然而原理是一样的,必须要观察完一部分牌才能知道右手拿起的牌的位置。

循环不变式:

书中如此形容循环不变式:

排序数组A[n],包含元素A[0,....j-1]的子数组构成了当前排序好的左手中的牌,子数组A[j,....n-1]包含了未排序的牌。事实上,元素A[0,...j-1]就是原来在位置1到j-1的元素,但现在已按序排列。

性质:

初始化:循环的第一次迭代之前,它为真

保持:如果循环的某次迭代之前它为真,下次迭代之前它仍为真

终止:在循环终止时,不变式为我们提供了一个有用的性质

上述的过程很明显就是数学归纳法了,不同的是数归的迭代是无终止以至无穷而算法问题一般是需要终止的,否则没有意义。方法便可以通过逻辑推理去证明最终终止时不变式的性质。

2.2分析算法

分析算法无非就是分析算法的时间效率和空间效率。时间效率上,为了使算法独立于机器的配置,我们采取运算步数来体现时间效率,因此分析算法也就是分析算法的步数,这也是数列的问题了(可能还会涉及极限和收敛的问题)。一般我们会关注最差和平均的情况(输入的不同决定)。

2.3设计算法

书中用了归并排序来做例子:

1void merge_sort(int A[],int p,int r){ 2 if(p < r){ 3 int q = (p+r)/2; 4 merge_sort(A,p,q); 5 merge_sort(A,q+1,r); 6 merge(A,p,q,r); 7 } 8} 9 10void merge(int A[],int p,int q,int r){ 11 int n1 = q-p+1; 12 int n2 = r-q; 13 int i,j; 14 int *L = new int[n1+1]; 15 for(i =0;i<n1;i++) 16 L[i] = A[p+i]; 17 int *R = new int[n2+1]; 18 for(j =0;j<n2;j++) 19 R[j] = A[q+j+1]; 20 L[n1] = 10000; 21 R[n2] = 10000; 22 i=0; 23 j=0; 24 for(int k = p;k<r+1;k++){ 25 if(L[i] <= R[j]){ 26 A[k] = L[i]; 27 i++; 28 } 29 else { 30 A[k] = R[j]; 31 j++; 32 } 33 } 34} 35
1分解、解决、合并一直是解决复杂的问题的步骤,也是高中时候解决难题的一种思维,难则反,易则正,其中蕴含了递归的思维。 2

 

代码交流 2021