【算法导论】6.堆排序

堆排序时间复杂度为O(nlgn), 空间复杂度为O(1).

应用:最大堆用于堆排序,最小堆用于构造优先队列。


6.1堆

二叉堆是一个数组,可被看作一个近似的完全二叉树。除了最底层外,该树是完全满的。

所以可以计算得到父结点、左孩子和右孩子的下标。

Parent(i)
    return i/2

Left(i)
    return 2i

Right(i)

    return 2i+1.


6.2维护堆的性质

Max-Heapify通过使A[i]的值在最大堆中逐级下降,使以下标i为根结点堆子树重新遵循最大堆的性质

//伪代码
Max-Heapify(A,i)//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[largest]>A[i]
        largest=r;
    if largest!=i
        exchange A[i] with A[largest];
    Max-Heapify(A,largest);
}

这段代码通过将下标为i的值与其左孩子和右孩子的值进行比较,并将最大下标存在largest中。如果A[i]为最大值,程序结束。如果A[i]小于其左孩子或者右孩子,那么,首先将A[i]与A[largest]进行交换,将最大值换到根(也就是A[i]的位置)。此时,本来的A[i]存在A[largest]的位置,要考虑这时以该结点(换下来的A[i])为根结点的子树是否满足最大堆的性质,所以需要递归调用Max-Heapify.

维护一个堆的时间复杂度为O(lgn),即O(h),h为树高。实际上就是,在最坏情况下,最小的结点开始存放在根结点,需要换到最底部,走过的长度就是树高。每一次交换的时间复杂度为常数。


6.3建堆

Build-Max-Heap(A)
{
    A.heap-size=A.length;
    for i=A.length/2 down 1
        Max-Heapify(A,i);
}

以上代码通过对每个非叶子结点调用Max-Heapify,把一个大小为n=A.length的数组转换为最大堆。

时间复杂度为O(n),即把一个无序数组构造成最大堆的时间为O(n)

将上述代码修改为Build-Min-Heap,即将第三行修改为Min-Heapify即可构造成一个最小堆。


6.4堆排序算法

初始化堆:利用Build-Max-Heap将输入A[1..n]构建成最大堆。

将根结点的元素放到正确位置:由于构造的是最大堆,整个数组中最大的元素一定在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--;
        Max-Heapify(A,1);
    }
}

时间复杂度为O(nlgn),因为每次调用Build-Max-Heap的时间复杂度为O(n),而n-1次调用Max-Heapify,每次的时间为O(lgn)。


6.5优先队列

优先队列分为:最大优先队列和最小优先队列。优先队列是一种用来维护由一组元素构成的集合S的数据结构,其中每个元素都有一个相关的值,称为关键字。

优先队列的操作:

Insert(S,x):把元素x插入集合S。

Maximum(S):返回S中具有最大键值的元素。

Extract-Max(S):去掉并返回S中具有最大键值的元素。

Increase-Key(S,x,k):将元素x的值增加到k。

最大优先队列的应用:共享计算机系统的作业调度,最大优先队列用于记录将要执行的各个作业以及它们的相对优先级。

最小优先队列的应用:被用于基于事件驱动的模拟器。

用堆来实现优先队列:

Maximum: 该代码在O(1)时间内实现Maximum操作。(前提是最大堆已经建好)

Heap-Maximum(A)
    return A[1];

Extract-Max:(类比于HeapSort)

时间复杂度为O(lgn). 将最大值保存在max中,把A[A.heap-size]的值换到A[1],然后重新维护一个最大堆。

Heap-Extract-Max(A)
{
    if A.heap-size<1
        return error;
    max=A[1];
    A[1]=A[A.heap-size];
    A.heap-size--;
    Max-Heapify(A,1);
    return max;
}

Increase-Key:在将元素A[i]值从x增大到k时,可能会使其违反最大堆的性质。所以需要在从当前结点到根结点到路径上,为新增的关键字寻找恰当的插入位置。在进行Increase-Key的操作中,当前元素通过不断与其父结点进行比较,判断是否需要与父结点交换位置,直到当前元素的值小于其父结点。

时间复杂度为O(lgn).在更新关键字值时,更新结点到根结点的路径长度为O(lgn).

Heap-Increase-Key(A,x,key)
{
    if key<A[i];
        return error;//这里需要key值大于A[i],即x
    A[i]=key;
    while i>1 and A[parent(i)]<A[i]
    {    
        exchange A[i] with A[parent(i)];
        i=parent(i);
    }
        
}

Insert:输入为要被插入到最大堆中的A中的新元素的关键字。首先通过增加一个关键字为极小值的叶结点来扩展最大堆。然后调用Heap-Increase-Key为新结点设置对应的关键字,同时保持最大堆的性质。时间复杂度为O(lgn).

Max-Heap-Insert(A,key)
{
    A.heap-size++;
    A[A.heap-size]=-10000000;
    Heap-Increase-Key(A,A.heap-size,key);
}

在包含一个n个元素的堆中,所有优先队列的操作的时间复杂度都为O(lgn).

猜你喜欢

转载自blog.csdn.net/Chen_Swan/article/details/85867635