数据结构与算法分析--c语言描述(原书第二版)练习自答(第七章)

7.1

#!/bin/bash
#形如7-1.sh 3 1 4 1 5 9 2 6 5的输入即可排序
array=(`echo $@`)
length=${#array[@]}
for((i=0;i<$length;i++))
do
        Tmp=${array[i]}
        j=$i
        while [ $j -gt 0 ]&&[ ${array[$j-1]} -gt $Tmp ]
        do
                array[$j]=${array[$j-1]}
                j=$[j-1]
        done
        array[$j]=$Tmp
done
echo ${array[@]}

插入排序的c函数在仓库内可以看到

7.2

O ( N ) O(N) O(N),因为内层循环不会进入。

7.3

交换至少可以消除 A [ i ] A[i] A[i] A [ i + k ] A[i+k] A[i+k]两者的逆序,所以至少1个。而在最多的情况下,他们的交换针对位于他们之间的元素如 A [ i + 1 ] A[i+1] A[i+1], A [ i + 2 ] A[i+2] A[i+2] A [ i + k − 1 ] A[i+k-1] A[i+k1] k − 1 k-1 k1个,每个元素消除2个逆序,再加上他们之间的1个, 2 ( k − 1 ) + 1 = 2 k − 1 2(k-1)+1=2k-1 2(k1)+1=2k1

7.4

Original987654321
after 7-sort217654398
after 3-sort214357698
after 1-sort123456789

7.5

  1. 2-排序就是两个元素数量为 N 2 N\over2 2N子数组的插入排序,所以是 O ( N 2 ) O(N^2) O(N2),同理1-排序也是 O ( N 2 ) O(N^2) O(N2),所以总的是 O ( N 2 ) O(N^2) O(N2)
  2. 待完善
  3. 待完善

7.6

待完善

7.7

待完善

7.8

待完善

7.9

  1. O ( N l o g N ) O(NlogN) O(NlogN)。没有交换,但是每一趟有 O ( N ) O(N) O(N),采用希尔建议的增量序列的话是 l o g N logN logN趟。
  2. 待完善

7.10

  1. 不会,因为依然有可能在相邻的增量间存在公因子。
  2. 可能,因为相邻的增量互素,这时候的界来到 O ( N 3 / 2 ) O(N^{3/2}) O(N3/2)。例如1,3,5,11,23,47

7.11

代码中只使用了Initialize,PrintHeap,BuildHeap,PercolateDown,DeleteMin,IsEmpty这几个函数,其余函数未修改

读入数据后构建堆的结果879 811 572 434 543 123 142 65 111 242 453 102
然后循环将堆顶的元素从堆中删除并放入数组尾部,每一步如下:
811 543 572 434 453 123 142 65 111 242 102 879
572 543 142 434 453 123 102 65 111 242 811 879
543 453 142 434 242 123 102 65 111 572 811 879
453 434 142 111 242 123 102 65 543 572 811 879
434 242 142 111 65 123 102 453 543 572 811 879
242 111 142 102 65 123 434 453 543 572 811 879
142 111 123 102 65 242 434 453 543 572 811 879
123 111 65 102 142 242 434 453 543 572 811 879
111 102 65 123 142 242 434 453 543 572 811 879
102 65 111 123 142 242 434 453 543 572 811 879
65 102 111 123 142 242 434 453 543 572 811 879

7.12

堆排序总是使用至少 N l o g N NlogN NlogN - O ( N ) O(N) O(N)次比较,所以不存在特别的好的输入。所以运行时间仍然是 O ( N l o g N ) O(NlogN) O(NlogN)

7.13

首先处理{3,1,4,1},{5,9,2,6},针对第一个子序列,递归处理{3,1},{4,1}。继续推进递归处理{3},{1},到达基准情况。开始合并得到{1,3},同理{1,4}。然后继续合并得到{1,1,3,4},另一边的子序列同理,得到{2,5,6,9},最终合并得到{1,1,2,3,4,5,6,9}。

7.14

可以这样处理,先把数组分成许多长度为1的子数组,每两个为一对,进行merge。然后把数组分成许多长度为2的子数组,每两个为一对,进行merge。继而把数组分成许多长度为4的子数组,每两个为一对,进行merge。以此类推,直到子数组的长度大于原数组长度的一半。

#include <stdio.h>
#include <stdlib.h>
#define min(x,y) (x)<(y)?(x):(y)
typedef int ElementType;
void FataError(char *s)
{
        fputs(s,stderr);
        putc('\n',stderr);
}
void Merge(ElementType A[],ElementType TmpArray[],int Lpos,int Rpos,int RightEnd)
{
        int i,LeftEnd,NumElements,TmpPos;
        LeftEnd=Rpos-1;
        TmpPos=Lpos;
        NumElements=RightEnd-Lpos+1;
        while(Lpos<=LeftEnd&&Rpos<=RightEnd)
        {
                if(A[Lpos]<=A[Rpos])
                        TmpArray[TmpPos++]=A[Lpos++];
                else
                        TmpArray[TmpPos++]=A[Rpos++];
        }
        while(Lpos<=LeftEnd)
                TmpArray[TmpPos++]=A[Lpos++];
        while(Rpos<=RightEnd)
                TmpArray[TmpPos++]=A[Rpos++];
        for(i=0;i<NumElements;i++,RightEnd--)
                A[RightEnd]=TmpArray[RightEnd];
}
void MergeSort(ElementType A[],int N)
{
        ElementType *TmpArray;
        int SubListSize,Part1Start,Part2Start,Part2End;
        TmpArray=malloc(N*sizeof(ElementType));
        if(TmpArray!=NULL)
        {
                for(SubListSize=1;SubListSize<N;SubListSize*=2)
                {
                        Part1Start=0;
                        while(Part1Start+SubListSize<N)
                        {
                                Part2Start=Part1Start+SubListSize;
                                Part2End=min(N-1,Part2Start+SubListSize-1);
                                Merge(A,TmpArray,Part1Start,Part2Start,Part2End);
                                Part1Start=Part2Start+1;
                        }
                }
                free(TmpArray);
        }
        else
                FataError("No space for tmp array!");
}
int main(void)
{
        int arr[]={2,5,3};
        MergeSort(arr,3);
        for(int i=0;i<3;i++)
                printf("%d ",arr[i]);
        return 0;
}

7.15

因为合并例程必然会运行 θ ( O ) \theta(O) θ(O),所以归并排序不论什么样的输入,开销总是 θ ( N l o g N ) \theta(NlogN) θ(NlogN)

7.16

待完善

7.17

初始输入为3,1,4,1,5,9,2,6,5,3,5
根据三数中值分割法,确定最初,中间和最后的值分别是3,5,9,故枢纽元为5,将其分别放到数组首位,倒数第二位和末位,数组变为3,1,4,1,5,3,2,6,5,5,9。第一次交换发生在两个5之间,然后继续前进,知道i,j交错,最后将枢纽元5交换回i所指位置,得到数组3,1,4,1,5,3,2,5,5,6,9。
然后递归的快速排序子数组3,1,4,1,5,3,2。继续利用三数中值分割法,确定最初、中间和最后的值分别是1,2,3,枢纽元为2,数组通过一系列交换,最后为1,1,2,3,5,4,3。同理继续对子数组1,1进行插入排序而3,5,4,3调用快速排序。
第一步中得到的子数组5,6,9也进行插入排序。
最后得到的结果是1,1,2,3,3,4,5,5,5,6,9

7.18

O ( N l o g N ) O(NlogN) O(NlogN),因为这样的输入会使三数中值分割法得到的子数组尽可以大小相等。而随机的输入数据得益于三数中值分割法和截止范围,也可以得到 O ( N l o g N ) O(NlogN) O(NlogN)

7.19

  1. 当使用第一个元素作为枢纽元时,排过序和反序排列的输入,都会得到 O ( N 2 ) O(N^2) O(N2)的时间开销,因为这时候所有的元素不是被划入前面的子数组就是全部被划入后面的子数组。而随机的输入则会得到 O ( N l o g N ) O(NlogN) O(NlogN)的开销。
  2. 与上面一样
  3. 使用随机元素作为枢纽元则可以期望 O ( N l o g N ) O(NlogN) O(NlogN)的开销,但是在随机结果特别差的情况下,也有可能得到 O ( N 2 ) O(N^2) O(N2)的最坏情况,虽然这种可能性很小。并且随机数的生成一般是昂贵的。
  4. 待完善

7.20

  1. O ( N l o g N ) O(NlogN) O(NlogN),这时候数组每次都会被分成大小相等的子数组。
  2. 应该放置sentinel以防止i,j跑出界。但是这时候运行时间将是 O ( N 2 ) O(N^2) O(N2),因为i将直接跑到sentinel,而所有元素将被划入一个子数组,另一个子数组没有元素。
  3. 同理应该放置sentinel以防止j跑出界,并且同样的所有元素将被划入一个子数组,另一个子数组没有元素,所以运行时间将是 O ( N 2 ) O(N^2) O(N2)

7.21

是的,但是这不能保证产生尽可能平衡的子数组。

7.22

20 , 3 , 5 , 7 , 9 , 11 , 13 , 15 , 17 , 19 , 4 , 10 , 2 , 12 , 6 , 14 , 1 , 16 , 8 , 18 20,3,5,7,9,11,13,15,17,19,4,10,2,12,6,14,1,16,8,18 20,3,5,7,9,11,13,15,17,19,4,10,2,12,6,14,1,16,8,18

7.23

#define Cutoff (3)
void Swap(ElementType *A1,ElementType *A2)
{
        ElementType Tmp;
        Tmp=*A1;
        *A1=*A2;
        *A2=Tmp;
}
ElementType Median3(ElementType A[],int Left,int Right)
{
        int Center=(Left+Right)/2;
        if(A[Left]>A[Center])
                Swap(&A[Left],&A[Center]);
        if(A[Left]>A[Right])
                Swap(&A[Left],&A[Right]);
        if(A[Center]>A[Right])
                Swap(&A[Center],&A[Right]);
        Swap(&A[Center],&A[Right-1]);
        return A[Right-1];
}
void Qselect(ElementType A[],int k,int Left,int Right)
{
        int i,j;
        ElementType Pivot;
        if(Left+Cutoff<=Right)
        {
                Pivot=Median3(A,Left,Right);
                i=Left;
                j=Right-1;
                for(;;)
                {
                        while(A[++i]<Pivot);
                        while(A[--j]>Pivot);
                        if(i<j)
                                Swap(&A[i],&A[j]);
                        else
                                break;
                }
                Swap(&A[i],&A[Right-1]);
                if(k<=i)
                        Qselect(A,k,Left,i-1);
                else if(k>i+1)
                        Qselect(A,k,i+1,Right);
        }
        else
                InsertionSort(A+Left,Right-Left+1);
}       //第k个最小元就在下标k-1处

7.24

待完善

7.25

例如插入排序,归并排序这种是稳定的,例如快速排序则不是。

7.26

待完善

7.27

因为这种决策树将有N片树叶,所以深度至少是 l o g N logN logN,即 Ω ( l o g N ) \Omega(logN) Ω(logN)次比较。

7.28

l o g ( N ! ) ≈ N l o g N − N l o g e log(N!)\approx NlogN-Nloge log(N!)NlogNNloge

7.29

待完善

7.30

花费常数时间将所有元素放入桶中,有N个元素 O ( N ) O(N) O(N)。另外再花费常数时间将元素从桶中提取出来,有M个桶,故一共需要 O ( M + N ) O(M+N) O(M+N)

7.31

设定一个元素maybe,令false<maybe<true,将maybe置于N+1处。然后使用标准的快速排序的算法,让maybe成为枢纽元,只要一趟操作,则可以让maybe前面的子数组全是false,后面的子数组全是true。然后再交换maybe和位置N+1处的元素,则前N个元素组成的数组就是目标答案。

7.32

原理同上题,先在N+1的位置上放置一个ProbablyFalse( f a l s e < P r o b a b l y F a l s e < m a y b e false<ProbablyFalse<maybe false<ProbablyFalse<maybe),选取ProbablyFalse为枢纽元,进行一趟快速排序的算法,然后再交换ProbablyFalse与N+1处的元素,这时候前面的子数组全由false组成,后面则有maybe和true组成。接下来在N+1的位置上放置一个ProbablyTrue( m a y b e < P r o b a b l y T r u e < t r u e maybe<ProbablyTrue<true maybe<ProbablyTrue<true),同样的操作,然后前N个元素组成的数组就是目标答案了。

7.33

  1. 根据正文中定理7.6得知,需要 l o g ( 4 ! ) = 5 log(4!)=5 log(4!)=5
  2. 扩展正文中的决策树即可。

7.34

  1. 同上, l o g ( 5 ! ) = 7 log(5!)=7 log(5!)=7
  2. 同上

7.35

void ShellSort(ElementType A[],int N)
{
        int i,j,Increment;
        ElementType Tmp;
        for(Increment=N/2;Increment>0;Increment/=2)
        {
                for(i=Increment;i<N;i++)
                {
                        Tmp=A[i];
                        for(j=i;j>=Increment;j-=Increment)
                        {
                                if(Tmp<A[j-Increment])
                                        A[j]=A[j-Increment];
                                else
                                        break;
                        }
                        A[j]=Tmp;
                }
        }
}

7.36

#define Cutoff (3)
void Swap(ElementType *A1,ElementType *A2)
{
        ElementType Tmp;
        Tmp=*A1;
        *A1=*A2;
        *A2=Tmp;
}
ElementType Median3(ElementType A[],int Left,int Right)
{
        int Center=(Left+Right)/2;
        if(A[Left]>A[Center])
                Swap(&A[Left],&A[Center]);
        if(A[Left]>A[Right])
                Swap(&A[Left],&A[Right]);
        if(A[Center]>A[Right])
                Swap(&A[Center],&A[Right]);
        Swap(&A[Center],&A[Right-1]);
        return A[Right-1];
}
void Qsort(ElementType A[],int Left,int Right)
{
        int i,j;
        ElementType Pivot;
        if(Left+Cutoff<=Right)
        {
                Pivot=Median3(A,Left,Right);
                i=Left;
                j=Right-1;
                for(;;)
                {
                        while(A[++i]<Pivot);
                        while(A[--j]>Pivot);
                        if(i<j)
                                Swap(&A[i],&A[j]);
                        else
                                break;
                }
                Swap(&A[i],&A[Right-1]);
                Qsort(A,Left,i-1);
                Qsort(A,i+1,Right);
        }
        else
                InsertionSort(A+Left,Right-Left+1);
}
void QuickSort(ElementType A[],int N)
{
        Qsort(A,0,N-1);
}

7.37

待完善

7.38

  1. 时间开销是二次的,观察输入,第一个枢纽元必定是2,将2与1交换然后运行,i会挺在第二个位置,而j会到达数组第一个位置。这时候再将枢纽元交换回来,数组就是1,2,4,5……N-1,N,3。对右边的子数组再次调用快速排序,同理,枢纽元会是4,又会形成两个极不平衡的子数组。每一次递归调用的子数组只比原数组少两个元素,所以时间开销是二次的。
  2. 虽然在第一次选取枢纽元时,可以将数组分成两个平衡的子数组,但是再对两个子数组递归调用快速排序时,又会形成上面那种情况,所以时间开销依然是二次的。

7.39

用归纳法证明,一个有L片树叶的二叉树,它的平均深度至少为 l o g L logL logL
L = 1 L=1 L=1时显然成立,假设当 L − 1 L-1 L1时推论成立。考虑一个有L片树叶的二叉树,要有最少的平均深度,那么必然它的根有左右子树。设左子树有 L L L_L LL片树叶,右子树有 L R L_R LR片树叶,所以左子树的树叶的总深度是 L L ( 1 + l o g L L ) L_L(1+logL_L) LL(1+logLL),而右子树的树叶的总深度是 L R ( 1 + l o g L R ) L_R(1+logL_R) LR(1+logLR)。之所以加1,是因为他们是在根的基础上计算的深度。所以总的深度就是 L + L L l o g L L + L R l o g L R L+L_LlogL_L+L_RlogL_R L+LLlogLL+LRlogLR
f ( x ) = x l o g x f(x)=xlogx f(x)=xlogx x ≥ 1 x\geq1 x1时, f ( x ) + f ( y ) ≥ 2 f ( ( x + y ) / 2 ) f(x)+f(y)\geq2f((x+y)/2) f(x)+f(y)2f((x+y)/2)成立。于是总深度大于 L + 2 ( L / 2 ) l o g ( L / 2 ) ≥ L + L l o g ( L − 1 ) ≥ L l o g L L+2(L/2)log(L/2)\geq L+Llog(L-1)\geq LlogL L+2(L/2)log(L/2)L+Llog(L1)LlogL。所以平均深度至少是 l o g L logL logL

7.40

待完善

7.41

待完善

7.42

待完善

7.43

待完善

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值