优先队列实现哈夫曼树
哈夫曼树给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。哈夫曼树的建立过程:这里使用STL容器适配器priority_queue实现哈夫曼树.结构体定义提示:能存储字符有字符的频率(优先级)有左子结点和右子结点t...
哈夫曼树
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
哈夫曼树的建立过程:
这里使用STL容器适配器priority_queue实现哈夫曼树.
结构体定义
提示:
- 能存储字符
- 有字符的频率(优先级)
- 有左子结点和右子结点
typedef struct Haffm {
int priority;//有字符的频率(优先级)
char word;//能存储字符
Haffm* lchild;
Haffm* rchild;//有左子结点和右子结点
}Haffmtree;
建立哈夫曼树
使用优先队列建树过程:
- 各结点依次入队
- 出队两个元素,以新结点为父结点,出队结点为左右子结点连接起来,新结点的优先级为其子结点优先级之和(先出队的为左子结点)
- 将父结点入队
- 循环直到优先队列中只剩一个结点,出队,哈夫曼树构建完成
Haffmtree* buildHaffmtree(priority_queue<Haffmtree*, vector<Haffmtree*>, myCompare> &haffm) {
if (haffm.empty()) {
exit(1);
}
do {
Haffmtree* first = haffm.top();
haffm.pop();
if (haffm.empty()) {
return first;
}
Haffmtree* root=new Haffmtree;
root->lchild = first;
root->rchild = haffm.top();
root->word = '-';
root->priority = root->lchild->priority + root->rchild->priority;
haffm.pop();
haffm.push(root);
} while (!haffm.empty());
}
优先队列的模板类型,第一个参数是存储对象的类型,第二个参数是存储元素的底层容器,第三个参数是函数对象,是比较的方法(依据).后两个参数有默认值,底层容器默认为vector,比较方法默认为less<>(),这是一个仿函数,后文会详细讲到.本文将自定义比较方法.
特别说明:如果需要指定第三个参数,那么也需要指定第二个参数.
template <typename T, typename Container=std::vector<T>, typename Compare=std::less<T>> class priority_queue
总结
在STL提供的一些适配器和排序方法中,常用到比较函数,有时默认的不能满足我们的要求,这时就需要我们自定义.一般有以下几种方法:
- 运算符重载
template<>
struct less<void>
{ // transparent functor for operator<
typedef int is_transparent;
template<class _Ty1,
class _Ty2>
constexpr auto operator()(_Ty1&& _Left, _Ty2&& _Right) const
-> decltype(static_cast<_Ty1&&>(_Left)
< static_cast<_Ty2&&>(_Right))
{ // transparently apply operator< to operands
return (static_cast<_Ty1&&>(_Left)
< static_cast<_Ty2&&>(_Right));
}
};
这是库中针对基础数据类型写的默认的比较函数,大的排在前面,如果我们把这里的"<"重载,变成自己想要的效果,比如以传进来的自定义的结构体的某个成员的大小为依据排序,那么就能用它默认的比较方法达成对特定的元素比较的效果.
typedef struct Haffm {
int priority;
char word;
Haffm* lchild;
Haffm* rchild;
bool operator<(Haffm &a) {
return priority > a.priority;//小的排在前面
}
}Haffmtree;
重写如上,但是本篇代码是不能用这个方法的,因为适配器里装的是Haffm*
类型的数据,而我重载的其实是Haffm
类型数据的比较方法.不过读者可以类比这个例子写自己的重载函数.
2. 函数指针
这个就很简单,写一个自己的比较函数,然后把函数名(也就是函数指针)传入参数.改写如下,这里也不能使用这个方法,因为参数明确指出要函数对象,不过读者可以类比写自己的比较函数.
bool myCompare(Haffmtree* a, Haffmtree* b) {
return a->priority > b->priority;//小的排在前面
}
- 函数对象(仿函数)
仿函数是什么?
仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
仿函数可以通过结构体实现(事实上结构体也是类的一种),也可以通过类实现,传参的时候把结构体名/类名当做函数名传入就可以了.在这里我两种代码都会给出,读者可以类比写自己的仿函数.
struct myCompare {//结构体实现仿函数
bool operator()(Haffmtree* &a, Haffmtree* &b) {
return a->priority > b->priority;
}
};
class mycompare {
public:
bool operator()(Haffmtree* &a, Haffmtree* &b) {
return a->priority > b->priority;
}
};
全部代码及测试代码
#include <iostream>
#include <queue>
#include <functional>
using namespace std;
typedef struct Haffm {
int priority;
char word;
Haffm* lchild;
Haffm* rchild;
}Haffmtree;
struct myCompare {
bool operator()(Haffmtree* &a, Haffmtree* &b){
return a->priority > b->priority;
}
};
Haffmtree* buildHaffmtree(priority_queue<Haffmtree*, vector<Haffmtree*>, myCompare> &haffm) {
if (haffm.empty()) {
exit(1);
}
do {
Haffmtree* first = haffm.top();
haffm.pop();
if (haffm.empty()) {
return first;
}
Haffmtree* root=new Haffmtree;
root->lchild = first;
root->rchild = haffm.top();
root->word = '-';
root->priority = root->lchild->priority + root->rchild->priority;
haffm.pop();
haffm.push(root);
} while (!haffm.empty());
}
void preorder(Haffmtree* &root) {//前序遍历打印结果
if (!root) {
return;
}
cout << root->word << endl;
preorder(root->lchild);
preorder(root->rchild);
}
int main(void) {
priority_queue<Haffmtree*, vector<Haffmtree*>, myCompare> haffm;
int n;
cout << "请输入编码的字符的个数:";
cin >> n;
while (n--) {
Haffmtree* node = new Haffmtree;
cout << "请输入字符:";
cin >> node->word;
cout << "请输入出现的频率:";
cin >> node->priority;
node->lchild = node->rchild = nullptr;
haffm.push(node);
}
Haffmtree* root = buildHaffmtree(haffm);
preorder(root);
system("pause");
return 0;
}
更多推荐
所有评论(0)