二叉堆:优先队列的实现与堆排序

二叉堆的定义:

二叉堆(英语:binary heap)是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树。二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆。


大根堆与小根堆的定义:

当父节点的键值总是大于左右孩子节点的键值时为最大堆。当父节点的键值总是小于左右孩子的键值时为最小堆


二叉堆的应用:

优先队列、堆排序


二叉堆与二叉搜索树的区别:

二叉搜索树的每个节点值映射到一条直线上,必定是一个升序的有序数组;二叉堆(小根堆为例)则不一定,因为小根堆的根节点值只需要小于左右孩子节点就行,没有要求左右孩子节点值的大小顺序。


优先队列的实现

可以基于无序数组、有序数组、无序链表、有序链表。在这里,我本人的代码是基于无序数组实现的,而优先队列是基于大根堆和无序数组实现的。

1

用二叉堆实现优先队列最关键也是最要的操作就是上浮(swim)和下沉(sink)

对于最大堆,会破坏堆性质的有有两种情况:

  • 1)如果某个节点 A 比它的子节点(中的一个)小,那么 A 就不配做父节点,应该下去,下面那个更大的节点上来做父节点,这就是对 A 进行下沉
  • 2)如果某个节点 A 比它的父节点大,那么 A 不应该做子节点,应该把父节点换下来,自己去做父节点,这就是对 A 的上浮

当然,错位的节点 A 可能要上浮(或下沉)很多次,才能到达正确的位置,恢复堆的性质。所以代码中肯定有一个 while 循环。

注:对于堆底元素我们使用的操作是上浮,对于堆顶元素我们使用的操作是下沉。

上浮代码实现:

template<typename T>
void Priority_Queue<T>::swim(int k)			//由下至上堆有序化上浮
{
	while (k > 0 && less(k / 2, k))
	{
		std::swap(vec[k / 2], vec[k]);
		k = k / 2;
	}
}

2

下沉代码实现:

template<typename T>
void Priority_Queue<T>::sink(int k)			//由上至下堆有序化下沉
{
	int size = vec.size();
	while (2 * k <size)
	{
		int j = 2 * k;
		//寻找子结点中的较大者
		if (j < size && j + 1 < size && less(j, j + 1))j++;

		//直到父节点大于子结点跳出循环
		if (!less(k, j))break;
		std::swap(vec[k], vec[j]);
		k = j;
	}
}

3

删除最大元素实现(等价于Priority_Queue.pop()):

方法先把堆顶元素 A 和堆底最后的元素 B 对调,然后删除 A,最后让 B 下沉到正确位置。

5

template<typename T>
T Priority_Queue<T>::delMax()
{
	T max = vec[0];							//从根结点获得最大元素
	std::swap(vec[0],vec[vec.size()-1]);				//将其和最后一个结点交换
	vec.pop_back();							//删除根结点
	sink(0);							//第一个元素下沉恢复堆的有序性
	return max;
}

插入元素实现(等价于Priority_Queue.push()):

方法先把要插入的元素添加到堆底的最后,然后让其上浮到正确位置。

4

template<typename T>
void Priority_Queue<T>::insert(const T& val)
{
	vec.push_back(val);
	swim(vec.size() - 1);//最后一个元素上浮
}

优先队列的实现的完整代码如下:

//Priority_Queue.h
#pragma once
#include <iostream>
#include <vector>

template <typename T>
class Priority_Queue
{
public:
	Priority_Queue() = default;			//显式构造默认构造函数
	~Priority_Queue() = default;		//显式构造默认析构函数
	Priority_Queue(const std::vector<T> val);
private:
	std::vector<T> vec;
public:
	T delMax();							//删除并返回最大元素
	T max();							//返回最大元素
	bool isEmpty();						//返回队列是否为空
	int size();							//队列大小
	void insert(const T& val);			//插入元素
	void print();						//打印堆
private:
	bool less(int i, int j);			//比较元素
	void swim(int k);					//上浮
	void sink(int k);					//下沉
};

template<typename T>
inline Priority_Queue<T>::Priority_Queue(const std::vector<T> val)
{
	for (const auto& it : val)
	{
		vec.push_back(it);
		swim(vec.size() - 1);//最后一个元素上浮
	}
}

template<typename T>
T Priority_Queue<T>::delMax()
{
	T max = vec[0];							//从根结点获得最大元素
	std::swap(vec[0],vec[vec.size()-1]);	//将其和最后一个结点交换
	vec.pop_back();							//删除根结点
	sink(0);								//第一个元素下沉恢复堆的有序性
	return max;
}

template<typename T>
T Priority_Queue<T>::max()
{
	return vec.at(0);
}

template<typename T>
bool Priority_Queue<T>::isEmpty()
{
	return vec.empty();
}

template<typename T>
int Priority_Queue<T>::size()
{
	return vec.size();
}

template<typename T>
void Priority_Queue<T>::insert(const T& val)
{
	vec.push_back(val);
	swim(vec.size() - 1);//最后一个元素上浮
}

template<typename T>
void Priority_Queue<T>::print()
{
	for (const auto& it : vec)
		std::cout << it << " ";
	std::cout << std::endl;
}

template<typename T>
bool Priority_Queue<T>::less(int i, int j)
{
	return vec[i] < vec[j];
}

template<typename T>
void Priority_Queue<T>::swim(int k)			//由下至上堆有序化上浮
{
	while (k > 0 && less(k / 2, k))
	{
		std::swap(vec[k / 2], vec[k]);
		k = k / 2;
	}
}

template<typename T>
void Priority_Queue<T>::sink(int k)			//由上至下堆有序化下沉
{
	int size = vec.size();
	while (2 * k <size)
	{
		int j = 2 * k;
		//寻找子结点中的较大者
		if (j < size && j + 1 < size && less(j, j + 1))j++;

		//直到父节点大于子结点跳出循环
		if (!less(k, j))break;
		std::swap(vec[k], vec[j]);
		k = j;
	}
}

堆排序:

优先队列可以发展为一种排序方法,将所有元素插入一个查找最小元素的PQ,然后不断调用 delMin() 方法,即可得到有序数组。用无序数组实现的优先队列相当于进行一次插入排序。用堆实现的,则是堆排序。 堆排序中,直接调用swim()sink()方法,将需要排序的数组本身作为堆,不需要额外的存储空间。

堆排序的两部分:

构造堆(将原始数组重新组织放入一个堆中)和下沉排序(按照降序取出原始构成排序结果)。

在这里插入图片描述

完整代码如下:

//sort_heap.h
bool less(std::vector<int>& vec, int i, int j)
{
	return vec[i] < vec[j];
}

void sink(std::vector<int>& vec, int k, int N)
{
	while (2 * k < N)
	{
		int j = 2 * k;
		//寻找子结点中的较大者
		if (j < N && j + 1 < N && less(vec, j, j + 1))j++;

		//直到父节点大于子结点跳出循环
		if (!less(vec, k, j))break;
		std::swap(vec[k], vec[j]);
		k = j;
	}
}

void heap_sort(std::vector<int>& vec)
{
	int size = vec.size() - 1;
	for (int k = size / 2; k >= 0; k--)
		sink(vec, k, size);
	while (size > 1)//最后两个元素的顺序已经排好了,不需要交换
	{
		std::swap(vec[0], vec[size--]);//最大元素排在最后
		sink(vec, 0, size);//首元素下沉
	}
}

测试代码如下:

//test.cpp
#include "Priority_Queue.h"
#include "heap_sort.h"
#include <iostream>
#include <vector>

using namespace std;

int main()
{
 vector<int> a{ 1,2,3,4,5,6 };
 Priority_Queue<int> PQ(a);
 cout << "优先队列的元素排列为:";
 PQ.print();
 PQ.insert(7);
 PQ.insert(8);
 PQ.insert(9);
 cout << "优先队列的元素排列为:";
 PQ.print();
 cout << "优先队列的最大元素为:" << PQ.max() << endl;
 cout << "删除优先队列的最大元素:" << PQ.delMax() << endl;
 cout << "优先队列的大小为:" << PQ.size() << endl
 cout << "优先队列是否为空:" << (PQ.isEmpty() == true ? "是" : "否") << endl;
 vector<int> b;
 int num;
 cout << "请输入一组数:";
 while (cin >> num)
  b.push_back(num);
  heap_sort(b);
  cout << "堆排序之后的顺序为:";
 for (auto it : b)
  cout << it << " ";
  cout << endl;
 system("pause");
 return 0;
}

测试结果如下:
在这里插入图片描述
注:本文的图片来自labuladong

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值