数据结构之哈夫曼树


哈夫曼树

哈夫曼编码

  • 建立哈夫曼树
  • 对应哈夫曼编码
  • 由编码对应原数据
  • 带权路径长度
#include<iostream>
#include<stack>

using namespace std;

typedef struct
{
	int weght;		//权值
	int left;		//左节点
	int right;		//右节点
	int parent;		//父节点
}HuffNode,*HuffTree;
//结点选择
void Select(HuffTree& hf, int len, int& m1, int& m2);
//生成哈夫曼树
void CreateHuffManTree(HuffTree& hf, int arr[], int len);
//先序遍历哈夫曼树
void PreOrder_Huffman(HuffTree hf, int len);
//对哈夫曼树解码
void HuffManDeCode(HuffTree hf, char code[], int len, int n);
//返回哈夫曼树对应的编码
char** HuffManCode(HuffTree hf, int len);
//求解哈夫曼树的路径权值
int WPLHuffManTree(HuffTree hf, int len);

//int main()
//{
//	int n;
//	cin >> n;
//	int arr[10];
//	for (int i = 0; i < n; i++)
//		cin >> arr[i];
//	HuffTree hf = NULL;
//	CreateHuffManTree(hf, arr, n);
//	char** code = HuffManCode(hf, 2 * n);
//	char s[6] = "17234";
//	cout << WPLHuffManTree(hf, 2 * n);
//	return 0;
//}
/*
哈夫曼树的特性决定它是不可能存在只有单个子节点的结点,如果有n个叶子结点,那么设总结点有N个,
则二度结点有N-n个,由握手定理得 2*(N-n)=N-1  N=2*n-1 
可以知道非叶子结点有 n-1个
*/
//生成哈夫曼树
//len为数组arr的长度
void CreateHuffManTree(HuffTree& hf, int arr[], int len)
{
	hf = new HuffNode[2 * len];			//将哈夫曼树赋总空间2*len,第一个空间舍弃不用,因此1~len是
	hf[0] = { 0,0,0,0 };				//叶子结点,len+1~2*len-1是非叶子结点。
	for (int i = 1; i <= len; i++)
	{
		hf[i] = { arr[i - 1],0,0,0 };	//将叶子结点都进行初始化,权值对应,父节点设为0,左右结点也为0。
	}
	for (int i = len + 1; i <= 2 * len - 1; i++)
	{
		int m1, m2;						//计算非叶子结点。
		m1 = m2 = 0;
		Select(hf, i - 1, m1, m2);		//从已经算出的结点中选择最小的,且没有父节点,即父节点为0的两个结点 。
										//结点m1要小于m2.。
		hf[i].left = m1, hf[i].right = m2;//这个新结点的左节点为m1,右节点为m2。
		hf[i].weght = hf[m1].weght + hf[m2].weght;//权值则为m1、m2的和。
		hf[i].parent = 0;				//新结点的父亲结点设为0。
		hf[m1].parent = hf[m2].parent = i;//结点m1、m2的父亲结点则设为新结点。
	}
}
//选出两个权值最小且无父亲结点的结点
void Select(HuffTree& hf, int len,int &m1,int&m2)
{
	int min1, min2;
	min1 = min2 = hf[1].weght;
	for (int i = 1; i <= len; i++)
	{
		if (min1 > hf[i].weght && hf[i].parent == 0)		//一次遍历找到权值最小且无父节点的节点m1。
		{
			m1 = i;
			min1 = hf[i].weght;
		}
	}
	for (int i = 1; i <= len; i++)
	{
		if (min2 > hf[i].weght && i!=m1 && hf[i].parent == 0)//一次遍历找到权值第二小且无父节点的节点m2。
		{
			m2 = i;
			min2 = hf[i].weght;
		}
	}
}
//先序遍历哈夫曼树
void PreOrder_Huffman(HuffTree hf, int len)
{
	stack<HuffNode> s;
	s.push(hf[len-1]);							//由哈夫曼树的构造过程很容易就知道最后一个结点就是根节点。
	while (!s.empty())							//利用栈进行一次非递归的先序遍历。
	{
		HuffNode h = s.top();
		s.pop();
		cout << h.weght << " " << h.parent << " " << h.left << " " << h.right << endl;
		if (h.right != 0)
			s.push(hf[h.right]);
		if (h.left != 0)
			s.push(hf[h.left]);
	}
}
//哈夫曼树解码
void HuffManDeCode(HuffTree hf, char code[], int len, int n)
{
	HuffNode p = hf[len - 1];					//解码从根节点往下走 左0右1。
	for (int i = 0; i < n; i++)
	{
		if (code[i] == '0')						//如果该字符码是 0则进入左结点。
			p = hf[p.left];
		else									//如果该字符码是 1则进入右结点。
			p = hf[p.right];
		if (p.left == 0)						//叶子节点的左右节点都是0,因此每次都对节点判断一下左右子节点
		{										//如果有0则说明已经到底了 输出这一段字符码所对应的数据。
			cout << p.weght << endl;
			p = hf[len - 1];					//输出完后进入下一段字符码的解码,此时要将p重新置回根节点,再从根节点开始解码。
		}
	}
}
//建立哈夫曼树对应的字符编码
//len 是该哈夫曼树总节点个数
char** HuffManCode(HuffTree hf, int len)
{
	len /= 2;									//len/2后得到了叶子节点的个数。
	char** Code = new char* [len];				//申请len/2个字符串空间。
	for (int i = 1; i <= len; i++)
	{
		char str[10];
		Code[i - 1] = new char[10];
		int count = 0;							//求一个叶子节点所对应的编码的思路是:从该叶子节点开始,先获取它父节点的
		HuffNode p = hf[i],q;					//下标,然后判断它父节点的左右节点哪个是它,如果是左节点则该节点字符编码
		while (p.parent != 0)					//赋值0,是右节点则赋值1,这样依次往上求,直到父节点为0就退出。
		{										//但这样求出来的字符编码是反过来的,因为是从叶子节点往上求,所以用一个临时
												//字符数组存储,然后再翻转给Code数组即可。
			q = hf[p.parent];					//q赋值为p的父节点。
			if (hf[q.left].weght == p.weght)	//如果父节点的左节点是p,则字符编码存0。
				str[count++] = '0';
			else								//如果父节点的右节点是p,则字符编码存1。
				str[count++] = '1';
			p = q;								//p赋值为该节点的父节点,然后重复上述操作。
		}
		str[count] = '\0';
		for (int j = 0, k = count - 1; j < count; j++, k--)//翻转str数组的编码给Code字符数组。
		{
			Code[i - 1][j] = str[k];
		}
		Code[i - 1][count] = '\0';				//给字符数组最后加'\0' 形成一个完整的字符串。
	}
	return Code;
}
//求WPL
//求带权路径长,这里用最方便的,将所有非叶子节点的权值加和得到WPL,具体推导过程可以去
//网上搜索
int WPLHuffManTree(HuffTree hf,int len)
{
	int wpl = 0;
	for (int i = len / 2+1; i < len; i++)			//len/2+1就是第一个非叶子节点的下标。
	{
		wpl += hf[i].weght;
	}
	return wpl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值