算法导论第八章线性时间排序课后答案

来源:互联网 发布:中科大软件学院招生 编辑:程序博客网 时间:2024/05/18 02:01

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]<<" ";    }}

8.3-1参照8-3的方法,说明RADIX-SORT在下列英文单词上的操作过程 COW DOG SEA RUG ROW MOB BOX TAB BAR EAR TAR DIG BIG TEA NOW FOX
1.对第三位进行计数排序后:SEA TEA MOB TAB DOG RUG DIG BIG BAR EAR TAR COW ROW NOW BOX FOX
2.对第二位进行计数排序后:TAB BAR EAR TAR SEA TEA DIG BIG MOB DOG COW ROW NOW BOX FOX RUG
3.对第一位进行计数排序后:BAR BIG BOX COW DIG DOG EAR FOX MOB NOW ROW RUG SEA TAB TAR TEA
4.完成排序。


8.3-2 下面的排序算法中哪些是稳定的,插入排序,归并排序,堆排序和快速排序?给出一个能使任何排序算法都稳定的方法。
归并和插入排序是稳定的,因为他们不改变相同元素原有的顺序。堆排序和快速排序是不稳定的。如果想让以上4种排序都是稳定排序,那么我们保存原数组相同元素的下标,在进行比较排序时,不同元素肯定没有这个问题,而相同元素在原数组中较小的下标对应的值放入到前面,较大的放到后面。


8.3-3 利用归纳法证明基数排序是正确的,在你所给出的证明中,你所给出的证明中,在哪里需要假设所用的底层算法是稳定的?
假设n个数有t位,那么n个数中的所有t-1位数已经用计数排序排好序了,我们现在开始对第t位开始使用计数排序,有两种可能的情况,如果n个数中的t位有相同的数,那么我们应该假设计数排序是稳定的,对于相同的数,先出现的排在前面,后出现的排在后面。如果n个数中的第t位不相同,那么我们对其再次进行计数排序。直到所有t位数都排好了。

8.3-4  说明如何在O(n)时间内,对0到n^3-1区间内的n个整数进行排序。
要想在线性时间内完成排序,那么我们需要对其进行非比较排序,可以选择基数排序我可以把在[0..n^3-1]区间内的数转化成b位二进制数,这个b位二进制数不大于b位都是1的整数,所以有n^3-1<=1*2^0+1*2^1+....1*2^(b-1) => b>=3lgn>lgn  这种情况书中已经给出结论选择r=lgn,可以得到不超过常数系数范围内的最优时间代价(貌似是对b/r(n+2^r)求导得出的极值)。书中已经给出当b=O(lgn)时,并且r=lgn,基数排序运行时间在Θ(lgn)的结论。
 
8.3-5 在本届给出的第一个卡片排序算法中,为排序d位十进制数,在最坏情况下需要进行多少轮排序?

最坏情况需要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;}

8.4-1  参照8-4的方法,说明BUCKET-SORT在数组A={0.79,0.13,0.16,0.64,0.39,0.20,0.89,0.53,0.71,0.42}
操作过程和原书桶排序类似。这里不做累述。

8.4-2 解释为什么桶排序在最坏情况下运行时间是Θ(n^2)?我们应该如何改善算法,使其在保持平均情况为线性时间代价的同时,最坏情况下时间代价为O(nlgn)?
如果分的1-9个桶的值都落在某一个桶中,这样对于该桶进行插入排序,那么总的时间复杂度就为插入排序的时间复杂度Θ(n^2)。如果想用最坏时间为O(nlgn)的算法,应该用比如归并排序来替换插入排序算法,这样目的达成。

8.4-3 设X是一个随机变量,用于表示在将一枚硬币抛掷两次时,正面朝上的次数E(x^2)是多少呢? E^2(x)?

貌似必须抛掷均匀硬币,这样才可能是等可能的出现正背面情况,每次抛掷出现正面的概率是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是两次抛掷独立)


8.4-4 在单位圆内给定n个点,Pi(xi,yi),对所有i=1,2..n,有0<xi^2+yi^2<1,假设假设所有点服从均匀分布,既在单位圆的任一区域内找到给定点的概率与该区域的面积成正比。请设计一个在平均情况下有Θ(n)时间代价的算法,它能够按照点到原点之间的距离di=√(xi^2+yi^2)对这n个点进行排序。
将圆平均划分为n个区域(相当于n个桶),因为di=√(Xi^2+Yi^2)是单位圆里某点到原点的距离(di相当于书中数组中的数,按照桶排序把di分到各个桶中),所以其中di∈(0,1),每个点服从均匀分布,那么每个点等可能的落在这n个区域,所以每个桶中的数据趋近于平均个数,所以这样可以达到平均的Θ(n)时间。

8.4-5 定义随机变量X的概率分布函数P(x)为P(x)=Pr{X=x}.假设有n个随机变量X1,X2,...Xn服从一个连续概率分布函数P,且它可以在O(1)时间内被计算得到。设计一个算法,使其能够在平均情况下在线性时间内完成这些数的排序。
这n个Pi∈(0,1)变量可能不服从均匀概率分布,如果服从均匀概率分布,那么就可以接近平均的把其放入各个桶中,从而在什么情况下都肯定能得到Θ(n)的时间复杂度,如果不是均匀分布,那么不考虑最坏情况的极不平均的情况,按照桶排序在平均情况下还是Θ(n)。

0 1
原创粉丝点击