赫夫曼树与赫夫曼编码

19年复习知识点 专栏收录该内容
7 篇文章 0 订阅

1.赫夫曼树也叫最优二叉树,n个权值构造一颗有n个叶子结点的二叉树,且使叶子结点带权路径长度之和最小,则得到一颗赫夫曼树。

2.赫夫曼树的构造
⑴给定n个权值,构成一个森林的集合F,F中初始为n颗只有一个根节点的二叉树,即n个权值。
⑵每次从F中取出根节点权值最小的二叉树,作为左右子树,得到一颗新树(这里规范一下,左子树根节点权值小于右子树根节点权值,这样可以得到唯一的赫夫曼树)
⑶将新树加入森林F,重复⑵⑶直到森林中仅剩一棵树,这棵树就是构造好的赫夫曼树。

3.赫夫曼编码
⑴前缀编码:若要设计长短不等的编码,则必须满足任一个字符的编码都不能是另一个字符编码的前缀,这种编码成为前缀编码。
⑵利用二叉树可以设置二进制前缀编码,从根节点出发到达目的节点的,设左子树为0,右子树为1,这样就得到了目标节点的二进制前缀编码。
⑶若现在要给一个字符串的所有字符编码,使得最后编码过的字符串总长最短,可以用赫夫曼编码。记录字符串中每个字符串的出现次数,作为赫夫曼树的初始权值,构造出赫夫曼树,按照左0右1的原则即可得到赫夫曼编码。

4.代码
⑴数据结构定义

#include "BiTree.h"

struct TNode
{
	int data;
	int num;    //结点编号
	int parent; //父结点、左右孩子结点的编号,从1开始
	int lchild;
	int rchild;
};

class HTree
{
private:
	int n;  //初始森林中,树的个数
	int n_pos; //森林中树的当前个数
	int available_node;  //可用结点的编号
	TNode *F;
	
public:
	HTree(int a[],int n);
	void create_HTree();              //构建赫夫曼树
	void print_HTree();
	int getAva();                    //获取当前available_node
	int getData(int num);     
	Node* to_BiTree(int num);         //将顺序存储的赫夫曼树转为 二叉链表形式
	int cal_huffman_code(int num,int*a);  //计算某结点的赫夫曼编码
};

HTree::HTree(int a[],int nn)
{
	n = nn;
	n_pos = nn;
	available_node = n + 1;
	F = new TNode[2*n-1];   //注意,n个点构成的赫夫曼树最多只有2n-1个结点,因为n个点全是叶子结点n0,而n0=n2+1,没有度为1结点,故总数为n0+n2=n+n-1=2*n-1
	for (int i = 0; i < n; i++)
	{
		F[i].data = a[i];
		F[i].num = i + 1;
		F[i].lchild = 0;
		F[i].rchild = 0;
		F[i].parent = 0;
	}
}

int HTree::getAva()
{
	return available_node;
}

int HTree::getData(int num)
{
	return F[num - 1].data;
}

⑵构建赫夫曼树

void HTree::create_HTree()
{
	while (n_pos > 1)
	{
		int min_w = INT_MAX;
		int num1 = 0, num2 = 0;
		for (int i = 0; i < 2*n - 1; i++)
		{
			if (F[i].data && !F[i].parent&&F[i].data < min_w)  //森林中的树
			{
				min_w = F[i].data;
				num1 = F[i].num;
			}
		}
		F[num1 - 1].parent = INT_MAX;
		min_w = INT_MAX;
		for (int i = 0; i < 2 * n - 1; i++)
		{
			if (F[i].data && !F[i].parent&&F[i].data < min_w)  //森林中的树
			{
				min_w = F[i].data;
				num2 = F[i].num;
			}
		}
		//现在已经找到森林中最小的两个根节点
		n_pos -= 2;

		F[available_node - 1].data = F[num1 - 1].data + F[num2 - 1].data;   //更新父节点的编号、数据、父节点编号
		F[available_node - 1].num = available_node;
		F[available_node - 1].parent = 0;   //注意,这里如果不赋为0,上面森林中查找时会漏掉

		F[num1 - 1].parent = available_node;
		F[num2 - 1].parent = available_node; //更新孩子结点的父节点编号

		if (F[num1 - 1].data < F[num2 - 1].data)  //更新父节点的左右孩子编号。    注意:限制左子树根节点权重小于右子树,可得到唯一赫夫曼树
		{
			F[available_node - 1].lchild = num1;
			F[available_node - 1].rchild = num2;
		}
		else
		{
			F[available_node - 1].lchild = num2;
			F[available_node - 1].rchild = num1;
		}

		available_node++;  //可用结点编号后移
		n_pos++;            //新树加入森林
	}
}

⑶计算某个结点的赫夫曼编码

int HTree::cal_huffman_code(int num,int*a)
{
	int i = 0;
	while (F[num - 1].parent)
	{
		
		if (F[F[num - 1].parent - 1].lchild == num)
		{
			*(a + i) = 0;
		}
		else
		{
			*(a + i) = 1;
		}
		num = F[num - 1].parent;
		i++;
	}
	return i;
}

⑷赫夫曼树转换为二叉链表结构存储的二叉树

二叉链表详见:https://blog.csdn.net/weixin_42416780/article/details/90145953

Node* HTree::to_BiTree(int num)
{
	Node *root = new Node;
	root->data = F[num - 1].data;
	if (F[num - 1].lchild)
		root->lchild = to_BiTree(F[num - 1].lchild);
	else
		root->lchild = NULL;                    //这里如果没有左孩子或者右孩子,一定要把相应的指针设为NULL

	if (F[num - 1].rchild)
		root->rchild = to_BiTree(F[num - 1].rchild);
	else
		root->rchild = NULL;
	return root;
}

⑸打印赫夫曼树
void HTree::print_HTree()
{
cout << “num” << " | " << “data” << " | " << “parent” << " | " << “lchild” << " | " << “rchild”<<endl;
for (int i = 0; i < 2 * n - 1; i++)
{
cout << setfill(’ ‘) << setw(3) << F[i].num << " ";
cout << setfill(’ ‘) << setw(4) << F[i].data << " ";
cout << setfill(’ ‘) << setw(6) << F[i].parent << " ";
cout << setfill(’ ‘) << setw(6) << F[i].lchild << " ";
cout << setfill(’ ') << setw(6) << F[i].rchild << " " << endl;
}
}

5.测试

int main()
{
	/*构造赫夫曼树*/
	int n;
	cout << "请输入赫夫曼树的原始结点数目:" << endl;
	cin >> n;
	int *a=new int[n];
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	HTree myHTree(a, n);   
	myHTree.create_HTree();
	myHTree.print_HTree();
	cout << endl;

	/*转二叉链表并先序遍历*/
	Node* root = myHTree.to_BiTree(myHTree.getAva()-1);  
	BiTree myTree2(root);
	cout << "赫夫曼树的先序遍历:";
	myTree2.pre_traverse_recursion(myTree2.getRoot());
	cout << endl;
	cout << endl;

	/*计算赫夫曼编码,根据结点编号*/
	int*b = new int;
	int l = myHTree.cal_huffman_code(1, b);
	cout <<myHTree.getData(1)<< "的赫夫曼编码是:";
	for (int i = l - 1; i >= 0; i--)
		cout << *(b + i);
	cout << endl;
	
	delete b; 
	b = NULL;  
	
	return 0;
}

在这里插入图片描述在这里插入图片描述

  • 4
    点赞
  • 0
    评论
  • 8
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值