算法导论第2章习题解析
来源:互联网 发布:see electrical软件 编辑:程序博客网 时间:2024/06/05 08:16
2.1 插入排序
2.1-1: 略.
2.1-2 :将伪代码第5行 while i > 0 and A[i] > key 修改为 while i > 0 and A[i] < key 即可.
2.1-3 :代码如下所示:
int Linear_Find(int a[],int length,int key){ for(int i = 0;i < length;i++){ if(a[i] == key) return i; } return -1;}
很显然,这里的循环不变式为:
初始化:首先证明在第一次迭代之前(当 i = 0 时),循环不变式成立。当 i=0 时,
保持:在循环体中,如果
终止:导致
综上,算法正确。
2.1-4:代码如下所示:
void Binary_Add(int a[],int b[],int c[],int length){ auto r = 0; auto q = 0; for(auto i = length-1;i >=0;i--){ c[i+1] = (a[i]+b[i]+q)%2; q = (a[i]+b[i]+q)/2; } c[0] = q;}
这里需要的注意的一点是,用于保存结果的数组c的元素个数要比a,b中的元素个数多一个,才能容纳因为进位产生的二进制数。
2.2 分析算法
2.2-1:
2.2-2: 代码如下所示:
void Selection_Sort(int a[],int length){ for(auto j = 0;j < length - 1;j++){ auto k = j; for(auto i = j+1;i< length;i++){ if(a[k]>a[i]){ k = i; } } auto temp = a[j]; a[j] = a[k]; a[k] = temp; }}
这里,其循环不变式为a[0..j],每次迭代之前,a[0..j]都已是按从小到大排好了序的。当j = length - 2,即当j已迭代至倒数第二个元素时,经过循环体后,a[length - 1] >= a[length -2],因此只需要对前n-1个元素运行即可。最好情况(已按从小到大排好了序)是
2.2-3: 假定要查找的元素等可能的为数组中的任意元素,假设元素个数为 n,则每一个元素在位置 i 出现的概率的概率为 1 / n ,E = 1*(1 / n) + 2*(1 / n) + ……+ n*(1 / n) = (n+1) / 2。最坏情况下需要查询 n 次。所以在平均情况和最坏情况下运行时间都是
2.2-4:略。
2.3 设计算法
2.3-1 :略。
2.3-2:代码如下所示:
void Merge(int a[],int p,int q,int r){ int n1 = q - p + 1; int n2 = r - q; int* L = (int*)malloc((n1)*sizeof(int)); int* R = (int*)malloc((n2)*sizeof(int)); 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]; } int m = 0; int n = 0; while(m < n1 && n < n2){ if(L[m] < R[n]){ a[p++] = L[m++]; } else{ a[p++] = R[n++]; } } if(m == n1){ while(n < n2){ a[p++] = R[n++]; } } if(n == n2){ while(m < n1){ a[p++] = L[m++]; } }}
2.3-3: 略。
2.3-4:代码如下所示:
#include<stdio.h>void Insert(int a[],int p){ int key = a[p]; int i = p - 1; while(i>=0 && a[i] > key){ a[i+1] = a[i]; i--; } a[i+1] = key;}//插入排序的递归版本//T(n) = c(n = 1)//T(n) = T(n-1) + c*n (n >=1)void InsertSort_Cursor(int a[],int p){ if(p >= 1){ InsertSort_Cursor(a,p-1); Insert(a,p); }}//打印数组函数void PrintArray(int a[],int length){ for(auto i =0; i < length;i++){ printf("%d ",a[i]); } printf("\n");}int main(){ int a[] = {31,41,59,26,41,58}; printf("Before Sorting....\n"); PrintArray(a,6); InsertSort_Cursor(a,5); printf("After Sorting....\n"); PrintArray(a,6); getchar(); return 0;}
2.3-5:代码如下所示:
#include<stdio.h>int BinarySearch(int key,int a[],int low,int high){ if(low > high) return -1; int mid = low + (high - low) / 2; if(key < a[mid]) return BinarySearch(key,a,low,mid - 1); else if(key > a[mid]) return BinarySearch(key,a,mid+1,high); else return mid; }int main(){ int a[] = {1,2,3,4,5,6,7,8,9}; printf("%d\n",BinarySearch(9,a,0,8)); getchar(); return 0;}
根据递归式:T(n) = T(n / 2) + c (n > 1);T(n) = c (n = 1) 可证明耗时为:(lgn + 1)*c,可表示为
2.3-6:代码如下所示:
#include<stdio.h>int BinarySearch(int key,int a[],int low,int high){ if(low > high) return low; int mid = low + (high - low) / 2; if(key < a[mid]) return BinarySearch(key,a,low,mid - 1); else if(key > a[mid]) return BinarySearch(key,a,mid+1,high); else return mid; }void Insertion_Sort_BS(int a[],int length){ for(int j = 1; j < length; j++){ int key = a[j]; int i = j - 1; int low = BinarySearch(key,a,0,i); if(a[low] < key){ low++; } while(i >= low){ a[i+1] = a[i]; i--; } a[i+1] = key; }}void PrintArray(int a[],int length){ for(auto i =0; i < length;i++){ printf("%d ",a[i]); } printf("\n");}int main(){ int a[] = {8, 7, 6, 5, 4, 3, 2, 1}; Insertion_Sort_BS(a,8); PrintArray(a,8); getchar(); return 0;}
其伪代码如下所示:
代价 次数for j = 2 to A.length c1 n key = A[j] c2 n-1 i = j - 1; c3 n-1 low = Binary(key,a,0,i) c4*lg(n-1) n-1 if(a[low] < key) c5 n-1 low ++ c6 n-1 while(i >= low) c7 (i 从 1 到 n-1 的求和式) a[i+1] = a[i] c8 (i 从 1 到 n-2 的求和式) i-- c9 (i 从 1 到 n-2 的求和式) a[i+1] = key c10 n-1
从上面的伪代码分析,不难看出在while循环中会产生n的2次方项,那么其运行时间必然是
2.3-7:该代码可描述如下:
(1) 先用合并排序算法对 S 中的元素按从小到大的顺序排序,其产生的运行时间为
(2) 迭代 S 中的元素e,在循环体中使用二分查找算法查找元素 x-e,运行时间也为
因为(1)和(2)是顺序运行的,所以总的运行时间还是为
思考题:
2-1: 直接上代码:
#include<stdio.h>#include<limits.h>#include<stdlib.h>#define K 4void Insertion_Sort(int a[],int low,int high){ for(auto j = low;j<high;j++){ auto i = j+1; auto key = a[i]; while(i > low && a[i-1] > key){ a[i] = a[i - 1]; i--; } a[i] = key; }}void Merge(int a[],int p,int q,int r){ int n1 = q - p + 1; int n2 = r - q; int* L = (int*)malloc((n1+1)*sizeof(int)); int* R = (int*)malloc((n2+1)*sizeof(int)); 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] = INT_MAX; R[n2] = INT_MAX; int m = 0; int n = 0; for(int h = p; h <= r; h++){ if(L[m] < R[n]){ a[h] = L[m]; m++; } else{ a[h] = R[n]; n++; } }}void Merge_Sort(int a[],int p,int r){ int q = (p + r) / 2; if(q - p + 1 <= K){ Insertion_Sort(a, p, q); } else{ Merge_Sort1(a, p, q); } if(r - q <= K){ Insertion_Sort(a, q+1, r); } else{ Merge_Sort1(a, q+1,r); } Merge(a,p,q,r);}void PrintArray(int a[],int length){ for(auto i =0; i < length;i++){ printf("%d ",a[i]); } printf("\n");}int main(){ int a[] = {1,2,3,13,12,23,45,67,7,8,9,10,43,4,24,56,78,95,100,23,45,67,78,89,32,43,54,65,87,98,123,234,17,14,16,18,19,20,43,36,37,78,90,21,31,41,54,65,76,81,91,21,25,26,27,28,29,31,43,54,65,21,32,45}; Merge_Sort(a,0,63); PrintArray(a,64); getchar(); return 0;}
a. 证明: 插入排序最坏情况可以在
我们知道插入排序的耗时为
b. 表明在最坏情况下如何在
我们先写出上述代码耗时的递归式:
上述递归式中,当
如果我们画出其递归树的话,那么递归树的最底层的每个叶子节点为
那么怎么去找这个层数?我的方法是举一个实际的例子。假如
c. 假定修改后的算法的最坏运行时间为
首先,由上述
明白了这个式子的由来,我们再来分析问题。我们知道标准的合并排序的耗时为
从上面的分析可知,k 的最大值为
d. 在实践中,我们该如何选择 k?
参见习题 1.2-2
2-2. 冒泡排序的正确性
我不喜欢伪代码,所以老规矩先上完整代码:
#include<stdio.h>void PrintArray(int a[],int length){ for(auto i =0; i < length;i++){ printf("%d ",a[i]); } printf("\n");}void Bubble_Sort(int a[], int length){ for(int i = 0; i < length - 1; i++){ for(int j = length; j > i; j--){ if(a[j-1] > a[j]){ int temp = a[j]; a[j] = a[j-1]; a[j - 1] = temp; } } }}int main(){ int a[] = {31,41,59,26,41,58}; printf("Before Sorting....\n"); PrintArray(a,6); Bubble_Sort(a,6); printf("After Sorting....\n"); PrintArray(a,6); getchar(); return 0;}
a. 我们还需要证明 A’ 的元素是由 A 中的元素组成。
b. 第 2 到 4 行的 for 循环的循环不变式为 A[j..n].
在每次迭代之前,A[j..n] 中的元素就是上一次迭代产生的A[j..n]中的元素,但A[j]却是A[j..n]中最小的元素。
初始化:在第一次迭代之前 j = n,A[j..n]中只有一个元素A[n]组成,实际上就是A[n]中原来的元素,而且A[j]是最小的元素。这表明第一次循环迭代之前循环不变式成立。
保持:在循环体中,比较A[j]和A[j - 1]的大小,使A[j - 1]成为较小的那个元素,同时子数组长度增加1,其第一个元素是子数组中最小的一个。
终止:当 j = i + 1 时,比较A[i] 和 A[i+1]的大小,使A[j - 1]成为较小的那个元素,同时子数组长度增加1,其第一个元素是子数组中最小的一个,同时循环终止。
c. 1 到 4 行的 for 循环的循环不变式为:A[1..i - 1],每次迭代之前,A[1..i-1]已排好序,并且都小于A[i..n].
初始化:在第一次迭代之前,A[1..i-1]为空,自然满足循环不变式的要求。
保持: 从 b 部分,我们知道,内循环之后,A[i]是A[i..n]中的最小的数,而在外循环开始时,A[1..i-1]中的元素都比A[i..n]中的元素小,并且A[1..i-1]已排好了序。因此外循环之后,A[1..i]中的元素都比A[i+1..n]中的元素小,并且已排好了序。
终止:当 i = A.length 时,循环终止,A[1..n]中的元素已排好序。
d. 显然,冒泡排序的最坏情况运行时间为
2-3:a. 很显然霍纳规则求解多项式的算法运行时间是
b. 朴素的方式求解多项式代码如下:
int polynomial_value(int A[], int x, int n){ int y = A[0]; for(int i = 1; i <= n; i++){ int h = x; for(int j =1; j < i;j++){ h*= x; } y+=A[i]*h; } return y;}int main(){ int A[] = {1, 2, 3, 4}; int value = polynomial_value(A,1,3); return 1;}
上述普通的算法的具有嵌套for循环结构,因此,其算法的运行时间为
c. 略
d. 略
2- 4 :
a. (2,1),(8,1),(6,1),(8,6),(3,1)。
b. 倒序数组组成的逆序对最多,如果一个数组中有n个按照倒序从大到小的元素,那么其逆序对的个数为
c. 假如用 y 表示数组中的逆序对的个数,那么插入排序的运行时间
d. 在合并时,如果 L[m] > R[n] ,那么L数组中从 m 开始到结束(不包括哨兵元素)的元素都会大于 R 数组中从 m 开始到结束(不包括哨兵元素)的元素,根据这个特点我们就可以写出代码,代码如下所示:
#include<stdlib.h>#include<limits.h>#include<stdio.h>int cnt = 0;void Merge(int a[],int p,int q,int r){ int n1 = q - p + 1; int n2 = r - q; int* L = (int*)malloc((n1+1)*sizeof(int)); int* R = (int*)malloc((n2+1)*sizeof(int)); 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] = INT_MAX; R[n2] = INT_MAX; int m = 0; int n = 0; for(int k = p; k <= r; k++){ if(L[m] <= R[n]){ a[k] = L[m]; m++; } else{ a[k] = R[n]; cnt += q - p - m + 1; n++; } }}void Merge_Sort(int a[],int p,int r){ if(p < r){ int q = (p + r) / 2; Merge_Sort(a, p, q); Merge_Sort(a, q + 1, r); Merge(a, p, q, r); }}void PrintArray(int a[],int length){ for(auto i =0; i < length;i++){ printf("%d ",a[i]); } printf("\n");}int main(){ int a[] = {5, 2, 4, 7, 1, 3, 2, 6}; Merge_Sort(a,0,7); getchar(); return 0;}
- 算法导论第2章习题解析
- 算法导论第3章习题解析
- 算法导论第4章习题解析
- 算法导论 第7章 课后习题
- 算法导论第15章习题答案
- 算法导论课后习题解析 第六章
- 算法导论课后习题解析 第七章
- 算法导论课后习题解析 第三章
- 算法导论课后习题解析 第二章
- 算法导论习题解-第16章贪心算法
- 《算法导论(第2版)》第20章习题解答
- Introduction to Algorithms 算法导论 第2章 算法入门 学习笔记及习题解答
- 算法导论 第7章部分习题解答
- 算法导论 第15章 动态规划 习题C++实现
- 算法导论习题解-第17章摊还分析
- 算法导论习题解-第18章B树
- 算法导论习题解-第23章最小生成树
- 《算法导论》 - 第6章 - 堆排序 - 习题解答
- 可持久化Treap
- 数据结构之单链表
- 文章标题
- RabbitMQ如何引入项目?
- BZOJ 1637: [Usaco2007 Mar]Balanced Lineup
- 算法导论第2章习题解析
- PhysioBank简介
- 通用产品设计的巨大障碍--软件配置数据--的通用化处理方法
- Windows环境下HTK3.4.1的安装
- STL map
- 可持久化Treap
- 本地存储
- 【剑指offer】题60:分层遍历打印二叉树
- 【leetcode】第67题 Add Binary 题目+解析+JAVA代码