【算法与数据结构】堆(C++实现)

本文详细介绍了C++中的二叉堆数据结构,包括小根堆的构建方法,如何插入和访问最小元素,以及删除堆顶元素的过程。同时讨论了堆排序的相关特性,如空间复杂度和时间复杂度。
摘要由CSDN通过智能技术生成

堆(C++实现)

二叉堆

image-20240402164958618

二叉堆在结构上我们可以看成一棵“完全二叉树”,其分为:

  • 大根堆(根 > 左右子树)
  • 小根堆(根 < 左右子树)

其具有特性:①堆顶元素最大(或最小) ②父节点 > 子节点(或大于)

因为堆可以看作二叉树,所以在存储为容器形式时,我们有如下规律

  • i节点的 左子树:2i,右子树:2i+1
  • i节点的父节点:$\lfloor \frac{i}{2} \rfloor $

我们通常使用堆来排序,而这种排序是一种不稳定(即排序后,元素相对位置可能改变)的排序方式。

堆排序 空间复杂度(因为属于原地排序,不需要辅助空间):O(1)

堆排序 时间复杂度:

  • 建堆(O(n) <= 4n);
  • n-1次调整,每轮:(log n)
  • 好&坏&平:O(nlogn)

以下以构建**小根堆**为例,大根堆的构造方式类似

BinaryHeap 基础框架

template <typename Comparable>
class BinaryHeap
{
public:
    // 构造空堆
	explicit BinaryHeap(int capacity = 100);
    // 用vector 构造堆
	explicit BinaryHeap(const vector<Comparable>& items);

    // 判断heap是否为空
	bool isEmpty() const;
	
    // 访问最小元素
	const Comparable& findMin() const;
	// 插入新元素
	void insert(const Comparable& x);

	void insert(Comparable& x);

	// 删除最小项
	// 如果为空则抛出out_of_range异常
	void deleteMin();
	// 删除最小项并将其放在minItem处
	// 若为空抛出异常
	void deleteMin(Comparable& minItem);
	
    // 将堆置为空
	void makeEmpty();

private:
	/* data */
	int curSize;              // 堆中元素个数
	vector<Comparable> array; // 堆的数组

	void bulidHeap();     // 构造堆

	// 调整以hole位置为根节点的子树排序
	void percolateDown(int hole);
};

注意:在array中我们使用array[1]位置作为堆的根节点,将array[0]视作一个临时位,而在curSize存储堆中真正的有效元素,即array[1, curSize]

构建堆

步骤(使用vector items初始化):

1、初始设置 array.size() == items.size() + 10,cursize = items.size()

2、把items的元素从array[1]开始全部填充到array

3、从cursize / 2位置开始调整堆,不断往上,直到调整完根节点array[1]

3、堆的调整方式:

  1. 传入pos,将pos视作根节点(这是重点,也就是我们在遍历调整某个节点时,我们只关注往下。)
  2. 使用tmp缓存array[pos] 中的数据
  3. 比较tmp与当前pos位较小子节点的大小【根据二叉树特性,我们可知子节点为 pos*2 or pos*2 +1 】
    1. 若 tmp > child ,array[pos] == array[child] ; pos位==child位; 往下循环
    2. 若 tmp <= child 直接跳出
  4. 最后将tmp 存放在pos位中。

代码如下

// 构造函数
explicit BinaryHeap(const vector<Comparable>& items) : array(items.size() + 10), curSize(static_cast<int>(items.size()))
{
    for (int i = 0; i < items.size(); ++i)
        array[i + 1] = items[i];
    bulidHeap();
}

// 构建堆,使堆满足其特性
void bulidHeap()
{
    // 从中间节点往根节点遍历
	for (int i = curSize / 2; i > 0; --i)
		percolateDown(i);
	std::cout << "The Heap is bulid over ! " << std::endl;
}

// 调整以hole位置为根节点的子树排序
void percolateDown(int hole)
{
	int child;
	Comparable tmp = std::move(array[hole]);

	for (; hole * 2 <= curSize; hole = child)
	{
		child = hole * 2;
		// 要和更小那个子节点比较
		if (child != curSize && array[child + 1] < array[child])
			++child;
		if (array[child] <  tmp)
			array[hole] = std::move(array[child]);
		// 不会更小,不用再遍历子节点,直接跳出循环
		else
			break;
	}
	// 此处的hole记录原有元素应该存在的位置
	array[hole] = std::move(tmp);
}

插入元素

1、传入一个待排序元素x,如果array已经满了,需要扩容

2、cursize ++,然后把待排元素x 放置最后一个位置hole(这步是我们假设的,事实上现在x元素缓存在array[0])

3、从hole位置不断往上和父节点比较

  1. x > array[hole.father] ; 跳出循环
  2. x < array[hole.father] ; array[hole.father] 元素放到hole位置,hole = hole.father

4、最终的hole位是x实际应该插入的位置。

代码如下

void insert(const Comparable& x)
{
    // 如果array的大小不够
    if (curSize == array.size() - 1)
        array.resize(array.size() * 2);

    // 上滤,curSize + 1
    int hole = ++curSize;
    // 存储insert元素
    Comparable copy = x;

    array[0] = std::move(copy);
    // 遍历其父节点,找到合适的位置
    for (; x < array[hole / 2]; hole /= 2)
    {
        array[hole] = std::move(array[hole / 2]);
    }
    // 找到insert元素最合适的位置
    array[hole] = std::move(array[0]);
}

访问最小(最大)元素

时间复杂度:O(1)

因为我们构建堆及插入元素时,已经让堆按照它的特性排序,所以我们只需要访问堆顶元素就可以得到最小(最大)元素

    bool isEmpty() const { return curSize == 0; }

    const Comparable& findMin() const
    {
        if (!isEmpty())
            return array[1];
        else
            throw std::out_of_range("[Warning]the heap is empty!");
    }

删除堆顶元素

1、用末尾元素覆盖堆顶元素,cursize–,以堆顶为根节点调整堆(方式与构造时相同)

代码如下:

void deleteMin()
{
    if (isEmpty())
        throw std::out_of_range("[Warning]the heap is empty!");

    // 抛出顶端元素,把尾元素放到顶端
    array[1] = std::move(array[curSize--]);
    // 重新调整位置
    percolateDown(1);
}

附:完整的BinaryHeap.h

#ifndef BINARYHEAP_H
#define BINARYHEAP_H

#include<vector>
#include<utility>
#include<stdexcept>
#include<iostream>

using std::vector;

template <typename Comparable>
class BinaryHeap
{

public:
	explicit BinaryHeap(int capacity = 100):array(capacity + 1), curSize(0) {}
	explicit BinaryHeap(const vector<Comparable>& items) : array(items.size() + 10), curSize(static_cast<int>(items.size()))
	{
		for (int i = 0; i < items.size(); ++i)
			array[i + 1] = items[i];
		bulidHeap();
	}

	bool isEmpty() const { return curSize == 0; }

	const Comparable& findMin() const {
		if (!isEmpty()) 
			return array[1];
		else
			throw std::out_of_range("[Warning]the heap is empty!");
	};

	// 因为普通形参的const会忽略实参的顶层const,所以不用再重写非const的insert
	void insert(const Comparable& x)
	{
		// 如果array的大小不够
		if (curSize == array.size() - 1)
			array.resize(array.size() * 2);

		// 上滤,curSize + 1
		int hole = ++curSize;
		// 存储insert元素
		Comparable copy = x;

		array[0] = std::move(copy);
		// 遍历其父节点,找到合适的位置
		for (; x < array[hole / 2]; hole /= 2)
		{
			array[hole] = std::move(array[hole / 2]);
		}
		// 找到insert元素最合适的位置
		array[hole] = std::move(array[0]);
	}

	// 删除最小项
	// 如果为空则抛出out_of_range异常
	void deleteMin()
	{
		if (isEmpty())
			throw std::out_of_range("[Warning]the heap is empty!");

		// 抛出顶端元素,把尾元素放到顶端
		array[1] = std::move(array[curSize--]);
		// 重新调整位置
		percolateDown(1);
	}

	// 删除最小项并将其放在minItem处
	// 若为空抛出异常
	void deleteMin(Comparable& minItem)
	{
		if (isEmpty())
			throw std::out_of_range("[Warning]the heap is empty!");

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

	void makeEmpty() {
		curSize = 0;
	}

private:
	/* data */
	int curSize;              // 堆中元素个数
	vector<Comparable> array; // 堆的数组

	void bulidHeap()
	{
		for (int i = curSize / 2; i > 0; --i)
			percolateDown(i);
		std::cout << "The Heap is bulid over ! " << std::endl;
	}

	// 调整以hole位置为根节点的子树排序
	void percolateDown(int hole)
	{
		int child;
		Comparable tmp = std::move(array[hole]);

		for (; hole * 2 <= curSize; hole = child)
		{
			child = hole * 2;
			// 要和更小那个子节点比较
			if (child != curSize && array[child + 1] < array[child])
				++child;
			if (array[child] < tmp)
				array[hole] = std::move(array[child]);
			// 不会更小,不用再遍历子节点,直接跳出循环
			else
				break;
		}
		// 此处的hole记录原有元素应该存在的位置
		array[hole] = std::move(tmp);
	}
};

#endif

进行测试

#include<iostream>
#include "BinaryHeap.h"

int main() {
	std::vector<int> items = { 24, 13, 46, 25, 31, 19 };
	BinaryHeap<int> biheap = BinaryHeap<int>(items);
	const int x = 14;
	biheap.insert(x);
	biheap.deleteMin();
	while (!biheap.isEmpty()) {
		std::cout << biheap.findMin() << " ";
		biheap.deleteMin();
	}				// 输出:14 19 24 25 31 46
	std::cout << std::endl;
	return 0;
}

参考:

[1] 《数据分析与算法分析——C++语言描述(第四版) by: Mark Allen Weiss

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值