算法导论 第六章:堆排序 笔记(堆、维护堆的性质、建堆、堆排序算法、优先级队列、堆排序的代码实现)

版权声明:站在巨人的肩膀上学习。 https://blog.csdn.net/zgcr654321/article/details/83065338

堆排序(heapsort) 像合并排序而不像插入顺序,堆排序的运行时间为O(nlgn) 。像插入排序而不像合并排序,它是一种原地( in place) 排序算法:在任何时候,数组中只有常数个元素存储在输入数组以外

堆:

(二叉)堆数据结构是一种数组对象,它可以被视为一棵完全二叉树。树中每个结点与数组中存放该结点值的那个元素对应。树的每一层都是填满的,最后一层可能除外(最后一层从一个结点的左子树开始填) 。

堆存在以下性质:

根节点:A[1]

父结点:Parent[i] = i/2

左子节点:Left[i] = 2i

右子节点:Right[i] = 2i + 1

数组长度为:A.length

数组中有效数据长度为:A.heap-size

堆分为最大堆和最小堆,在最大堆中 Parent[i] >= A[i],在最小堆中 Parent[i]  <= A[i]

表示堆的数组A是一个具有两个属性的对象:length[A]是数组中的元素个数, heap-size[A]是存放在A中的堆的元素个数。即虽然A[1. .length[A]]中都可以包含有效值,但A[heap-size [A]]之后的元素都不属于相应的堆,

二叉堆有两种:最大堆和最小堆(小根堆)。在堆排序算法中,我们使用的是最大堆。最小堆通常在构造优先队列时使用

下面几个是堆排序中的基本过程:

MAX-HEAPITY: 其时间复杂度为O(lgn), 它是维护最大堆性质的关键。

BUILD-MAX-HEAP: 具有线性时间复杂度,功能是从无序的输入数据数组中构造一个最大堆。

HEAPSORT: 其时间复杂度为O(nlgn),功能是对一个数组进行原址排序。

MAX-HEAP-INSERT/HEAP-EXTRACT-MAX/HEAP-INCREASE-KEY/HEAP-MAXIMUM: 时间复杂度为O(nlgn),功能是利用堆实现一个优先队列。

维护堆的性质:

MAX-HEAPIFY 是对最大堆进行操作的重要的子程序。调节父结点与子结点位置,使他们满足最大堆的大小关系。将A[i]的值在最大堆中“逐级下降”,从而使以下标i为根结点的子树重新遵循最大堆的性质。时间复杂度为O(lgn)。

伪代码:

MAX-HEAPIFY(A,i)
l <- Left[i]
r <- Right[i]
if l <= heap-size[A] and A[l] > A[i]
    then largest <- l
    else largest <- i
if r <= heap-size[A] and A[r] > A[lagest]
    then lagest <- r
if i != largest //当根结点不是最大值时
    then exchage A[i] <-> A[lagest]
        MAX-HEAPIFY(A,lagest)
        //交换位置后,以该结点为根的子树可能违法最大堆的性质,所以需要递归调用

如:

下图描述了MAX- HEAPIFY的过程。在算法的每一步里,从元素A[i], A[LEFT(i)] 和A[RIGHT(i)] 中找出最大的,并将其下标存在largest 中。如果A[i]是最大的,则以i为根的子树己是最大堆,程序结束。否则,i的某个子结点中有最大元素,则交换A[i]和A[largest] , 从而使i及其子女满足堆性质。下标为largest 的结点在交换后的值是A[i] , 以该结点为根的子树又有可能违反最大堆性质。因而要对该子树递归调用MAX- HEAPIFY。

建堆:

建堆(BUILD-MAX-HEAP):将无序的数组转换为最大堆。

伪代码:

BUILD-MAX-HEAP(A)
heap-size[A] <- length[A] 
for i <- [length[A]/2] downto 1 
//此处i的值为length[A]/2向下取整的值,因为A[length[A]/2+1...length[A]]为叶子结点,没有子结点,所以只需考虑前半部分的值  
    do MAX-HEAPIFY(A,i)
    

过程BUILD-MAX-HEAP对树中的每一个其他结点都调用一次MAX-HEAPIFY。时间复杂度为 O(n)。

如:

堆排序算法:

堆排序算法(HEAPSORT):建堆后,数组中的数据并不是有序的,但最大值一定位于根结点A[1],根据此性质,可以进行排序。时间复杂度为O(nlogn)。

伪代码:

HEAPSORT(A)
    BUILD-MAX-HEAP(A)
    for i <- length[A] downto 2 
    //不断将最大值放在堆中最后一个元素位置,对应就是数组中由后向前进行排序
        do exchange A[1] <-> A[i] 
        //将最大元素置后
        heap-size[A] <- heap-size[A] - 1 
        //有效长度减1,表示该元素已经归位
        MAX-HEAPIFY(A,1) 
        //因为将原堆末尾元素提到了根结点,所以需要对根结点重新执行MAX-HEAPIFY维护堆的性质。

如:

优先级队列:

优先队列(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的原关键字值。

伪代码:

HEAP-MAXIMUM(A)
    return A[1]
HEAD-EXTRACT-MAX(A)
    if heap_size[A]<1
        then error"heap underflow"
    max <- A[1]
    A[1] <- A[heap_size[A]]
    heap_size[A] <- heap_size[A]-1
    MAX-HEAPIFY(A,1)
    return MAX
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]
        do exchange A[i] <-> A[PARENT(i)]
            i <- PARENT(i)

MAX-HEAP-INSERT(A, key)
    heap_size[A] <- heap_size[A]+1
    A[heap_size[A]] <- -inf
    HEAP_INCREASE_KEY(A, heap_size[A], key)

程序HEAP-MAXIMUM 用O(1) 时间实现了MAXIMUM 操作。HEAP-EXTRACT-MAX 的运行时间为O(lgn), 因为它除了时间代价为O(lgn) 的MAXHEAPIFY外,只有很少的固定量的工作。

程序HEAP-INCREASE-KEY实现了INCREASE-KEY 操作。在优先级队列中,关键字值需要增加的元素由对应数组的下标i来标识。该过程首先将元素A[i] 的关键字值更新为新的值。由于增大A[i] 的关键字可能会违反最大堆性质,所以过程中采用了类似于INSERTION-SORT的插入循环(第5 -7 行)的方式,在从本结点往根结点移动的路径上,为新增大的关键字寻找合适的位置。在移动的过程中,此元素不断地与其父母相比,如果此元素的关键字较大,则交换它们的关键字且继续移动。当元素的关键字小于其父母时,此时最大堆性质成立,因此程序终止。

MAX-HEAP-INSERT 实现了INSERT 操作。它的输入是要插入到最大堆A中的新元素的关键字。这个程序首先加入一个关键字值为负无穷的叶结点来扩展最大堆,然后调用HEAP-INCREASE- KEY来设置新结点的关键字的正确值,并保持最大堆性质。在包含n个元素的堆上, MAX-HEAP-INSERT 的运行时间为O(lgn) 。

如:

堆排序的代码实现:

C/C++代码如下:

猜你喜欢

转载自blog.csdn.net/zgcr654321/article/details/83065338