哈夫曼树

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

哈夫曼树的建立过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这里使用STL容器适配器priority_queue实现哈夫曼树.

结构体定义

提示:

  1. 能存储字符
  2. 有字符的频率(优先级)
  3. 有左子结点和右子结点
typedef struct Haffm {
	int priority;//有字符的频率(优先级)
	char word;//能存储字符
	Haffm* lchild;
	Haffm* rchild;//有左子结点和右子结点
}Haffmtree;
建立哈夫曼树

使用优先队列建树过程:

  1. 各结点依次入队
    在这里插入图片描述
  2. 出队两个元素,以新结点为父结点,出队结点为左右子结点连接起来,新结点的优先级为其子结点优先级之和(先出队的为左子结点)
    在这里插入图片描述
  3. 将父结点入队
    在这里插入图片描述
  4. 循环直到优先队列中只剩一个结点,出队,哈夫曼树构建完成
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
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提供的一些适配器排序方法中,常用到比较函数,有时默认的不能满足我们的要求,这时就需要我们自定义.一般有以下几种方法:

  1. 运算符重载
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;//小的排在前面
}
  1. 函数对象(仿函数)
    仿函数是什么?

仿函数(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;
}

在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐