8.1排序算法的下界
8.1-1 在一颗比较排序算法的决策树中,一个叶结点可能的最小深度是多少?
最少进行n-1次比较,所以深度最小是n-1
8.1-2不用斯特林近似公式,给出lg(n!)的渐近紧确界,利用A.2节介绍的技术来求累加和∑lgk.
∫(lgk)dk=klgk-∫kd(lgk)=klgk-(1/ln2)k 所以∑lgk=(nlgn-1lg1)-(1/ln2)(n-1)=nlgn-(1/ln2)(n-1)-cnlgn=(1-c)nlgn-(1/ln2)(n-1)
如果1-c>0,那么对于足够大的n来说nlgn增长速度比(n-1)快,所以(1-c)nlgn-(1/ln2)(n-1)≥0 所以lg(n!)=Ω(nlgn)
如果1-c<0,对于足够大的n显然有(1-c)nlgn-(1/ln2)(n-1)≤0。所以lg(n!)=Ο(nlgn) 所以lg(n!)=Θ(nlgn)
8.1-3证明:对于n!种长度为n的输入中至少一半,不存在能达到线性运行时间的比较排序算法。
如果只要求对1/n的输入达到线性时间呢?1/2^n呢?
假设x种输入达到h1=Θ(n).x<2^h1=>h1>lgx. 如果存在x>n!/2输入达到线性时间。那么就应该有 h1>lgx>lg(n!/2)>Ω(nlgn)与假设矛盾。
假设x种输入达到h1=Θ(n).x<2^h1=>h1>lgx.如果存在x>n!/n输入达到线性时间。那么就应该有 h1>lgx>lg(n!/n)>Ω(nlgn)与假设矛盾。
同理1/2^n的输入也与假设矛盾。所以以上这三种输入都不行。
8.1-4 假设现有一个包含n个元素的待排序序列。该序列由n/k个子序列组成,每个子序列包含k个元素一个给定子序列中的每个元素都小于其后继子序列中的所有元素,且大于其前驱子序列中的每个元素。因此,对于这个长度为n的序列的排序转化为对n/k个子序列中的k个元素的排序。试证明,这个排序问题中所需比较次数的下界Ω(nlgk).
因为每个子序列有k!种排列方式,那么n/k个子序列就有(k!)^(n/k)种排列方式,所以(k!)^(n/k)≤2^h h≥(n/k)lgk! 因为由(公式3.19)lgk!=Θ(klgk) lgk!≥klgk 所以h≥(n/k)klgk=nlgk.得证。
8.2计数排序
计数排序代码:
//计数排序 //附带8.2-4代码 /*#include <iostream> using namespace std; const n=8; void COUNTING_SORT(int A[n],int B[n],int k) { int *C=new int[k+1]; for (int i=0;i<=k;i++) { C[i]=0; } for (int j=0;j<n;j++) { C[A[j]]=C[A[j]]+1; } for (i=0;i<=k;i++) { C[i+1]=C[i+1]+C[i]; } for (j=n-1;j>=0;j--) { B[C[A[j]]-1]=A[j]; C[A[j]]=C[A[j]]-1; } } int counting_SORT(int A[n],int a,int b,int k) { int *C=new int[k+1]; for (int i=0;i<=k;i++) { C[i]=0; } for (int j=0;j<n;j++) { C[A[j]]=C[A[j]]+1; } for (i=0;i<=k;i++) { C[i+1]=C[i+1]+C[i]; } int x=C[b]-C[a-1]; return x; } void main() { //int A[n]={6,0,2,0,1,3,4,6,1,3},B[n]={0},k=0; int A[n]={2,5,3,0,2,3,0,3},B[n]={0},k=0; for (int i=0;i<n;i++) { if (A[i]>k) { k=A[i]; } } COUNTING_SORT(A,B,k); for (int j=0;j<n;j++) { cout<<B[j]<<" "; } int a=0,b=0; while (1) { cout<<"请输入需要查找的区间"<<endl; cin>>a; cin>>b; if (a<B[0]||b>B[n-1]) { cout<<"输入错误"<<endl; } else { break; } } cout<<"落在["<<a<<","<<b<<"]区间内的个数"<<counting_SORT(A,a,b,k)<<endl; }
8.2-1 参照图8-2的方法,说明COUNTING-SORT 在数组 A={6,0,2,0,1,3,4,6,1,3,2}上的操作过程。
(1)C[1..k]被初始化为0。
(2)把等于A[j]的个数C[A[j]]进行循环累加 C[6]=1+1=2 C[0]=1+1=2 C[2]=1+1=2 C[1]=1+1=2 C[3]=1+1=2 C[4]=1
(3)把小于等于A[j]的个数C[A[j]]进行循环累加。
C[0]=2
C[1]=C[1]+C[0]=2+2=4
C[2]=C[2]+C[1]=2+4=6
C[3]=C[3]+C[2]=2+6=8
C[4]=C[4]+C[3]=1+8=9
C[5]=C[5]+C[4]=0+9=9
C[6]=C[6]+C[5]=2+9=11
(4)把A[j]放入到恰当的B[C[A[j]]]位置中去。
B[C[2]]=B[6]=2 C[2]-- C[2]=5
B[C[3]]=B[8]=3 C[3]-- C[3]=7
B[C[1]]=B[4]=1 C[1]-- C[1]=3
B[C[6]]=B[11]=6 C[6]-- C[6]=10
B[C[4]]=B[9]=4 C[4]-- C[4]=8
B[C[3]]=B[7]=3 C[3]-- C[3]=6
B[C[1]]=B[3]=1 C[1]-- C[1]=2
B[C[0]]=B[2]=0 C[0]-- C[0]=1
B[C[2]]=B[5]=2 C[2]-- C[2]=4
B[C[0]]=B[1]=0 C[0]-- C[0]=0
B[C[6]]=B[10]=6 C[6]-- C[6]=9 所以:B[10]={0,0,1,4,2,2,3,3,4,6,6}
8.2-2 试证明COUNTING-SORT是稳定的。
由8.2-1例子可以发现 开始时候B[6]=2,这个是数组靠后位置的2,因为第4个循环是从后往前循环的,所以经过多次j--后,到了B[5]=2 这是考前位置的2.所以由8.2-1例子可以看出,原数组A靠后相同的数放到了新数组B靠后的位置,而原数组A靠前的数放到了新数组B靠前的位置。所以COUNTING-SORT是稳定的。
8.2-3 假设我们在COUNTING-SORT的第10行循环开始部分,将代码改写为:10.for j=1 to A.length 试证明该算法仍然正确,它还稳定吗?
如果把第10行改成for j=1 to A.length ,颠倒顺序处理元素的顺序,那么还是8.2-1的例子,其中的第(4)个循环就要微调了,对于相同元素来说,原数组靠前的相同元素出现在新数组靠后的位置上,反之亦然。所以就不稳定了。
8.2-4 设计一个算法,它能够对于任何给定的驾驭0到K之间的n个整数先进行预处理,然后再O(1)时间内回答输入的n个整数中有多少个落在区间[a..b]内你设计的算法预处理时间应为Θ(n+k).
在n个整数中,通过第(3)个循环,计算出小于a值得元素个数C[a-1],小于等于b值得元素个数C[b],两者差值就是在[a,b]区间上的元素个数。
设落在区间[a,b]上元素个数x=C[b]-C[a-1].
8.3基数排序
基数排序如下:
#include <iostream> #include <time.h> using namespace std; const n=10,m=4; int Max(int B[n][m],int h)//O(n) {//求最大k值 int k=0; for (int i=0;i<n;i++)//O(n) { if (B[i][h]>k) { k=B[i][h]; } } return k; } void COUNTING_SORT(int B[n][m],int C[n],int k,int h)//O(k)+O(n)+O(k)+O(n)=O(n+k) {//计数排序 int *D=new int[k+1]; for (int i=0;i<=k;i++)//O(k) { D[i]=0; } for (int j=0;j<n;j++)//O(n) { D[B[j][h]]=D[B[j][h]]+1; } for (i=0;i<=k;i++)//O(k) { D[i+1]=D[i+1]+D[i]; } for (j=n-1;j>=0;j--)//O(n) { C[D[B[j][h]]-1]=j;//把排好序的下标存放到数组C中以便按顺序把它存储到辅助数组E中。 D[B[j][h]]=D[B[j][h]]-1; } } int Converted_to_Decimal(int A[],int B[][m],int i)//O(d)+O(d)=O(d) {//此函数是将十进制数以2维数组B的形式存放。 int x=A[i]; for (int j=0;x>0;j++ )//O(d)循环了j<d次 { B[i][j]=x%10; x=x/10; } if (j<m) { for (int k=j;k<m;k++)//O(d) 循环了d-j次 { B[i][k]=0; } } return j; } void Radix_sort(int A[n],int B[][m],int C[n],int E[n],int d) { d=m; for ( int i=0;i<n;i++)//O(nd) { Converted_to_Decimal(A,B,i); } for ( i=0;i<d;i++)//O(d) d为位数 {//因为d<=d位数的最小值<=k,(例如3<3位数最小值100)所以d<=n+k,内层循环O(n)+O(n+k)+O(n)+O(n)+O(d)+O(n)=O(n+k) int k=Max(B,i);//O(n) COUNTING_SORT(B,C,k,i);//O(n+k) for (int i=0;i<n;i++)//O(n) { E[i]=A[C[i]];//每位上排好序后,将其复制到辅助数组E上。 } for ( i=0;i<n;i++)//O(n) { Converted_to_Decimal(E,B,i);//把辅助数组E上的数转换成二维数组存放到二维数组B中。 } for (i=0;i<n;i++)//O(n) { A[i]=E[i];//每次将按位排好序的数存放到数组A中以便在下一次循环中对下一位进行排序。 } }//所以Radix_sort时间复杂度为O(d(n+k)) //O(nd)+O(d(n+k))=O(d(n+k)) } void main() { srand( (unsigned)time( NULL ) ); int A[n]={0}; for (int i=0;i<n;i++) { A[i]=rand()%(n); } int B[n][m]={0},C[n]={0},E[n]={0}; Radix_sort(A,B,C,E,m); for ( i=0;i<n;i++) { cout<<A[i]<<" "; } }
最坏情况需要d轮排序。(因为有d位数,每一轮排1位。)
8.4桶排序
桶排序算法如下:
//1.此程序的基本思想是先把数组中的数按位划分(所有1位数在第1行,所有2位数第2行。。以此类推) //2.然后对于同一行同位数的数按最高位划分成1-9组数(所有2位数的最高位为1的在第1行,比如16,19这些,最高位为2的再第2行,比如23 25,以此类推) //3.对同位数(比如3位数 329 826 546)的一组数进行插入排序,这样同位数已经有序。 //4.最后按照由低位到高位数分别赋值给原数组。然后完成排序并输出。 //此程序还有待研究。因为原始数组以我的电脑来看,只能将12000-13000个数排序,多了就太占内存了所以不能输出。因为看到书上用链表的形式存放临时数组的 //或许用链表能输出大数据?本人没有试过。。 #include <time.h> #include <iostream> #include <iomanip> using namespace std; const int len=2000,bit=5; double **B; void InitialArr(double *arr,int n); //数组排序前的初始化 void PrintArr(double *arr,int n); //数组排序后的打印函数 int power(int i);//计算i位数的最小值 int One_dimensional_array_bit(double *arr,int i);//对于由一维数组保存的整数求出位数。 int Two_dimensional_array_bit(double **B,int m);//对于由二维数组保存的整数求出位数。 void insertion_sort(double **B,int m,int n);//插入排序。 void Bit_BucketSort(double *arr,int A[bit],int n);//当位数不一样时,按照位数由低到高放入桶中。 void first_BucketSort( double **B,int A[],int m);//当位数都一样时,按照最高位对应的值划分放入桶中。 void main() { double *arr=new double[len]; for (int i=0;i<len;i++) { arr[i]=0; } int A[bit]={0}; B = new double*[9]; for ( i = 0;i<9;i++) { B[i] = new double[len]; } for (i=0;i<bit;i++) { for (int j=0;j<len;j++) { B[i][j]=0; } } InitialArr(arr,len); Bit_BucketSort(arr,A,len); PrintArr(arr,len); delete []arr; } void InitialArr(double *arr,int n) { srand((unsigned)time(NULL)); for (int i = 0; i<n;i++) { arr[i] = (double)rand(); } } void PrintArr(double *arr,int n) { for (int i = 0;i < n; i++) { cout<<setw(15)<<arr[i]; if ((i+1)%5 == 0 || i == n-1) { cout<<endl; } } } int power(int i) { int s=1; for (int j=1;j<i;j++) { s*=10; } return s; } int One_dimensional_array_bit(double *arr,int i) { int m=0; double x=arr[i]; while (x>=1) { x/=10; m++; } return m; } int Two_dimensional_array_bit(double **B,int m) { int s=0; double x=B[m][0]; while (x>=1) { x/=10; s++; } return s; } void insertion_sort(double **B,int m,int n) { double key=0; for (int j=2;j<=n;j++) { key=B[m][j-1]; int i=j-1; while (i>0&&B[m][i-1]>key) { B[m][i]=B[m][i-1]; i=i-1; } B[m][i]=key; } } void Bit_BucketSort(double *arr,int A[bit],int n)//O(n) { int t=0,j=0,k=0; A=new int[bit]; for (int i=0;i<bit;i++) { A[i]=0;//A[bit]记录每个位数的个数,比如一共有A[1]个数是2位数,一共有A[2]个数是3位数 } for ( i=0;i<len;i++) { t=One_dimensional_array_bit(arr,i)-1; j=A[t]; B[t][j]=arr[i];//把t位数放入数组B的第t-1行,j为数组存放t位数的个数。 A[t]++; } for (i=0;i<bit;i++) { first_BucketSort( B,A,i); } for (i=0;i<bit;i++) { for (int j=0;j<len;j++) { if (B[i][j]>0&&k!=len) { arr[k]=B[i][j]; k++; } } } delete []A; } void first_BucketSort( double **B,int A[],int m)//B[bit][len] {//A[m]表示m位数的个数 比如是3位数的数一共有A[3]个 int s=power(Two_dimensional_array_bit(B,m)); //double C[9][len]={0};//一个数的第一位有9种数字可以选择 double **C=new double*[9]; for (int i = 0;i<9;i++) { C[i] = new double[len]; } for ( i=0;i<9;i++) { for (int j=0;j<len;j++) { C[i][j]=0; } } int *D=new int[len],t=0,k=0;//保存位数相同时的数的第一位相同时的元素个数D[len]。 for ( i=0;i<len;i++) { D[i]=0; } for (int j=0;j<A[m];j++) { t=(int)B[m][j]/s-1;//计算n位数的最高位的数值t C[t][D[t]]=B[m][j];//比如同为三位数,百位为1的放在C的第一行中,百位为8的放在C的第八行中。 D[t]++; } for ( i=0;i<9;i++) { j=D[i]; insertion_sort(C,i,j); } for (i=0;i<9;i++) { for (int j=0;j<len;j++) { if (C[i][j]>0&&k!=A[m]) { B[m][k]=C[i][j]; k++; } } } for ( i = 0 ; i<9 ;i++) { delete C[i]; C[i] =NULL; } delete []C; C = NULL; delete []D; }
貌似必须抛掷均匀硬币,这样才可能是等可能的出现正背面情况,每次抛掷出现正面的概率是1/2,抛掷2次,那么出现正面次数服从伯努利实验分布,正面算做成功抛掷,其概率是p=1/2,背面算做失败抛掷,其概率是1-1/2,其期望值有公式 E(x)=np 既然抛掷了2次,那么n=2,所以E(x)=2*1/2=1次,假设两次抛掷是独立E(x^2)=E(x)*E(x)=E^2(x)=1*1=1 这个答案需要满足2个条件(1是均匀硬币,2是两次抛掷独立)