算法导论课后习题解析 第六章

来源:互联网 发布:黑暗之魂3捏御姐数据 编辑:程序博客网 时间:2024/05/21 23:01

算法导论课后习题解析 第六章

6.1-1
元素最少的情况是最底层只有一个叶子,即2h2h;元素最多的情况是整棵树是满的,即2h+112h+1−1。(这里按照叶子到根的最大边数来定义高度)

 

6.1-2
设高度为h,那么可知

2hn2h+112h≤n≤2h+1−1
hlgn<h+1⇒h≤lg⁡n<h+1
lgn1<hlgn⇒lg⁡n−1<h≤lg⁡n
h=lgnh=⌊lg⁡n⌋

 

6.1-3
令p(i)为第i个元素的父亲下标,那么对任意i>1i>1,都有A[i]A[p(i)]A[p(p(i))]A[1]A[i]≤A[p(i)]≤A[p(p(i))]≤⋯≤A[1],即都小于根元素。

 

6.1-4
最小的元素处于最底层或次底层的叶子结点,即该结点没有子结点。这时因为大根堆(max heap)中父结点一定不小于子结点,所以最小的元素必须出现在叶子结点上。

 

6.1-5
对于有序的数列肯定有i<j,ai<aj∀i<j,ai<aj,而Parent(i)=i/2<iParent(i)=i/2<i,故有aParent(i)<aiaParent(i)<ai,满足小根堆的性质。

 

6.1-6
树结构如下,元素7和6不满足大根堆的性质,故不是大根堆。

         23    17        14  6    13   10   15  7  12

 

6.1-7
设堆的高度为h,那么除去最底层的结点的数量为2h2h+1/22h=2h+1/2个,所以底层结点的下标为2h+1/2+1,2h+1/2+2,n2h+1/2+1,2h+1/2+2⋯,n,即n/2+1,n/2+2,,n⌊n/2⌋+1,⌊n/2⌋+2,⋯,n

 

6.2-1
简单起见,我们只列出收影响的部分子树

    3 10    18  9  0---------    10  3    18  9  0---------   10 9     18  3  0

 

6.2-2
只要把相应的最小元素移到父亲的位置即可

1
2
3
4
5
6
7
8
9
10
11
12
Min-Heapify(A, i)
    l = Left(A, i)
    r = Right(A, i)
    if l <= A.heap-size and A[l] < A[i]
        smallest = l
    else
        smallest = i
    if r <= A.heap-size and A[r] < A[smallest]
        smallest = r
    if smallest != i
        swap A[i] with A[smallest]
        Min-Heapify(A, smallest)

由于只改变了比较部分,该算法的时间复杂度与Max-Heapify一致。

 

6.2-3
不会有任何改变。

 

6.2-4
i>A.heapsize/2i>A.heap−size/2时,结点为叶子结点没有孩子,所以不会有任何改变。

 

6.2-5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Max-Heapify(A, i)
    while true
        l = Left(A, i)
        r = Right(A, i)
        if l <= A.heap-size and A[l] > A[i]
            largest = l
        else
            largest = i
        if r <= A.heap-size and A[r] > A[largest]
            largest = r
        if largest != i
            swap A[i] with A[largest]
            i = largest
        else
            break

 

6.2-6
Max-Heapify最坏的情况下对从根结点到最深的叶子结点中的所有结点都调用一遍自身,例如叶子结点大于到根结点路径上所有结点的值,如果结点数为n,那么高度为lgnlg⁡n,如果每次比较和交换的时间为Θ(1)Θ(1),那么总时间为Θ(lgn)Θ(lg⁡n)

 

6.3-1

         5    3        17 10   84   19   622 9------------------         5    3        17 22   84   19   610 9------------------         5    3        19 22   84   17   610 9------------------         5    84       19 22    3   17   610 9------------------         84    22       19 10    3   17   65  9

 

6.3-2
因为调用Max-Heapify都假定当前结点的左右子树都是堆,只有从下往上调用才能满足这样的前提。

 

6.3-3
首先考虑h=0,即叶子结点的情况。假设堆的高度为H,当该堆是一个满二叉树时,显然有叶子结点的数量为2H+12H=2H=n/2=n/2h+12H+1−2H=2H=n/2=n/2h+1。当堆不是满二叉树时,叶子结点分布在深度为H和H-1的结点中。我们令x表示深度为H的结点数,那么n-x即为高度为H-1的满二叉树的结点数,所以n-x必为奇数。由此可见x和n中有一个奇数一个偶数。

  • 当n是奇数x是偶数时,说明所有的非叶结点都有两个孩子,那么nleaf+ninternal=2ninternal+1nleaf+ninternal=2ninternal+1可得nleaf=ninternal+1nleaf=ninternal+1,所以n=nleaf+ninternal=2nleaf1nleaf=(n+1)/2=n/2n=nleaf+ninternal=2nleaf−1⇒nleaf=(n+1)/2=⌈n/2⌉
  • 当n是偶数x是奇数时,我们为最右下的叶子结点添加一个兄弟使其变为第一种情况,这时可得nleaf+1=(n+1)/2=n/2+1nleaf=n/2nleaf+1=⌊(n+1)/2⌋=⌈n/2⌉+1⇒nleaf=⌈n/2⌉

接下来我们假设当高度为h-1时命题成立,证明当高度为h时也成立。令nhnh表示高度为h的结点的个数,nhnh′表示去掉叶子结点后高度为h的结点的个数,由于去掉叶子结点后所有结点的高度减1,所以有nh=nh1nh=nh−1′。又由于叶子结点的数量前面已经求出为n/2⌈n/2⌉所以去掉后结点的数量变为n/2⌊n/2⌋,代入上式可得:

nh=nh1n2h=n/22hn/22h=n2h+1nh=nh−1′≤⌈n′2h⌉=⌈⌊n/2⌋2h⌉≤⌈n/22h⌉=⌈n2h+1⌉
于是我们利用数学归纳法得证。

 

6.4-1
竖线表示堆的末尾

          5    13         2  25   7    17   208  4建堆------------------          25    13        20  8    7    17   25  4 |---------------------          20    13        17  8    7    4   25 | 25---------------------          17    13        5  8    7    4   2 |20 25---------------------          13    8         5  2    7    4 | 1720 25---------------------          8    7         5  2    4  | 13  1720 25---------------------          7    4         5  2 |  8    13  1720 25---------------------          5    4         2 |  7    8    13  1720 25---------------------          4    2    |    5  7    8    13  1720 25---------------------          2 |    4         5  7    8    13  1720 25

 

6.4-2

  • Initialization: 开始时由于使用Build-Max-Heap把A[1..n]变成一个堆,而A[n+1,n]即空集显然包含0个A种的最大值,所以满足循环不变条件;
  • Maintenance: 一遍循环开始前有A[1]为A[1..i]中的最大值,同时A[1]A[i+1]A[i+2]A[n]A[1]≤A[i+1]≤A[i+2]≤⋯A[n],这时交换A[1]和A[i]使得A[i]A[i+1]A[i+2]A[n]A[i]≤A[i+1]≤A[i+2]≤⋯A[n]。之后将heap-size减1调用Max-Heapify,使得A[1..i-1]重新成为一个堆,这在i增1之后满足了下一遍循环的前提条件;
  • Termination: 当循环终止时i=1,此时有A[1]A[1+1]A[1+2]A[n]A[1]≤A[1+1]≤A[1+2]≤⋯A[n],即序列有序。

 

6.4-3

  • 当序列已经有序时,建堆的时候每次调用Max-Heapify都要进行最大次数的交换,根据6.3节所求的上界,时间为O(n)。之后排序的时间为O(nlgn)O(nlg⁡n),所以总时间为O(nlgn)O(nlg⁡n)
  • 当序列为递减时,建堆的时候每次调用Max-Heapify只进行一次交换,共n/2次,所以时间为O(n)。之后排序的时间为O(nlgn)O(nlg⁡n),所以总时间为O(nlgn)O(nlg⁡n)

 

6.4-4
当排序阶段每次调用Max-Heapify时都进行最大次数的交换时,总的交换次数为:

Ti=2nlgn=Θ(nlgn)=Ω(nlgn)T≥∑i=2n⌊lg⁡n⌋=Θ(nlg⁡n)=Ω(nlg⁡n)

 

6.4-5
堆排序的过程可以理解为堆中每个元素都不断地向堆顶移动,直到所有的元素都到达过堆顶的过程,该题可参见Sedgewick的论文The analysis of heapsort

 

6.5-1

          15    13           9  5    12     8    74  0  6  2  1---------------------          1    13           9  5    12     8    74  0  6  2---------------------          13    1            9  5    12     8    74  0  6  2---------------------          13    12           9  5    1      8    74  0  6  2---------------------          13    12           9  5    6      8    74  0  1  2

 

6.5-2

          15    13           9  5    12     8    74  0  6  2  1  10---------------------          15    13           9  5    12     10    74  0  6  2  1   8---------------------          15    13          10  5    12     9    74  0  6  2  1   8---------------------          15    13          10  5    12     9    74  0  6  2  1   8

 

6.5-3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Heap-Minimum(A)
    return A[1]
 
Heap-Min(A)
    if A.heap-size == 0
        error"heap underflow"
    min = A[1]
    A[1] = A[A.heap-size]
    A.heap-size = A.heap-size - 1
    Min-Heapify(A, 1)
    return min
 
Heap-Decrease-Key(A, i, key)
    if A[i] < key
        error"new key is bigger than original"
    A[i] = key
    while i > 1 and A[i] < A[Parent(i)]
        exchange A[i]  A[Parent(i)]
        i = Parent(i)
 
Heap-Insert(A, key)
    A.heap-size = A.heap-size + 1
    A[A.heap-size] = INFINITE
    Heap-Decrease-Key(A, A.heap-size, key)

 

6.5-4
因为Heap-Increase-Key的前提条件是原来的key小于新的key,将原来的key设为负无穷就可以保证Heap-Increase-Key调用成功。

 

6.5-5

  • Initialization: 在还没有增加之前A[1..A.heap-size]是一个大根堆,所以在A[i]增加为key之后只有可能A[i]和A[Parent(i)]之间不满足堆的性质;
  • Maintenance: 假如一遍循环开始前满足循环条件,那么将A[i]和A[Parent(i)]之中大的那个放到A[Parent(i)]使得他们之间满足堆的性质,而有可能使得A[Parent(i)]和A[Parent(Parent(i))]违背堆的性质,当i变为Parent(i)之后满足了下一次循环的前提条件;
  • Termination: 当循环终止时i=1或者A[i]<A[Parent(i)],当i=1时A[Parent(i)]不存在,而A[i]<A[Parent(i)]时不破坏堆的性质,所以整个A[1..A.heap-size]都满足堆的性质。

 

6.5-6
可以先向下移动小于他的祖先,直到没有小于他的祖先后放在空出的位置上

1
2
3
4
5
6
7
Heap-Increase-Key(A, i, key)
    if A[i] < key
        error"new key is smaller than original"
    while i > 1 and A[Parent(i)] < key
        A[i] = A[Parent(i)]
        i = Parent(i)
    A[i] = key

 

6.5-7
要使得队列先进先出就要使先进入元素的key大于后进入的key即可,最简单的方式就是第一个进入的key值最大,后面进入的key值比前面的少1。 反过来第一个进入的key值为0,之后进入的key值递增就能实现先进后出的栈。

 

6.5-8
相当于对A[i]为根的堆进行Extract-Max操作

1
2
3
4
Heap-Delete(A, i)
    A[i] = A[A.heap-size]
    A.heap-size = A.heap-size - 1
    Heapify(A, i)

 

6.5-9
建一个大小为k的堆,堆中的每个元素代表一个List,元素的key为List当前最小元素的值,每次取出堆顶的元素,然后插入相应List中下一个元素的值,如果该List没有下一个元素就不插入,直到n个元素归并完毕。这里用类C++代码描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
struct Node
{
    int key;
    int list;
};
 
class MinHeap
{
public:
    Node extract();
    void insert(Node node);
};
 
class List
{
public:
    int at(int index);
    void set(int index,int value);
    int length();
    void reserve(int size);
};
 
void merge(List* lists, int k, List* result)
{
    MinHeap heap;
    int n = 0;
    int* A = new int[k];
    for (int i = 0; i < k; i++)
    {
        Node node = { lists[i].at(0), i };
        heap.insert(node);
        n += lists[i].length();
        A[i] = 1;
    }
    result->reserve(n);
    for (int i = 0; i < n; i++)
    {
        Node node = heap.extract();
        result->set(i, node.key);
        int j = node.list;
        if (A[j] < lists[j].length())
        {
            node.key = lists[j].get(A[j]);
            A[j]++;
            heap.insert(node);
        }
    }
    delete[] A;
}

 

6-1

a) 不一定能产生相同的堆,例如A={1,2,3,4,5},利用heapify产生的堆为

   5 4   31 2

利用heap-insert产生的堆为

   5 4   21 3

b) 当A初始时元素递增排列时,每次调用heap-insert都要将该元素移到堆顶,所以移动的次数

T(n)=i=2nlgii=2nlgi=O(nlgn)T(n)=∑i=2n⌊lg⁡i⌋≤∑i=2nlg⁡i=O(nlg⁡n)

 

6-2

a) 当用A[1]表示堆顶的时候,他的孩子下标范围是[2, d+1],A[2]的孩子的下标范围是[d+2, 2d+1],A[3]孩子的下标范围是[2d+2, 3d+1],由此可得A[i]孩子下标范围是[d(i-1)+2, di+1]。同理可求元素父亲的下标。用Parent(i)来求下标为i的元素的父亲,用Child(i,j)用来求下标为i的元素的第j个孩子

1
2
3
4
5
Parent(i)
    return (i - 2) / d + 1
 
Child(i, j)
    return d * (i - 1) + j + 1

b) 高度为H的堆节点数量为

ndHH=h=0H1dh+j(1jdH)=dH1d1+j=(nj)(d1)+1=O(logdn))=O(lgn)n=∑h=0H−1dh+j(1≤j≤dH)=dH−1d−1+j⇒dH=(n−j)(d−1)+1⇒H=O(logd⁡n))=O(lg⁡n)

c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Max-Heapify(A, i)
    max = i
    for j = 1 to d
        c = Child(i, j)
        if (c <= A.heap-size && A[c] > A[max])
            max = c
    if i != max
        exchange A[i] with A[max]
        Max-Heapify(A, max)
 
Extract-Max(A)
    max = A[1]
    A[1] = A[A.heap-size]
    A.heap-size = A.heap-size - 1
    Max-Heapify(A, 1)
    return max

最坏情况下,换到A[1]位置的叶子元素需要移到堆底,共需O(logdn)O(logd⁡n)次交换,每次交换需要d次比较,共O(dlogdn)O(dlogd⁡n)次比较。

d)

1
2
3
4
5
6
7
8
9
10
11
12
Increase(A, i, key)
    if A[i] > key
        error"new key should be lagger than original"
    A[i] = key
    while i > 1 && A[i] > A[Parent(i)]
        exchange A[i] with A[Parent(i)]
        i = Parent(i)
 
Max-Insert(A, key)
    A.heap-size = A.heap-size + 1
    A[A.heap-size] = -INFINITE
    Increase(A, A.heap-size, key)

最坏情况下,增大的元素需要换到堆顶,而且该元素处于堆底,共需O(logdn)O(logd⁡n)次交换。

e) 见d)

 

6-3

a) 最简单的方法就是按照顺序从左到右从上到下依次填充数字即可,剩余的位置填上无穷。

281639412514(234589121416∞∞∞∞∞∞∞)

b) 如果Y[1,1]为无穷则其右边和下边的数都不能小于无穷,只有所有的元素都是无穷(即Y为空)才能满足;同理,如果Y[m,n]小于无穷则其左上元素都小于无穷,即Y全满。

c) 类似heapify的过程,只不过每次与右边和下边的元素进行比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Youngify(Y, i, j)
    i2 = i
    j2 = j
    if i + 1 <= Y.m && Y[i + 1, j] < Y[i, j]
        i2 = i + 1
    if j + 1 <= Y.n && Y[i2, j + 1] < Y[i2, j]
        j2 = j + 1
    if i != i2 or j != j2
        exchange Y[i, j] with Y[i2, j2]
        Youngify(Y, i2, j2)
 
Extract-Min(Y)
    min = Y[1, 1]
    Y[1, 1] = Y[Y.m, Y.n]
    Y[Y.m, Y.n] = INFINITE
    Youngify(Y, 1, 1)
    return min

最坏情况下左上的元素要移到右下,共m+n-2次交换,所以时间复杂度为O(m+n)

d) 类似于堆的insert,每次与上方和左方的元素比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Decrease(Y, i, j, key)
    Y[i, j] = key
    i2 = i
    j2 = j
    if i > 1 && Y[i - 1, j] > Y[i, j]
        i2 = i - 1
    if j > 1 && Y[i, j - 1] > Y[i2, j]
        j2 = j - 1
    if i != i2 or j != j2
        key = Y[i, j]
        Y[i, j] = Y[i2, j2]
        Decrease(Y, i2, j2, key)
 
Insert(Y, key)
    Decrease(Y, Y.m, Y.n, key)

最坏情况下右下的元素要移到左上,共m+n-2次交换,所以时间复杂度为O(m+n)

e)

1
2
3
4
5
6
7
8
9
10
11
Sort(A)
    n = Sqrt(A.length)
    Y.m = n
    Y.n = n
    for i = 1 to n
        for j = 1 to n
            Y[i, j] = INFINITE
    for i = 1 to A.length
        Insert(Y, A[i])
    for i = 1 to A.length
        A[i] = Extract-Min(Y)

第7行共执行n2n2次,复杂度为O(n2)O(n2),第9行共执行n2n2次,复杂度为O(n+n)O(n2)=O(n3)O(n+n)O(n2)=O(n3),第11行共执行n2n2次,复杂度为O(n+n)O(n2)=O(n3)O(n+n)O(n2)=O(n3),所以总时间复杂度为O(n3)O(n3)

f) 特别感谢@逍遥承影的更正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Contains(Y, key, i, j)
    if i > Y.m || j < 1
        return false
    if Y[i, j] == key
        return true
    ii = i
    jj = j
    if i == Y.m || Y[i + 1, j] > key
        jj = jj + 1
    if j == 1 || Y[i, j - 1] < key
        ii == ii + 1
    return Contains(Y, key, ii, jj)
 
Contains(Y, key)
    return Contains(Y, key, 1, Y.n)

最坏情况下需要从右上的元素检查到左下角的元素,每次向右或向下移动1,所以共需检查m+n次,时间复杂度为O(m+n)

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 win10系统安装失败怎么办 英雄联盟自动更新失败怎么办 英雄联盟老出错怎么办? lol下载0kb怎么办 手机md5校验失败怎么办 王者荣耀占内存怎么办 信用卡被暂停使用怎么办 宝宝爱涂鸦怎么办呢 大良老板欠薪怎么办 投影仪流明很低怎么办 户外涂鸦没有墙怎么办 word突然未响应怎么办 文档突然未响应怎么办 pr不自己播放怎么办 笔记本电脑空格键打字失灵怎么办 小孩受凉打喷嚏流鼻涕怎么办 头疼打喷嚏流鼻涕鼻塞怎么办 头疼打喷嚏想吐怎么办 感冒鼻塞头疼打喷嚏怎么办 孕期打喷嚏漏尿怎么办 孕妇打喷嚏漏尿怎么办 打喷嚏停不下来怎么办 鼻炎不停的打喷嚏怎么办 感冒了鼻子痒怎么办 怀孕鼻炎严重了怎么办 鼻炎不停打喷嚏流鼻涕怎么办 鼻子里面干痒怎么办 有鼻炎老打喷嚏怎么办 鼻子喷嚏打不停怎么办 鼻子痒想打喷嚏怎么办 鼻痒打喷嚏流鼻涕怎么办 原配找到公司了怎么办 西北游不吃羊肉怎么办 水培植物冬天该怎么办 车里味道太大怎么办 同房后有异味怎么办 房间里有异味怎么办 身上有孤臭味怎么办 炉灶打不着火怎么办 檀香木香味小了怎么办 郁金香种球发霉怎么办