C++之优先级队列

一.优先级队列的认识

优先级队列实质上是一个堆,C++默认创建的优先级队列是一个大堆。
它是一个容器适配器类,底层容器使用vector,默认的比较方式是按照小于比较,也就是在进行堆结构的调整时,如果满足小于的关系才会进行调整,所以默认创建的是一个大堆。
如果需要指定创建的堆是一个小堆,可以通过指定第二个模板参数为vector<类型名>,第三个模板参数为用于比较的仿函数

二.模拟实现一个优先级队列

模拟实现源码:

#pragma once

#include <vector>
#include <functional>

//用vector实现一个优先级队列
//模板共有三个参数,第一个是数据类型,第二个是底层容器,第三个是进行堆调整时的比较函数
template<class T, class Container = std::vector<T>, class Comp = less<T>>
class PriorityQueue
{
private:
	Container _data;		//用vector存储数据
	Comp _cmp;				//一个用于比较的对象
public:
	//一个空的构造函数
	PriorityQueue(){}

	//插入元素
	void push(const T& val)
	{
		//插入数据
		_data.push_back(val);
		//从尾节点向上调整
		shiftUp(_data.size() - 1);
	}
	//删除元素
	void pop()
	{
		//交换头尾元素
		swap(0, _data.size() - 1);
		//删除尾部元素
		_data.pop_back();
		//从根节点向下调整
		shiftDown(0);
	}
	//返回顶部元素
	const T& top()
	{
		return _data[0];
	}
	//判空
	bool empty()
	{
		return _data.empty();
	}
	//返回有效元素个数
	size_t size()
	{
		return _data.size();
	}
private:
	//从pos向上调整
	void shiftUp(int pos)
	{
		int parent = (pos - 1) / 2;
		while (parent >= 0)
		{
			//不满足比较条件,结束调整
			if (!_cmp(_data[parent], _data[pos]))
			{
				break;
			}
			//满足交换条件就交换数据,向上更新
			swap(parent, pos);
			pos = parent;
			parent = (pos - 1) / 2;
		}
	}
	//从pos向下调整
	void shiftDown(int pos)
	{
		int child = 2 * pos + 1;
		
		while (child < _data.size())
		{
			//右孩子比左孩子更符合条件,更新child
			if (child + 1 < _data.size() && _cmp(_data[child], _data[child + 1]))
			{
				++child;
			}
			
			//不满足比较条件,直接退出
			if (!_cmp(_data[pos], _data[child]))
			{
				break;
			}
			//满足交换条件
			swap(pos, child);
			pos = child;
			child = 2 * pos + 1;
		}
	}
	//交换两个位置的数据
	void swap(int pos1, int pos2)
	{
		T tmp = _data[pos1];
		_data[pos1] = _data[pos2];
		_data[pos2] = tmp;
	}
};

三.使用优先级队列构建哈夫曼树并输出编码

(1) 核心思路:

  1. 统计每个字符出现的次数,将词频关系存入哈希表中;
  2. 将哈希表中的每一项构建成一个节点,插入优先级队列;
  3. 每次从优先级队列中取出最小的两个节点,构建一个新节点,直到优先级队列中只有一个节点,此时的节点就是根节点;
  4. 构建哈夫曼编码:
    从根节点开始递归搜索,进入左孩子搜索让哈夫曼编码+‘0’, 进入右孩子搜索让哈夫曼编码+‘1’,直到走到叶节点,走到叶节点将此时的哈夫曼编码存入map中(这个map的key是字符,value是该字符对应的哈夫曼编码,因为map按照key值从小到大进行排序,遍历是有序的,因此使用map存储),当所有的叶节点编码构造结束后,哈夫曼树编码构建完毕;
  5. 遍历存储哈夫曼编码的map的每一项,此时按照key值从小到大打印出每一项

(2) 完整代码演示:

#pragma once

#include <vector>
#include <functional>
#include <map>
#include <string>
#include <unordered_map>
#include <queue>
#include <iostream>

//用vector实现一个优先级队列
//模板共有三个参数,第一个是数据类型,第二个是底层容器,第三个是进行堆调整时的比较函数
template<class T, class Container = std::vector<T>, class Comp = less<T>>
class PriorityQueue
{
private:
	Container _data;		//用vector存储数据
	Comp _cmp;				//一个用于比较的对象
public:
	//一个空的构造函数
	PriorityQueue() {}

	//插入元素
	void push(const T& val)
	{
		//插入数据
		_data.push_back(val);
		//从尾节点向上调整
		shiftUp(_data.size() - 1);
	}

	//删除元素
	void pop()
	{
		//交换头尾元素
		swap(0, _data.size() - 1);
		//删除尾部元素
		_data.pop_back();
		//从根节点向下调整
		shiftDown(0);
	}

	//返回顶部元素
	const T& top() const
	{
		return _data[0];
	}

	//判空
	bool empty()
	{
		return _data.empty();
	}

	//返回有效元素个数
	size_t size()
	{
		return _data.size();
	}
private:
	//从pos向上调整
	void shiftUp(int pos)
	{
		int parent = (pos - 1) / 2;
		while (parent >= 0)
		{
			//不满足比较条件,结束调整
			if (!_cmp(_data[parent], _data[pos]))
			{
				break;
			}
			//满足交换条件就交换数据,向上更新
			swap(parent, pos);
			pos = parent;
			parent = (pos - 1) / 2;
		}
	}

	//从pos向下调整
	void shiftDown(int pos)
	{
		int child = 2 * pos + 1;
	
		while (child < _data.size())
		{
			//右孩子比左孩子更符合条件,更新child
			if (child + 1 < _data.size() && _cmp(_data[child], _data[child + 1]))
			{
				++child;
			}

			//不满足比较条件,直接退出
			if (!_cmp(_data[pos], _data[child]))
			{
				break;
			}
			//满足交换条件
			swap(pos, child);
			pos = child;
			child = 2 * pos + 1;
		}
	}
	//交换两个位置的数据
	void swap(int pos1, int pos2)
	{
		T tmp = _data[pos1];
		_data[pos1] = _data[pos2];
		_data[pos2] = tmp;
	}
};

//哈夫曼树节点
struct TreeNode
{
	char _val;				//节点实际存储的数据
	int _weight;			//节点对应的权重
	TreeNode* _left;		//左孩子
	TreeNode* _right;		//右孩子

	//创建一个有实际数据和权值的节点
	TreeNode(char val, int weight)
		:_val(val)
		,_weight(weight)
		,_left(nullptr)
		,_right(nullptr)
	{}

	//创建一个只有权值的节点
	TreeNode(int weight)
		:_val('\3')
		,_weight(weight)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

//用于比较节点权重的仿函数
//按照权重从小到大进行排序,如果两个节点之间的权重不满足小于关系就要进行调整
//此处的泛型只是为了满足优先级队列构造时的条件,没有任何实际意义
template<class T>
struct Cmp_Node
{
	bool operator()(TreeNode* node1, TreeNode* node2)
	{
		return node1->_weight > node2->_weight;
	}
};

//哈夫曼树
class HuffermanTree
{
private:
	typedef TreeNode Node;
	Node* _root;

public:
	HuffermanTree()
		:_root(nullptr)
	{}

	//根据传入的序列构建哈夫曼树
	void CreateTree(char* arr, int sz)
	{
		if (arr == nullptr)
		{
			return;
		}

		//1.使用哈希表统计每个元素出现的次数
		std::unordered_map<char, int> Ele_Cnt;
		for (int i = 0; i < sz; ++i)
		{
			++Ele_Cnt[arr[i]];
		}

		//2.将元素构建为一个树节点存入优先级队列,这个优先级队列是一种小堆结构
		PriorityQueue<Node*, std::vector<Node*>, Cmp_Node<char>> q;
		for (const auto& kv : Ele_Cnt)
		{
			Node* newNode = new Node(kv.first, kv.second);
			q.push(newNode);
		}

		//3.从优先级队列中每次拿出两个权重最小的节点,构建一个新节点并插入优先级队列
		//构建一个哈夫曼树
		while (q.size() > 1)
		{
			Node* left = q.top();
			q.pop();
			Node* right = q.top();
			q.pop();
			
			Node* newNode = new Node(left->_weight + right->_weight);
			newNode->_left = left;
			newNode->_right = right;
			q.push(newNode);
		}
		
		//最终的堆顶元素就是根节点
		_root = q.top();
	}


	//打印元素对应的哈夫曼编码
	void PrintAllHufferManCode()
	{
		std::map<char, std::string> char_codes;
		dfs(_root, "", char_codes);

		for (const auto& e : char_codes)
		{
			std::cout << e.first << ": " << e.second << std::endl;
		}
	}

	//测试用的层序遍历
	void LevelOrder()
	{
		Node* curNode = _root;
		std::queue<Node*> q;
		if (_root)
		{
			q.push(_root);
		}

		while (!q.empty())
		{
			size_t sz = q.size();
			while (sz--)
			{
				Node* front = q.front();
				q.pop();
				std::cout << front->_val << ": " << front->_weight << "\t";

				if (front->_left)
				{
					q.push(front->_left);
				}
				if (front->_right)
				{
					q.push(front->_right);
				}
			}
			std::cout << std::endl;
		}
	}
private:
	//递归构建每个叶节点的哈夫曼编码
	void dfs(Node* root, std::string curNodeCode, std::map<char, std::string>& char_codes)
	{
		if (root == nullptr)
		{
			return;
		}
		if (root->_left == nullptr && root->_right == nullptr)
		{
			char_codes.insert(make_pair(root->_val, curNodeCode));
			return;
		}

		//左边哈夫曼编码+0
		dfs(root->_left, curNodeCode + '0', char_codes);
		//右边哈夫曼编码+1
		dfs(root->_right, curNodeCode + '1', char_codes);
	}
};

(3) 测试代码:

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

using namespace std;

void test_HuffermanTree()
{
	HuffermanTree tree;
	char arr[] = "abcdcbaded";
	int len = sizeof(arr) / sizeof(arr[0]) - 1;
	tree.CreateTree(arr, len);
	tree.LevelOrder();

	tree.PrintAllHufferManCode();
}

int main()
{
	test_HuffermanTree();
 
	return 0;
} 

(4) 测试用例运行结果:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值