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