实现一个完全二叉堆

目录

  • 1 完全二叉堆的结构特点
  • 2 优先级队列与完全二叉堆
  • 3 实现
    • 3.1 用向量结构来实现
    • 3.2 上滤
    • 3.3 下滤
    • 3.4 程序

1 完全二叉堆的结构特点

完全二叉堆通常也简称为,是一种建立在完全二叉树基础上的数据结构(注意不要和内存中的堆弄混),同时,构建堆的完全二叉树还需要满足一个特点:任何叶子节点都不大于(小顶堆)/不小于(大顶堆)其父节点

2 优先级队列与完全二叉堆

通常,优先级队列是使用完全二叉堆实现的。那为什么要选择堆而不是其它数据结构实现优先级队列呢?因为堆结构在实现优先级队列这件事情上很有优势:

  • 从算法复杂度的角度,入队和出队操作的复杂度都是O(n),或许BBST(平衡二叉搜索树)也能做到这一点,不过获取队首元素时,堆只需O(1)的复杂度。
  • 从内存和高速缓存的角度,由于完全二叉树的结构特点,可以用向量(数组)来实现堆,内存上非常紧凑,既节省了指向孩子节点的指针,也更有利于Cache机制发挥作用,空间和时间上都非常有优势。

3 实现

3.1 用向量结构来实现

接着上文,如何用数组来存放一个完全二叉树呢?且看下图:
实现一个完全二叉堆_第1张图片
光有用数组保存的完全二叉树是不够的,我们还要维护这棵树使它具有堆的特点——任何叶子节点都不大于(小顶堆)/不小于(大顶堆)其父节点。先考虑一个问题,堆的结构特点在什么时候会被破坏?当然是删除堆顶元素或向堆添加新元素的时候,此时就要用到上滤下滤操作。

3.2 上滤

上滤用于向堆添加新元素的时候,以小顶堆为例,当新增的元素小于其父节点时,就将该元素与其父节点交换位置,直到该元素不再小于其父节点:
实现一个完全二叉堆_第2张图片

3.3 下滤

下滤用于删除堆顶元素的时候,以小顶堆为例,当删除堆顶元素时,首先将堆顶元素和堆的最后一个元素(在数组中下标最大的元素)交换,然后将堆的大小减1(移除换到最后的堆顶元素)。联系堆的结构特点,通常被换到堆顶的元素是大于其孩子节点的,这就破坏了堆的结构特点。此时选择该节点的两个孩子中较小的那个,并交换位置,如果交换后该节点还是大于孩子节点,那么就继续上述流程,直到堆的结构特性得到恢复:
实现一个完全二叉堆_第3张图片

3.4 程序

这里给出一个小顶堆的具体实现,代码仅仅是示例,通常不会将堆写死为小顶或大顶,而是根据调用者提供的比较函数来进行元素的比较。关于通用的堆的实现,读者可自行尝试,原理都差不多:

class MinHeap {
private:
    vector<int> datas;

    void up(int idx);
    void down(int idx);

public:
    MinHeap()  = default;
    ~MinHeap() = default;

    bool empty() const { return datas.empty(); }
    const int &top() const;
    void pop();
    void push(const int &data);
};

/* 上滤操作 */
void MinHeap::up(int idx) {
    while ((idx > 0) && (datas[idx] < datas[(idx - 1) / 2])) {
        swap(datas[idx], datas[(idx - 1) / 2]);
        idx = (idx - 1) / 2;
    }
}

/* 下滤操作 */
void MinHeap::down(int idx) {
    int i = idx;
    while ((2 * i + 2) < datas.size())
    {
        int min_index = (datas[2 * i + 1] < datas[2 * i + 2]) ? (2 * i + 1) : (2 * i + 2);
        if (datas[i] <= datas[min_index])
            break;
        swap(datas[i], datas[min_index]);
        i = min_index;
    }

    if (((2 * i + 1) < datas.size()) && (datas[i] > datas[2 * i + 1]))
        swap(datas[i], datas[2 * i + 1]);
}

/* 获取队首元素 */
const int & MinHeap::top() const {
    assert(!datas.empty());
    return datas.front();
}

/* 将队首元素出队 */
void MinHeap::pop() {
    assert(!datas.empty());

    /* 首尾交换 */
    swap(datas.front(), datas.back());

    /* 移除当前尾部节点 */
    datas.pop_back();

    /* 对首部节点进行下滤 */
    down(0);
}

/* 将新元素入队 */
void MinHeap::push(const int &data) {
    int idx = datas.size();
    datas.emplace_back(data);
    up(idx);
}

你可能感兴趣的:(数据结构与算法)