堆排序

堆排序的时间复杂度是 O(nlgn) ,堆排序具有空间原址性,任何时候都只需要常数个额外的元素空间存储临时数据。

堆的介绍

(二叉)堆是一个数组,它可以被看成一个近似的完全二叉树。树上的每一个结点对应数组中的一个元素。除了最底层外,该树是完全充满的,而且从左向右填充。如下图:
这里写图片描述

表示堆数组A包括两个属性:A.length通常给出数组元素的个数,A.heap-size表示有多少个堆元素存储在该数组中。即虽然A[1..A.length]可能都存有数据,但只有A[1..A.heap-size]中存放的是堆的有效元素。这里 0A.heapsizeA.length 。树的根结点是A[1],这样给定一个结点的下标i, 我们很容易计算得到它的父结点、左孩子和右孩子的下标:

PARENT(i)
    return[i/2]

LEFT(i)
    return [2i]

RIGHT(i)
    return [2i+1]

上面的这个三个函数通常是以“宏”或“内联函数”的方式实现的。

二叉堆可以分为两种形式:最大堆和最小堆。
最大堆的性质是指除了根以外的所有结点都要满足:
A[PARENT(i)]A[i]

最小堆的性质是指除了根以外的所有结点都要满足:
A[PARENT(i)]A[i]
在堆排序算法中,我们使用的是最大堆。最小堆通常用于构造优先队列。

维护堆的性质

MAX-HEAPIFY是用于维护最大堆性质的重要过程。它的输入为一个数组A和一个下标i。在调用MAX-HEAPIFY的时候,我们假定根结点为LEFT(i)和RIGHT(i)的二叉树都是最大堆,但这时A[i]有可能小于其孩子,这样就违背了最大堆的性质。MAX-HEAPIFY通过让A[i]的值在最大堆中“逐级下降”,从而使得以下标i为根结点的子树重新遵循最大堆的性质。
MAX-HEAPIFY(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[largest]
    largest = r
if largest != i
    exchange A[i] with A[largest]
    MAX-HEAPIFY(A, largest)

一个实例过程如下:
这里写图片描述
上面伪代码中,第10行中的递归调用可能会使某些编译器产生低效的代码。所以可以用循环控制结构来取代递归,重写MAX-HEAPIFY代码
MAX-HEAPIFY(A, i)———-无递归调用

while(true)
    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[largest]
        largest = r
    if largest != i
        exchange A[i] with A[largest]
        i = largest
    else
        break

维护堆的时间复杂度为 T(n)=O(lgn) 也就是说对于一个树高为h的结点来说,MAX-HEAPIFY的时间复杂度为O(h)

建堆

我们可以用自底向上的方法利用过程MAX-HEAPIFY把一个大小为n=A.length的数组A[1..n]转换为最大堆。我们知道,子数组A[n/2+1..n]中的元素都是树的叶子结点。每个叶子结点都可以看成包含一个元素的堆。过程BUILD-MAX-HEAP对树中的其他结点都调用一次MAX-HEAPIFY。

BUILD-MAX-HEAP(A)

A.heap-size = A.length
for i = [A.length/2] downto 1
    MAX-HEAPIFY(A, i)

我们通过简单方法估算BUILD-MAX-HEAP(A)运行时间上界。每次调用MAX-HEAPIFY的时间复杂度为O(lgn),BUILD-MAX-HEAP需要O(n)次这样的调用。因此,总的时间复杂度是O(nlgn).当然这个上界虽然是正确的但不是渐近紧确的。BUILD-MAX-HEAP的时间复杂度渐近紧确的上界为O(n).
建堆的一个实例过程如下:
这里写图片描述

堆排序算法

初始时候,堆排序算法利用BUILD-MAX-HEAP将输入数组A[1..n]建成最大堆,其中n=A.length。因为数组中的最大元素总在根结点A[1]中,通过把它与A[n]进行互换,我们可以让该元素放到正确的位置。这时候,如果我们从堆中去掉结点n(这一操作可以通过减少A.heap-size的值来实现),剩余的结点中,原来根的孩子结点仍然是最大堆,而新的根结点可能会违背最大堆的性质。为了维护最大堆的性质,我们要做的是调用MAX-HEAPIFY(A, 1),从而在A[1..n-1]上构造一个新的最大堆。堆排序算法会不断重复这一过程,直到堆的大小从n-1降到2.
HEAPSORT(A)

BUILD-MAX-HEAP(A)
for i = A.length downto 2
    exchange A[1] with A[i]
    A.heap-size = A.heap-size - 1
    MAX-HEAPIFY(A, 1)

堆排序算法的时间复杂度为O(nlgn)
如下为一个堆排序的实例:
这里写图片描述

优先队列

堆排序是一个优秀的算法,但是在实际应用中,快速排序的性能一般要优于堆排序。尽管如此,堆这一结构仍然有很多应用。比如:作为高效的优先队列。和堆一样,优先队列也有两种形式:最大优先队列和最小优先队列。

优先队列(priority queue)是一种用来维护由一组元素构成的集合S的数据结构体。其中的每一个元素都一个相关的值,称为关键字(key)。一个最大优先队列支持一下操作:
INSERT(S, x): 把元素x插入集合S中,这一操作等价于 S=S{x}
MAXIMUM(S):返回S中具有最大关键字的元素
EXTRACT-MAX(S): 去掉并返回S中的具有最大关键字的元素
INCREASE-KEY(S, x, k): 将元素x的关键字值增加到k, 这里假设k的值不小于x的原关键字值.

用途:
最大优先队列的应用很多,其中一个就是在共享计算机系统的作业调度。
最小优先队列可以被用于基于事件驱动的模拟器。

HEAP-MAXIMUM(A)

    return A[1]

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-HEAPIFY(A, 1)
return max 

HEAP-EXTRACT-MAX的时间复杂度 T(n)=O(lgn)

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时间复杂度为O(lgn), 它的一个实例如下图:
这里写图片描述

MAX-HEAP-INSERT(A, key)

A.heap-size = A.heap-size + 1
A[A.heap-size] = -∞
HEAP-INCREASE-KEY(A, A.heap-size, key)

MAX-HEAP-INSERT时间复杂度为T(n)=O(lgn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值