前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >堆排序与优先队列

堆排序与优先队列

作者头像
hotarugali
发布2022-03-01 16:37:21
3010
发布2022-03-01 16:37:21
举报

【注】参考自教材「算法导论」。

1. 堆

1.1 定义

(二叉)堆物理上是一个数组,逻辑上是一棵完全二叉树,树上的每一个结点对应数组中的一个元素。 设表示堆的数组 A,其包含两个属性:

  1. A.length 给出数组元素的个数。
  2. A.heapsize给出有多少个堆元素存储在该数组中。

1.2 性

从根结点编号为 1 开始,同一深度的结点自左往右、不同深度的结点自上往下,对堆上的结点编号(即逐层从左往右进行编号),结点编号对应数组 A 中元素的下标。

  • n 个元素的堆的高度为 ⌊lg⁡n⌋   因为堆是一棵完全二叉树,设堆高为h,则 \begin{array}{c} 2^h \leq n \leq 2^{h+1} - 1 \Rightarrow \lg (n+1) - 1 \leq h \leq \lg n \\ \Rightarrow \lg n - 1 \lt \lg (n+1) - 1 \leq h \leq \lg n \end{array}

因为 h为整数,故 h=⌊lg⁡n⌋

  • 非根结点 i的双亲结点的编号为

\begin{array}{c} parent[i] = \lfloor i/2 \rfloor \end{array}

  • 非叶结点 i的左右孩子结点编号分别为

\begin{array}{c} left[i] = 2i \\ right[i] = 2i + 1 \end{array}

  • 当用数组表示存储 n 个元素的堆时,叶结点编号分别是 \lfloor n/2 \rfloor + 1, \lfloor n/2 \rfloor + 2, \cdots, n

证明   因为 n 是编号最大的结点,故 n 肯定是叶结点,且是编号最大的叶结点。故 n 的双亲 parent[n] = \lfloor n/2 \rfloor也是编号最大的非叶结点。故区间 (parent[n], n]范围内的结点编号都是叶结点。即 \lfloor n/2 \rfloor + 1, \lfloor n/2 \rfloor + 2, \cdots,

  • 对于包含 n 个元素的堆最多包含 \lceil n/2^{h+1} \rceil 个高度为 h 的结点。

证明 归纳法证明如下:

  • 对于高度为 0 的结点(即叶结点),因为堆是一棵完全二叉树,故叶结点个数 l 满足 l_0 = \lfloor (n+1)/2 \rfloor = \lceil n/2^{0 + 1} \rceil
  • 假设对于高度为 h 的结点,满足结点数l_h = \lceil n/2^{h+1} \rceil,由于堆是一棵完全二叉树,故对于高度为 h+1的结点,有

\begin{array}{c} l_{h+1} = \lceil l_h / 2 \rceil = \lceil n / 2^{(h+1)+1} \rceil \end{array}

堆可分为最大堆和最小堆,二者分别满足最大堆性质和最小堆性质。

  • 最大堆性质:除了根结点以外的所有结点 i 满足

\begin{array}{c} A[parent(i)] \geq A[i] \end{array}

  • 最小堆性质:除了根结点以外的所有结点 i满足

\begin{array}{c} A[parent(i)] \leq A[i] \end{array}

2. 堆排序

堆排序是原址的、不稳定的。以下仅以最大堆为例。

2.1 maxHeapify

该过程用于维护最大堆性质。即根据输入的结点 iii 维护以 iii 为根的堆的最大堆性质。

代码语言:javascript
复制
// 伪代码
maxHeapify(A, i) {
    l = left(i)     // 左孩子结点
    r = right(i)    // 右孩子结点
    largest = i     // 记录左孩子、右孩子、根三者中值最大的一者
    if l ≤ A.heapsize and A[l] > A[largest]
        largest = l
    if r ≤ A.heapsize and A[r] > A[largest]
        largest = r
    if largest != i
        swap(A[i], A[largest])
        maxHeapify(A, largest)
}

由于堆高为 ⌊lg⁡n⌋,故 maxHeapify 复杂度为 O(lg⁡n)

2.2 buildMaxHeap

采用自底向上的方法,利用 maxHeapify 过程,将数组 A 构建为一棵最大堆。即从最小的非空子树(内部结点)开始,由于最小非空子树最少两个元素、最多三个元素,故 maxHeapify 后一定是一棵最大堆,然后逐步向上对更大的子树递归利用 maxHeapify 构建最大堆。 根据上文堆的性质可知,堆的叶结点编号分别是 \lfloor n/2 \rfloor + 1, \lfloor n/2 \rfloor + 2, \cdots, n ,故非叶结点的编号为 1,2, \cdots, \lfloor n/2 \rfloor

代码语言:javascript
复制
// 伪代码
buildMaxHeap(A) {
    A.heapsize = A.length
    for i = ⌊A.heapsize/2⌋ downto 1 
        maxHeapify(A, i)
}

因为在高度为 h的树上执行 maxHeapify 过程的复杂度为 O(h),而由上文堆的性质可知,堆中高度为h 的结点数为 \lceil n/2^{h+1} \rceil,堆的高度为\lg n,故 buildMaxHeap 的复杂度为

\begin{array}{c} \sum^{\lfloor \lg n \rfloor}_{h = 0}\lceil n/2^{h+1} \rceil O(h) = O(n) \end{array}

2.3 heapSort

构建好最大堆后,值最大的元素在 A[1],通过将 A[1] 与 A[A.heapsize] 交换后,然后缩小 A.heapsize,并使用 maxHeapify 过程维护最大堆性质。如此往复,直到 A.heapsize = 2 时,数组 A 变为有序。

代码语言:javascript
复制
// 伪代码
heapSort(A) {
    buildMaxHeap(A)
    for i = A.length downto 2
        swap(A[1], A[i])
        A.heapsize = A.heapsize - 1
        maxHeapify(A, 1)
}

heapSort 的复杂度为 O(n + \lfloor \lg n \rfloor + \lfloor \lg (n-1) \rfloor + \cdots + \lfloor \lg 1 \rfloor) = O(n + \lg n!) = O(n\lg n)。

2.4 模板

代码语言:javascript
复制
#include <bits/stdc++.h>
using namespace std;

// 比较函数
template <typename T>
bool compare(const T & t1, const T & t2) {
    return t1 < t2;
}
// 最大堆
template <typename T>
struct MaxHeap {
    #ifndef _MAXHEAP_ 
    #define _MAXHEAP_
    #define ll long long
    #endif
    ll length;
    ll heapsize;
    MaxHeap() {}
    // 维护最大堆性质
    void maxHeapify(T *A, ll i, bool (*cmp)(const T &, const T &)) {
        ll l = 2*i, r = 2*i + 1;
        ll largest = i;
        if(l <= heapsize && cmp(A[largest], A[l])) {
            largest = l;
        }
        if(r <= heapsize && cmp(A[largest], A[r])) {
            largest = r;
        }
        if(largest != i) {
            swap(A[i], A[largest]);
            maxHeapify(A, largest, cmp);
        }
    }
    // 构建最大堆
    void buildMaxHeap(T *A, bool(*cmp)(const T &, const T &)) {
        heapsize = length;
        for(ll i = heapsize/2; i; --i) {
            maxHeapify(A, i, cmp);
        }
    }
    // 堆排序
    void heapSort(T *bA, T *eA, bool(*cmp)(const T &, const T &) = compare) {
        T *A = bA - 1;
        length = eA - bA;
        buildMaxHeap(A, cmp);
        for(ll i = length; i; --i) {
            swap(A[1], A[i]);
            heapsize--;
            maxHeapify(A, 1, cmp);
        }
    }
};

3. 优先队列

以下优先队列以最大堆为底层实现。

3.1 maxHeapifyUp

该过程用于从当前结点向根结点的路径上维护最大堆性质。

代码语言:javascript
复制
// 伪代码
maxHeapifyUp(A, i) {
    p = parent(i)   // 双亲结点
    if p > 0 and A[p] < A[i]
        swap(A[i], A[p])
        maxHeapifyUp(A, p)
}

由于堆高为 \lfloor \lg n \rfloor,故 maxHeapifyUp 复杂度为 O(\lg n)

3.2 maxHeapifyDown

该过程用于从当前结点向叶结点的路径上维护最大堆性质。

代码语言:javascript
复制
// 伪代码
maxHeapifyDown(A, i) {
    l = left(i)     // 左孩子结点
    r = right(i)    // 右孩子结点
    largest = i     // 记录左孩子、右孩子、根三者中值最大的一者
    if l ≤ A.heapsize and A[l] > A[largest]
        largest = l
    if r ≤ A.heapsize and A[r] > A[largest]
        largest = r
    if largest != i
        swap(A[i], A[largest])
        maxHeapifyDown(A, largest)
}

由于堆高为 \lfloor \lg n \rfloor,故 maxHeapifyDown 复杂度为O(\lg n)

3.3 模板

代码语言:javascript
复制
#include <bits/stdc++.h>
using namespace std;

#ifndef _PRIORITYQUEUE_
#define _PRIORITYQUEUE_
#define ll long long
#define MAXN 1000005

// 比较元素
template < typename T >
struct Compare {
    bool operator () (const T & a, const T & b) {
        return a < b;
    }
};
// 优先队列
template < typename T, typename F = struct Compare <T> >
struct PriorityQueue {
    ll heapsize;
    T A[MAXN];
    F compare;
    typedef T* iterator;
    // 构造函数
    PriorityQueue(): heapsize(0) {}
    // 向上维护最大堆性质
    void maxHeapifyUp(ll i) {
        ll p = i/2;
        if(p && compare(A[p], A[i])) {
            swap(A[p], A[i]);
            maxHeapifyUp(p);
        }
    }
    // 向下维护最大堆性质
    void maxHeapifyDown(ll i) {
        ll l = 2*i, r = 2*i + 1;
        ll largest = i;
        if(l <= heapsize && compare(A[largest], A[l])) {
            largest = l;
        }
        if(r <= heapsize && compare(A[largest], A[r])) {
            largest = r;
        }
        if(largest != i) {
            swap(A[i], A[largest]);
            maxHeapifyDown(largest);
        }
    }
    // 将元素压入优先队列中
    void push(T a) {
        A[++heapsize] = a;
        maxHeapifyUp(heapsize);
    }
    // 将元素弹出优先队列外
    T pop() {
        swap(A[1], A[heapsize]);
        --heapsize;
        maxHeapifyDown(1);
        return A[heapsize+1];
    }
    // 获取优先队列顶部元素
    T top() {
        return A[1];
    }
    // 获取优先队列大小
    ll size() {
        return heapsize;
    }
    // 判断优先队列是否为空
    bool empty() {
        return !heapsize;
    }
    // 清空优先队列
    void clear() {
        heapsize = 0;
    }
    // 获取优先队列首元素
    T* begin() {
        return &A[1];
    }
    // 获取优先队列尾元素
    T* end() {
        return &A[heapsize] + 1;
    }
};
#endif
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-08-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 堆
    • 1.1 定义
      • 1.2 性
      • 2. 堆排序
        • 2.1 maxHeapify
          • 2.2 buildMaxHeap
            • 2.3 heapSort
              • 2.4 模板
              • 3. 优先队列
                • 3.1 maxHeapifyUp
                  • 3.2 maxHeapifyDown
                    • 3.3 模板
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档