第二章-算法入门
总结:这一章讲了插入排序及其算法分析,循环不变式的证明,合并排序(分治法)及其算法分析。
1. 插入排序
类似于扑克牌的插入过程,设A[1...j-1]是排好序的一个数组,将A[j]插入A[1...j-1]中使A[j]称为排好序的一个数组,j <- 2 to length[A]
算法分析:最好情况,整个数组已经是顺序的了,O(n)
最坏情况,整个数组是逆序的,O(n^2)
平均情况,O(n^2)
是一种稳定排序
算法优化:将A[j]插入已经排好序的A[1...j-1]时,可以用二分查找法,这可以改变查找合适位置的效率,但是仍需要将数组的元素一个一个移动。
伪代码
INSERTION-SORT(A) for j<-2 to length[A] do key <- A[j] i <- j-1 while i>0 and A[i]>key do A[i+1] <- A[i] i <- i-1 A[i+1] <- key
C++代码
#include <iostream> using namespace std; void insertSort(int a[],int length) //°´´ÓСµ½´óÅÅÐò { for (int j=1;j<length;j++) { int 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 a[7]={3,4,2,1,8,0,9}; int length=sizeof(a)/sizeof(int); insertSort(a,length); for (int i=0;i<length;i++) { cout << a[i] << " "; } cout << endl; return 0; }
2. 循环不变式
证明三个性质:初始化、保持、终止
3. 合并排序
分治法:将原问题分成n个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
算法分析:时间复杂度O(nlgn)
空间复杂度O(n)
稳定排序
C++代码
void merge(int a[],int p,int q, int r) //合并已经排序的a[p...q-1]和a[q...r-1],从小到大排序 { int n1=q-p; int n2=r-q; int *left=new int[n1]; int *right=new int[n2]; int i; for(i=0;i<n1;i++) left[i]=a[p+i]; for(i=0;i<n2;i++) right[i]=a[q+i]; i=0; int j=0; int k=p; while(i<n1 && j<n2) { if(left[i]<right[j]) a[k++]=left[i++]; else a[k++]=right[j++]; } if(j<n2) { while(j<n2) a[k++]=right[j++]; } if(i<n1) { while(i<n1) a[k++]=left[i++]; } } void mergeSort(int a[],int begin,int end) //从a[begin]到a[end-1]合并排序 { if(begin>=end-1) return; int r=(begin+end)/2; mergeSort(a,begin,r); mergeSort(a,r,end); merge(a,begin,r,end); } int main() { int a[7]={3,4,2,1,8,0,9}; int length=sizeof(a)/sizeof(int); // insertSort(a,length); mergeSort(a,0,length); for (int i=0;i<length;i++) { cout << a[i] << " "; } cout << endl; while(1); return 0; }
【练习题】
1. 选择排序
从A中找到最小的元素,与A[0]互换,再从A中找出次最小元素,与A[1]互换,以此类推..
算法分析:时间复杂度 O(n^2)
稳定排序
伪代码:
SELECTION-SORT(A) for i <- 1 to length[A]-1 do smallest <- i for j <- i+1 to length[A] do if A[j]<A[smallest] then smallest <- j exchange A[i] <-> A[smallest]
问题:为什么i的循环只要到length[A]-1,因为到时前n-1个一定是最小的n-1个,那么第n个必定是最大的那个元素,所以最后一次循环可以省去了。
C++代码:
void selectionSort(int a[],int length) { for(int i=0;i<length-1;i++) { int smallest=i; for(int j=i+1;j<length;j++) if(a[smallest]>a[j]) smallest=j; int temp=a[i]; a[i]=a[smallest]; a[smallest]=temp; } } int main() { int a[7]={3,4,2,1,8,0,9}; int length=sizeof(a)/sizeof(int); selectionSort(a,length); for (int i=0;i<length;i++) { cout << a[i] << " "; } cout << endl; while(1); return 0; }
2. 二分查找(时间复杂度O(lgn))
1) 非递归(迭代)
//二分查找(迭代) int iterativeBinarySearch(int a[],int v, int low, int high) { while(low <= high) { int mid=(low+high)/2; if(a[mid]==v) return mid; else if (v>a[mid]) low=mid+1; else high=mid-1; } return NULL; }
2) 递归
int recursiveBinarySearch(int a[],int v, int low, int high) { if(low > high) return -1; int mid=(low+high)/2; if(v==a[mid]) return mid; else if(v>a[mid]) return recursiveBinarySearch(a,v,mid+1,high); else return recursiveBinarySearch(a,v,low, mid-1); }
3. 请给出一个运行时间为O(nlgn)的算法,使之能在给定一个由n个整数构成的集合S和另一个整数x时,判断出S中是否存在两个其和等于x的元素。
算法思路:
思路一:对S排序(O(nlgn)),去掉S中重复的元素(O(n)),对S中的每个元素y,计算z=x-y,将所有的z汇集成S’(O(n)),对S’排序(O(nlgn)),合并S,S’。若合并的集合中存在两个连续相等的元素,则说明S中存在两个其和等于x的元素。
void removeTheSame(int a[],int &length) { for(int i=1;i<length;i++) { if(a[i]==a[i-1]) { int j=i; while(j<length) { a[j]=a[j+1]; j++; } length--; } } } bool findSumX1(int a[],int x,int length) { mergeSort(a,0,length); removeTheSame(a,length); int *b=new int[length]; for(int i=0;i<length;i++) b[i]=x-a[i]; mergeSort(b,0,length); int len=length+length; int *c=new int[len]; for(int i=0;i< length;i++) c[i]=a[i]; for(int i=length;i<len;i++) c[i]=b[i-length]; merge(c,0,length,len); for(int i=1;i<len;i++) { if(c[i]==c[i-1]) return true; } return false; }
思路二:对S排序(O(nlgn)),对S中的元素y,计算z=x-y,从S中二分查找z(O(lgn)),找到则说明存在。最多循环n次,复杂度O(nlgn)
bool findSumX2(int a[],int x,int length) { mergeSort(a,0,length); removeTheSame(a,length); for(int i=0;i<length;i++) { int z=x-a[i]; if(recursiveBinarySearch(a,z,0,length)!=-1) return true; } return false; }
【思考题】
1. 用O(nlgn)的复杂度求给定n个元素的任何排列中逆序对的数目(提示:修改合并排序)
int merge2(int a[],int p,int q,int r) //a[p...q-1]和a[q...r-1] { int n1=q-p; int n2=r-q; int *left=new int[n1]; int *right=new int[n2]; int i; for(i=0;i<n1;i++) left[i]=a[p+i]; for(i=0;i<n2;i++) right[i]=a[q+i]; i=0; int j=0,k=p; int cnt=0; while(i<n1 && j<n2) { if(right[j]<left[i]) { a[k++]=right[j++]; cnt += n1-i; } else a[k++]=left[i++]; } if(i<n1) while(i<n1) a[k++]=left[i++]; if(j<n2) while(j<n2) a[k++]=right[j++]; return cnt; } int findInversion(int a[],int low, int high) { if(low>=high-1) return 0; int r=(low+high)/2; int count=0; count+=findInversion(a,low,r); count+=findInversion(a,r,high); count+=merge2(a,low,r,high); return count; }