实现一个完全二叉堆

1 完全二叉堆的结构特点

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

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

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

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

3 实现

3.1 用向量结构来实现

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

3.2 上滤

上滤用于向堆添加新元素的时候,以小顶堆为例,当新增的元素小于其父节点时,就将该元素与其父节点交换位置,直到该元素不再小于其父节点:
在这里插入图片描述

3.3 下滤

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

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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* * 基于向量实现完全二叉树 */ package dsa; public class ComplBinTree_Vector extends BinTree_LinkedList implements ComplBinTree { private Vector T;//向量 //构造方法:默认的空树 public ComplBinTree_Vector() { T = new Vector_ExtArray(); root = null; } //构造方法:按照给定的节点序列,批量式建立完全二叉树 public ComplBinTree_Vector(Sequence s) { this(); if (null !=s) while (!s.isEmpty()) addLast(s.removeFirst()); } /*---------- BinaryTree接口中各方法的实现 ----------*/ //返回树根(重写) public BinTreePosition getRoot() { return T.isEmpty() ? null : posOfNode(0); } //判断是否树空(重写) public boolean isEmpty() { return T.isEmpty(); } //返回树的规模(重写) public int getSize() { return T.getSize(); } //返回树(根)的高度(重写) public int getHeight() {return isEmpty() ? -1 : getRoot().getHeight(); } /*---------- ComplBinTree接口中各方法的实现 ----------*/ //生成并返回一个存放e的外部节点,该节点成为新的末节点 public BinTreePosition addLast(Object e) { BinTreePosition node = new ComplBinTreeNode_Rank(T, e); root = (BinTreePosition) T.getAtRank(0); return node; } //删除末节点,并返回其中存放的内容 public Object delLast() { if (isEmpty()) return null;//若树(堆)已空,无法删除 if (1 == getSize()) root = null;//若删除最后一个节点,则树空 return T.removeAtRank(T.getSize()-1); } //返回按照层次遍历编号为i的节点的位置,0 <= i < size() public BinTreePosition posOfNode(int i) { return (BinTreePosition)T.getAtRank(i); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值