【优先队列(堆)】二叉堆类模板的实现

本文详细介绍了二叉堆的结构和堆序性质,包括完全二叉树的概念以及在数组中的实现。堆的基本操作如插入、删除最小元素、降低和增加关键字的值以及构建堆的过程被详细阐述,并提供了C++实现的优先队列类模板。这些操作的时间复杂度分析表明,堆是一种高效的数据结构,适用于优先级队列等场景。
摘要由CSDN通过智能技术生成

二叉堆(binary heap),像二叉查找树一样,堆也有两个性质,即结构性和堆序性。

结构性质

堆是一棵被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入。这样的树称为完全二叉树(complete binary tree)。
在这里插入图片描述

图1 一棵完全二叉树

易证明,一棵高为h的完全二叉树有2h-1个节点,高为⌊㏒N⌋。用一个数组表示为

在这里插入图片描述

图2 完全二叉树的数组实现

对于数组中任一位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元(2i+1)中,它的父亲则在位置⌊i/2⌋上。
因此,这里不仅不需要链,而且遍历该树所需要的操作也极为简单,在大部分机器上运行也非常快。这种实现的唯一问题在于,最大的堆大小需要事先估计,但一般这并不成问题(而且如果需要,可以重新调整大小)。

堆序性质

使操作被快速执行的性质是堆序性质(heap-order property):在一个堆中,对于每一个节点X,X的父亲中的关键字小于(或等于)X中的关键字,根节点除外(它没有父亲)。
在这里插入图片描述

图3 两棵完全树(只有左边的树是堆,虚线破坏了有序性)

根据堆序性质,最小元总可以在根处找到。因此,我们以常数时间得到附加操作findMin。

基本的堆操作

  • insert(插入)
    为将一个元素X插入到堆中,我们在下一个可用位置创建一个空穴(hole),因为否则该堆将不是完全树。如果X可以放在该空穴中而不破坏堆的序,则插入完成。否则,我们把空穴的父节点上的元素移入该空穴中,这样,空穴就朝着根的方向上冒一步。继续该过程直到X能被放入空穴中为止。这种一般的策略叫作上滤(percolate up)。新元素在堆中上滤直到找到正确的位置。
    在这里插入图片描述
图4 尝试插入14:创建一个空穴,再将空穴上冒

在这里插入图片描述

图5 将14插入到前面的堆中剩余的两步

如果欲插入的元素是新的最小元从而一直上滤到根处,那么这种插入的时间将长达O(logN)

  • deleteMin(删除最小元)
    当删除一个最小元时,要在根节点建立一个空穴。由于现在的堆少了一个元素,因此堆中最后一个元素X必须移动到该堆的某个地方。如果X可以被放入空穴中,那么deleteMin完成。不过这一般不太可能,因此我们将空穴的两个儿子中较小者移入空穴,那么就把空穴向下推了一层。重复该步骤直到X可以被放入空穴中。因此,我们的做法是将X置入沿着从根开始包含最小儿子的一条路径上的一个正确的位置。这种一般的策略叫作下滤(percolate down)。
    在这里插入图片描述
图6 在根处建立空穴

在这里插入图片描述

图7 在deleteMin中的下两步操作

在这里插入图片描述

图8 deleteMin中最后的两步操作

当堆中存在偶数个元素的时候,此时将遇到一个节点只有一个儿子的情况。我们必须以节点不总有两个儿子为前提,因此这就涉及到一个附加的测试。一种极其巧妙的解决方法是始终保证算法把每一个节点都看成有两个儿子。当堆的大小为偶数时在每个下滤开始处,可将其值大于堆中任何元素的标记放到堆的终端后面的位置上。这种操作最坏情形运行时间为O(logN)

其他的堆操作

  • decreaseKey(降低关键字的值)
    decreaseKey(p,△)操作降低在位置p处的项的值,降值的幅度为正的量△。由于这可能破坏堆序性质,因此必须通过上滤对堆进行调整。该操作对系统管理程序是有用的:系统管理程序能够使它们的程序以最高的优先级来运行。
  • increaseKey(增加关键字的值)
    increaseKey(p,△)操作增加在p处的项的值,增值的幅度为正的量△。这可以用下滤来完成。许多调度程序自动地降低正在过多地消耗CPU时间的进程的优先级。
  • remove(删除)
    remove§操作删除堆中位置p上的节点。该操作通过首先执行decreaseKey(p,∞)然后再执行deleteMin( )来完成。当一个进程被用户中止(而不是正常终止)时,它必须从优先队列中被除去。
  • buildHeap(构建堆)
    一般的算法是将N项以任意顺序放入树中,保持结构特性,总运行时间平均为O(N)。

实现代码

//优先队列的类接口
#include<vector>
template<typename Comparable>
class BinaryHeap {
public:
	explicit BinaryHeap(int capacity = 100) :currentSize{ 0 },array(capacity+1) {
	}
	explicit BinaryHeap(const std::vector<Comparable>& items)
		:array(items.size() + 10), currentSize{ items.size() }
	{
		for (int i = 0; i < items.size(); ++i)
			array[i + 1] = items[i];
		buildHeap();
	}

	bool isEmpty()const {
		return currentSize == 0;
	}
	const Comparable& findMin()const {
		return array[1];
	}

	/**
	* 将项x插入,允许重复元
	*/
	void insert(const Comparable& x) {
		if (currentSize == array.size() - 1)
			array.resize(array.size() * 2);

		//上滤
		int hole = ++currentSize;
		Comparable copy = x;

		array[0] = std::move(copy);
		for (; x < array[hole / 2]; hole /= 2)
			array[hole] = std::move(array[hole / 2]);
		array[hole] = std::move(array[0]);
	}
	void insert(Comparable&& x) {
		if (currentSize == array.size() - 1)
			array.resize(array.size() * 2);

		//上滤
		int hole = ++currentSize;
		Comparable copy = std::move(x);

		array[0] = std::move(copy);
		for (; x < array[hole / 2]; hole /= 2)
			array[hole] = std::move(array[hole / 2]);
		array[hole] = std::move(array[0]);
	}

	/**
	* 删除最小项
	* 如果为空则抛出UnderflowException异常
	*/
	void deleteMin() {
		if (isEmpty())
			throw UnderflowException{ };

		array[1] = std::move(array[currentSize--]);
		percolateDown(1);
	}

	/**
	* 删除最小项并将其放在minItem处
	* 若为空则抛出UnderflowException异常
	*/
	void deleteMin(Comparable& minItem) {
		if (isEmpty())
			throw UnderflowException{ };

		minItem = std::move(array[1]);
		array[1] = std::move(array[currentSize--]);
		percolateDown(1);
	}
	void makeEmpty() {
		for (int i = 1; i <= currentSize; ++i)
			array[i] = 0;
		currentSize = 0;
	}

private:
	int currentSize;				//堆中元素的个数
	std::vector<Comparable> array;  //堆的数组

	void buildHeap() {
		for (int i = currentSize / 2; i > 0; --i)
			percolateDown(i);
	}

	/**
	* 在堆中进行下滤的内部方法
	* 空穴是下滤开始处的下标
	*/
	void percolateDown(int hole) {
		int child;
		Comparable tmp = std::move(array[hole]);

		for (; hole * 2 <= currentSize; hole = child) {
			child = hole * 2;
			if (child != currentSize && array[child + 1] < array[child])
				child++;
			if (array[child] < tmp)
				array[hole] = std::move(array[child]);
			else
				break;
		}
		array[hole] = std::move(tmp);
	}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhugenmi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值