1、 插入排序,时间复杂度O(n^2)
一个数组A[0,...,n-1],将A[j]插入到子数组A[0,...,j-1]中,其中数组A[0,...,j-1]是排好序的(假如为非降序),把A[j]和A[j-1]->A[0]逐个进行比较,大的数往后移位,将A[j]插入适当的位置(找到最后一个比它大的,放在前面)。
递归法,为排序A[0,...,n-1],先排序A[0,...,n-2],然后把A[n-1]插入到已经排序的数组A[0,...,n-2]中去。如下:
#include<stdio.h> #include<iostream.h> //算法导论2.3-4 //将数number插入已经排好序(非降序)的数组中 void insert(int num[],int len,int number){ int i=len-1; while(i>=0&&num[i]>number){ num[i+1]=num[i]; i--; } num[i+1]=number; } //递归程序 void insert_into(int num[],int n){ if(n>1){ insert_into(num,n-1); } insert(num,n,num[n]); } int main(){ int num1[8]={4,2,5,1,8,10,3,7}; int len=8; insert_into(num1,len-1); for(int k=0;k<len;k++){ cout<<num1[k]<<endl; } return 0; }
2、选择排序,时间复杂度O(n^2)
首先找出A中最小的元素,并将其与A[0]互换,接着找A中次小元素,与A[1]互换,以此类推。并且仅需要在n-1个元素上运行。伪代码如下:
for i<-1 to n-1 do//伪代码中下标从1开始
min<-Find_min(A[i,...,n])//此函数找出A中从下标i到n的最小元素,时间复杂度O(n)
min<->A[i]
endfor
根据以上伪代码可知,选择排序的时间复杂度为O(n^2)
最佳和最坏情况下的运行时间都为Θ(n²)。因为这种直接选择排序,其关键字的比较次数与各元素原来的排列顺序无关。
3、合并排序(递归法),时间复杂度O(n㏒2n)
分解:将n个元素分成各含n/2个元素的子序列
解决:用合并排序法对两个子序列进行递归排序
合并:合并两个已经排好序的子序列得到结果(merge函数)
#include<stdio.h> #include<iostream.h> #include <limits.h> //算法导论p17 void merge(int num[],int p,int q,int r){//假设num[p..q],num[q+1...r]已经排好序,将其合并成一个已经排好序的数组 int n1=q-p+1; int n2=r-q; int *left=new int[n1+1]; int *right=new int[n2+1]; for(int i=0;i<n1;i++){ left[i]=num[p+i]; } for(i=0;i<n2;i++){ right[i]=num[q+1+i]; } left[n1]=INT_MAX;//哨兵牌 right[n2]=INT_MAX; int j=0,k=0; for(i=p;i<=r;i++){ if(left[j]>right[k]){ num[i]=right[k]; k++; } else{ num[i]=left[j]; j++; } } for(i=p;i<=r;i++){ cout<<num[i]<<endl; } } void merge_sort(int num[],int p,int r){ int q=0; if(p<r){ q=(p+r)/2; merge_sort(num,p,q); merge_sort(num,q+1,r); merge(num,p,q,r); } } int main(){ int num[8]={2,4,5,7,1,2,3,6}; int p=0,r=7; int q=(p+r)/2; merge_sort(num,p,r); return 0; }
merge函数是从头开始分别比较两个子序列的数的大小,将较小的数字存入num中,相应队列的指针往后移。为了避免在每个步骤中都要检查每个序列是否为空,增加一个哨兵牌,包含一个特殊的值,来简化代码。此函数中,用正无穷来作为哨兵值,这样每当露出一张值为无穷的哨兵牌,其不可能是两张中最小的一张,除非另外一个序列也露出哨兵牌。
merge函数的时间复杂度为O(n),n=r-p+1
而合并排序,将问题分为1/2->1/4->1/8,总共分了㏒2n层,回推时每一层的代价是O(n)
4、合并排序的应用,求逆序对的个数,且算法的时间复杂度为O(n㏒2n)
逆序对:如果有i<j时,A[i]>A[j],则(i,j)叫做一个逆序对
合并排序法求逆序对的个数,当求出逆序对数目后,数组也已经排好序了,如果不想让原数组发生变化,可以把原数组拷贝到另一个数组,对新数组操作就好。整个过程一边排序一边数,递归到最深的地方如果有一个数,那么返回0,如果有两个数,看做有两个元素的子数组,两个子数组都已经排好序,并且左边只有一个元素的子数组的逆序对是0,右边只有一个元素的子数组的逆序对是0,那么两个元素的子数组的逆序对的个数就是1,若这两个数逆序,否则是0对。所以,当递归进行到最后一步,也就是这样的:a[1]到a[n/2]已经排好序,并且逆序对的个数为Inv1,a[n/2+1]到a[n]已经排好序,并且逆序对的个数为Inv2,那么整个数组的逆序对总数就是Inv1+Inv2+最后一步归并过程中的逆序对数。那么在merge这一步的逆序对个数又是怎么计算的呢?若i是左边子数组的下标(i的范围是p到q),j是右边子数组的下标(j的范围是q+1到r),假设某对(i, j),有a[i]>a[j],那么a[i]到a[q]都大于a[j],贡献的逆序对数为q-i+1。
#include<stdio.h> #include<iostream.h> #include <limits.h> //算法导论p24 2-4d int merge_num(int num[],int p,int q,int r){//假设num[p..q],num[q+1...r]已经排好序,将其合并成一个已经排好序的数组 int n1=q-p+1; int n2=r-q; int *left=new int[n1+1]; int *right=new int[n2+1]; int n=0;//n表示逆序对数 for(int i=0;i<n1;i++){ left[i]=num[p+i]; } for(i=0;i<n2;i++){ right[i]=num[q+1+i]; } left[n1]=INT_MAX;//32767 right[n2]=INT_MAX; int j=0,k=0; for(i=p;i<=r;i++){ if(left[j]>right[k]){ num[i]=right[k]; k++; n+=n1-j;//注意不可是q-j+1,因为q和j指的意义不同,一个是原来数组里的下标,一个是新数组里的下标 } else{ num[i]=left[j]; j++; } } return n; } int merge_sort(int num[],int p,int r){ int q=0; int total=0; if(p<r){ q=(p+r)/2; total+=merge_sort(num,p,q);//之前的左半部 total+=merge_sort(num,q+1,r);//之前的右半部分 total+=merge_num(num,p,q,r);//这次合并时加上total } return total; } int main(){ int num[8]={8,7,6,5,4,3,2,1}; int p=0,r=7; int q=(p+r)/2; //cout<<merge_num(num,p,q,r)<<endl; cout<<merge_sort(num,p,r)<<endl; for(int i=p;i<=r;i++){ cout<<num[i]<<endl; } return 0; }
此时,因为要记录每次递归的逆序对数,因此,递归函数merge_sort中total的写法需要注意。
5、折半查找(递归程序),时间复杂度O(㏒2n)
int binary_search(int num,int num_arr[],int start,int end){ int mid=0; if(start>=end&&num_arr[start]!=num){//注意递归终止条件 return 0; } mid=(start+end)/2; if(num==num_arr[mid]){ return mid; } else if(num<num_arr[mid]){ binary_search(num,num_arr,start,mid); } else{ binary_search(num,num_arr,mid,end); } }