《算法导论》笔记——第6章 堆排序

第6章 堆排序
本章主要介绍了堆的基本知识和几个基本操作过程


(二叉)堆数据结构是一种数组对象,它可以被看作是一棵完全二叉树。heap—size[A]即堆的大小是已知的,树的根结点是A[1],某个i结点,它的父结点PARENT(i)为,左儿子LEFT(i)和右儿子RIGHT(i)的下标可以简单地计算出来:
PARENT(i) :return ⌊i / 2⌋
LEFT(i) :return 2i
RIGHT :return 2i + 1

LEFT过程可以通过左移一位的位运算(相当于乘2)来计算;RIGHT过程可以通过左移一位的位运算(相当于乘2)来计算,并在低位加一;PARENT过程可以通过右移一位的位运算(相当于除以2)来计算。
二叉堆分为两种,分别是最大堆(大顶堆)和最小堆(小顶堆)。在最大堆中除根以外的每个结点i都有,
A[ PARENT(i) ] ≥ A(i),即所有的父结点必须大于等于它的左右子结点,值最大的结点就是根结点;在最小堆中除根以外的每个结点i都有,A[ PARENT(i) ] ≤ A[i]。即所有的父结点都小于等于它的左右子结点,根结点的值最小。

几个基本操作过程及在排序算法和优先级队列中的使用
MAX-HEAPIFY过程,其运行时间是Ο(lgn),是保持最大堆性质的关键;
BUILD-MAX-HEAP过程,其运行时间是Ο(n),可以在无序的输入数组基础上构造出最大堆;
HEAP-SORT过程,运行时间是Ο(nlgn),对一个数组原地进行排序(只有常数个元素存储在数组以外)
MAX-HEAP-INSERT,HEAP-EXTRACT-MAX,HEAP-INCREASE-KEY,HEAP-MAXIMUM过程的运行时间是Ο(lgn),这些操作保证了堆结构可以作为优先级队列使用。

保持最大堆性质(MAX-HEAPIFY过程)
对于某个结点i来说,以LEFT(i)和RIGHT(i)为根的两颗二叉树都是最大堆,但有可能A[i]的值小于它的子女LEFT(i)和RIGHT(i)的值,这就违反了最大堆的性质。所以 MAX-HEAPIFY这个操作就是用来使这种 i 结点的值“下降”,或者说让i跟最大的子i结点交换值,确保它的值大于等于它的子结点,使以i为根的子树成为最大堆,保持最大堆的性质。

MAX-HEAPIFY(A, i)
1. l <-- LEFT(i)
2. r <-- RIGHT(i)
3. if l ≤ heap-size[A] and A[l] ≥ A[i]
4. 	then largest <-- l
5. 	else largest <-- i
6. if r ≤ heap-size[A] and A[r] ≥ A[largest]
7.	then largest <-- r
8. if largest ≠ i
9.	then exchange A[i] and A[largest]
10. 	MAX-HEAPITY(A, largest) 

在这个操作中,其运行时间是对A[i],A[LEFT(i)],A[RIGHT(i)]的调整所用时间Θ(1),再加上对以i的某个子结点为根的子树递归调用MAX-HEAPIFY所需的时间。i结点的子树大小至多为2n/3(最坏情况发生在最底层恰好半满的时候),那么MAX-HEAPIFY的运行时间可由下式描述:T(n) ≤ T(2n/3) + Θ(1),根据主定理,
a=1, b=3/2, n^log(b)a = n ^0, 属于情形2,T(n) = Ο( n ^0 lgn ) = Ο(lgn)。所以MAX-HEAPIFY的运行时间是
Ο(lgn)。

对于 “i结点的子树至多为2n/3” 的证明:
最坏情况发生在最底层恰好半满,那么假设左子树比右子树在对底层的结点数多,并设左子树在最底层的结点数为k,已知所有的结点数是n,那么我们设想右子树在最底层也有k个结点,那么这棵树就是完全二叉树了,总的结点是n+k;
再假设高为h,最底层的高度是0,那么从下往上1-h的结点数就是(2 ^h) -1,最底层即高度为0的结点数为2 ^h,所以总的结点数n+k = (2 ^h) - 1 + 2 ^h ;
而我们知道k是左子树最底层的结点数,那么2k就是最底层即高度为0的所有结点数,上面算出来是2 ^h,所以,2k = 2 ^h,而n+k = (2 ^h) - 1 + 2 ^h,把2k = 2 ^h 代入得,n+k = 4k -1,n = 3k -1,k = (n+1) / 3;
完全二叉树总结点数为n+k,那么把根结点去掉再除以2,就可以得到左子树结点数为(n+k-1) / 2,由上面得, (n+k-1)/2 = (4k-2)/2 = 2k-1 = 2(n+1) /3 - 1 = (2n -1)/3 < 2n/3,所以结点数为n的 i 结点的子树大小至多为2n /3。

建堆(BUILD-MAX-HEAP过程)
我们可以自底向上地用MAX-HEAPIFY来将一个数组A[1…n](n=length[A])变成一个最大堆。因为子数组 A[(⌊n/2⌋ + 1) , (⌊n/2⌋ + 2) … n]中的元素都是树中的叶子(即最底层的元素),我们通过对这些元素的父结点调用MAX-HEAPIFY,之后对这些元素的父结点的父结点调用,如此类推,直到根结点,就可以实现建立最大堆了。

BUILD-MAX-HEAP(A)
1.heapsize[A] <-- length[A]
2.for i <-- ⌊length[A]/2⌋  downto 1
3.		do MAX-HEAPIFY(A, i)

我们知道堆的高h=⌊lgn⌋,而MAX-HEAPIFY的运行时间是Ο(lgn),我们也可以这样说,MAX-HEAPIFY作用于一个高度为h的结点所需运行的时间是Ο(h)。并且在任意高度h上,至多有 ⌈n/2 ^(h+1)⌉个结点,所以BUILD-MAX-HEAP的运行时间为作用每一个高度h上每一个结点所需运行时间之和,即T(n)=Σ⌈n/2 ^(h+1)⌉ Ο(h) (h = 0 … lgn),据计算T(n) = Ο(n),所以BUILD-MAX-HEAP的运行时间是Ο(n)。

堆排序算法

HEAPSORT(A)
1. BUILD-MAX-HEAP(A)
2. for i <-- length[A] downto 2
3. 	do exchange A[1] <--> A[i]
4. 	heap-size[A] <-- heap-size[A] - 1
5. 	MAX-HEAPIFY(A, 1)

HEAPSORT过程的时间代价为Ο(nlgn),其中调用BUILD-MAX-HEAP的时间是Ο(n),n-1次MAX-HEAPIFY调用中每一次的时间代价是Ο(lgn)。

优先级队列
优先级队列可以用堆来实现。优先级队列分为两种,分别是最大优先级队列和最小优先级队列。优先级队列是一种用来维护由一组元素构成的集合S的数据结构,这一组元素中的每一个都有关键字key。一个最大优先级队列支持一下操作:
INSERT(S, x):把元素x插入集合S;
MAXIMUM(S):返回S中具有最大关键字的元素;
EXTRACT-MAX(S):去掉并返回S中的具有最大关键字的元素;
INCREASE-KEY(S, x, k);将元素x的关键字的值增加到k,这里k不能小于x的原关键字的值。
最大优先级队列的一个应用是在一台分时计算机上进行作业调度。这种队列对要执行的各作业及它们之间的相对优先关系加以记录,当一个作业完成或中断时,用EXTRACT-MAX操作从所有等待的作业中,选择出具有最高优先级的作业。在任何时候,一个新的作业都可以用INSERT加入到队列中去。
最小优先级队列可被用在基于事件驱动的模拟器中。在这种应用中,队列中的各项是要模拟的事件,每一个都有一个发生时间作为其关键字。时间模拟要按照各事件发生事件顺序进行,所以在模拟程序的每一步都用EXTRACT-MIN来选择下一个模拟事件。当一个新事件产生,使用INSERT将其放入队列中。

HEAP-MAXIMUM(A)
1. return A[1]
HEAP-EXTRACT-MAX(A)
2. if heap-size[A] < 1
3.		then error"heap underflow" 堆下溢了
4. max <-- A[1]
5. A[1] <-- A[heap-size[A]]
6.  heap-size[A] <-- heap-size[A] - 1
7.  MAX-HEAPIFY(A, 1)
8.  return max 
HEAP-INCREASE-KEY(A, i, key)
1 if key < A[i] 
2 	then error"new key is smaller than current key"
3 A[i] <-- key
4 while i > 1 and A[PARENT(i)] < A[i]
5	do exchange A[i] <--> A[PARENT(i)]
6		i <-- PARENT(i)
MAX-HEAP-INSERT(A, key)
1 heap-size[A] <-- heap-size[A] + 1
2 A[heap-size[A]] <-- -∞  
3 HEAP-INCREASE-KEY(A, heap-size[A], key) 

将一个很小的值(这里用无穷小表示)赋给A[heap-size[A]],然后用HEAP-INCREASE-KEY可以将关键字大小为key的元素插入到堆中。
上面四种操作的运行时间都为Ο(lgn)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值