《算法导论3rd第六章》堆排序

前言

堆排序也是O(n lg n)的排序算法。jre中PriorityQueue(优新队列)的底层原理就基于它的。

(二叉)堆是一个数组,它可以被看成一个近似的完全的二叉树。

 即数组中的父子如下关系

PARENT(i)
    return i/2
LEFT(i)
    return 2*i
RIGHT(i)
    return 2*i+1

二叉堆有两种形式,最大堆和最小堆。对于这两种形式,我们能且仅能确定的是根元素分别是数组中的最大值最小值。

练习

1-1 

元素最少的情况是最底层只有一个叶子,即2^h;元素最多的情况是整棵树是满的,即2^{h+1}-1

1-2

设高度为h,那么可知

h = \lfloor \lg n \rfloor

1-3

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

1-4

最小元素必然在最大堆的叶子节点上

1-5

1-6

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

         23
    17        14
  6    13   10   1
5  7  12

1-7

设堆的高度为h,那么除去最底层的结点的数量为2^h = 2^{h+1}/2个,所以底层结点的下标为2^{h+1}/2 + 1, 2^{h+1}/2 + 2 \cdots ,n,即\lfloor n/2 \rfloor + 1, \lfloor n/2 \rfloor + 2, \cdots , n

保持堆的性质

MAX-HEAPIFY是对最大堆进行操作的最大子程序,其输入为数组A和下标i, 假定根节点为LEFT(i)和RIGHT(i)的二叉树都是最大堆,但此时A[i]可能小于其子女违反最大堆性质。通过让A[i]的值在最大堆中逐级下降,从而使得以下标i为根的子树为最大堆。

MAX-HEAPITY(A, i)
    l=LEFT(i)
    r=RIGHT(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[i]
        largest=r
    if largest!=i
        exchange A[i] with A[largest]
        MAX-HEAPITY(A, largest)


 

练习

2-1 

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

    3
 10    1
8  9  0
---------
    10
  3    1
8  9  0
---------
   10
 9     1
8  3  0

2-2

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

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)

2-3

不会有任何改变。

2-4

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

2-5

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

2-6

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

建堆

我们可以用自底向上的方法利用过程MAX-HEAPITY把一个大小为n=A.length的数组A[1, n]转换为最大堆。

BUILD-MAX-HEAP(A)
    A.heap_size=A.length
    for i=A.length/2 downto 1
        MAX-HEAPITY(A, i)

练习

3-1

         5
    3        17
 10   84   19   6
22 9
------------------
         5
    3        17
 22   84   19   6
10 9
------------------
         5
    3        19
 22   84   17   6
10 9
------------------
         5
    84       19
 22    3   17   6
10 9
------------------
         84
    22       19
 10    3   17   6
5  9

3-2

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

3-3

(略)

堆排序算法

HEAPSORT 不断的让A[1] 与 A[A.length]交换, 并缩小A数组-1来移出A[A.length]出堆,再通过MAX-HEAPITY调整堆性质。最终将得到一个有顺序的数组

HEAPSORT(A)
    BUILD-MAX-HEAP(A)
    for i=A.length downto 2
        exchange A[i] with A[1]
        A.heap_size=A.heap_size-1
        MAX-HEAPITY(A, 1)

练习

 4-1

竖线表示堆的末尾

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

4-2

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

4-3

  • 当序列已经有序时,建堆的时候每次调用Max-Heapify都要进行最大次数的交换和比较,时间为O(n)。之后排序的时间为O(n \lg n),所以总时间为O(n \lg n)
  • 当序列为递减时,建堆的时候每次调用Max-Heapify无交换,比较共n/2次,所以时间为O(n)。之后排序的时间为O(n \lg n),所以总时间为O(n \lg n)

4-4

当排序阶段每次调用Max-Heapify时都进行最大次数的交换时,总的交换次数为: T \ge \sum_{i=2}^{n} {\lfloor \lg n \rfloor} = \Theta(n \lg n) = \Omega (n \lg n)

4-5

(略)

优先队列

优先队列(priority queue)是一种用来维护由一个元素构成的集合S的数据结构,其中的每一个元素都有一个相关的值,成为关键字(key),一个最大优先队列支持以下操作:

  • INSERT(S, x): 把元素x插入集合S中。
  • MAXIMUM(S): 返回S中具有最大关键字的元素。
  • EXTRACT-MAX(S): 去掉并返回S中的具有最大关键字的元素。
  • INCREASE-KEY(S, x, k): 将元素x的关键字值增加到k,这里假设k的值不小于x的原关键字值
MAXIMUM(A)
     return A[1]

// 拿出头节点返回,并让最尾结点占头节点
// 使用MAX-HEAPITY调节堆性质
HEAP-EXTRACT-MAX(A)
    if A.heap_size<1
        error "heap underflow"
    max=A[1]
    A[1]=A[A.heap_size]      
    A.heap_size=A.heap_size-1
    MAX-HEAPITY(A, 1)           
    return max

// 将i位置的值,使用key替换
// key > A[i], 就只需自下向上
HEAP-INCREASE-KEY(A, i, key)
    if key<A[i]
        error "new key is smaller than current key"
    A[i]=key
    while i>1 and A[PARENT(i)]<A[i]
        exchange A[i] with A[PARENT(i)]
        i=PARENT(i)

// 在数据后面新开一空间,初始化最小值
// 使用HEAP-INCREASE-KEY 将最小值用key替换,间接实现新增结点功能
MAX-HEAP-INSERT(A, key)
    A.heap_size=A.heap_size+1
    A[A.heap_size]=-inf
    HEAP_INCREASE_KEY(A, A.heap_size, key)

练习

 5-1

          15
    13           9
  5    12     8    7
4  0  6  2  1
---------------------
          1
    13           9
  5    12     8    7
4  0  6  2
---------------------
          13
    1            9
  5    12     8    7
4  0  6  2
---------------------
          13
    12           9
  5    1      8    7
4  0  6  2
---------------------
          13
    12           9
  5    6      8    7
4  0  1  2

5-2

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

5-3

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)

5-4

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

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]都满足堆的性质

5-6

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

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

5-7

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

 5-8

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

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

5-9

   (略)

主要参考

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

猜你喜欢

转载自blog.csdn.net/y3over/article/details/9340951
今日推荐