赫夫曼树的构建编码译码解析

Posted claireyuancy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了赫夫曼树的构建编码译码解析相关的知识,希望对你有一定的参考价值。

当你開始看这篇博文的时候。我相信你对树及二叉树的基本概念已有所了解。我在这里就不再赘述。

我们主要对赫

夫曼树的特点、构建、编码、译码做一个具体的介绍,并附有代码,全部函数代码都通过了測试。我不保证全部代码是最优的(毕竟是我一个人苦思冥想出来的,我相信在大家的集思广益之下还有优化的空间),但我保证全部代码是正确的。

一、赫夫曼树的特点

赫夫曼树又称作最优二叉树,是一类带权路径长度最短的树。

首先给出路径和路径长度的概念。从树中一个节点到

还有一个节点之间的分支构成这两个节点之间的路径。路径上的分支数目称作路径长度树的路径长度是从树根到每个叶子节点的路径长度之和。

节点的带全路径长度为该节点到树根的路径长度乘以该节点上的权值。树的带权路径长度为树中全部叶子节点的带权路径长度之和,通常记做WPL=(w1*L1 + w2*L2 + ....+Wn * Ln)。

所谓的最优二叉树就是WPL的值最小。

二、赫夫曼树的构造方法

赫夫曼最早给出了一个带有一般规律的算法,俗称赫夫曼算法。例如以下:

①给定n个权值{w1。w2,...。wn}。构成n棵二叉树的集合T={T1,T2......。Tn},当中每棵二叉树Ti仅仅有一个权

Wi的根节点。其左右子树都为空。

②在T中选取两棵根节点权值最小的树做为左右子树构造一棵新的二叉树。且置新树的根节点的权值为其左右孩

子的上的根节点权值之和。

③在T中删除这两棵树,并将新得到的二叉树增加到T中。

④反复②、③步骤,直到T中仅仅有一个树为止。这棵树就是赫夫曼树。

构造的详细步骤例如以下:

技术分享

技术分享



三、赫夫曼树构造、编码、译码的思路

在构造赫夫曼树的时候,我们须要一个链表,当给我们n个keyword及其所相应的权值的时候,先构造一个赫夫曼

树节点。然后将赫夫曼树节点做为链表节点的数据域插入到链表中(这个链表的插入操作是依照树根节点的权值大小排好序的),再构造剩余的n-1个赫夫曼树节点,并插入到链表中,当完毕插入工作后。我们的有序链表也就构造出来了。然后对有序链表中的节点完毕合并、删除、插入,直到链表中仅仅有一个节点位置,这个节点的数据域就是指向赫夫曼树根节点的指针。

当有了赫夫曼树之后,我们对其进行编码,在编码的时候左分支为0,右分支为1,例如以下图所看到的。以先序遍历二叉

树,然后还要定义一整数a,初始值为1。在訪问某一节点时将其作为參数传入,假设訪问的是左节点传入a*2,訪问右节点传入a*2+1,也就是在a的二进制数据中,向左走在末尾加个0,向右走加个1(初始化为1是为了避免開始时向左走。无法加零的情况,输出时要将首位的1去掉),訪问到叶子节点时将a转化成二进制,再将首位的1去掉就可以。

技术分享

赫夫曼译码:我们从报文中取出二进制,假设为0,则沿着根节点的左孩子往下走;假设为1。则沿着根节点的右

孩子往下走;直到走到叶子节点为止。假设报文中还有数据则接着从赫夫曼树的根节点出发查找。

假设在查找的过程中无法找到叶子节点,则报文有错误。

四、赫夫曼树构造、编码、译码代码例如以下

1、我们须要两个结构体,一个是赫夫曼树节点的结构体。还有一个是链表节点的结构体:

typedef struct Node
{
	int weight;
	key_type key;
	struct Node *lchild, *rchild;
}HFMNode,*HFMTree;
/*用于链表的结构体*/
typedef struct link_node
{
	HFMTree data;
	struct link_node *next;
}LNode,*Link;
2、用到的函数例如以下:

void init_link(Link *head);//初始化链表
void insert_link(Link head, HFMTree hfm);//向链表中插入一个元素,并依照权重排序
int delete_link(Link head,HFMTree *hfm);//依次删除链表中的数据。成功返回1,失败返回0
/*创建赫夫曼树,str为keyword,w为相应的权重*/
int creat_hfmTree(HFMTree *root,char str[],int w[]);
/*获取赫夫曼编码表,存储在数组code中*/
void hfmTree_code(HFMTree head, int a,char code[]);
/*译码,译码结果存储在decode数组中,code输入的报文*/
int hfmTree_decode(HFMTree head,const char code[],char decode[]);
3、总体代码例如以下:

#include <iostream>
#include <assert.h>
typedef char key_type;
/*用于赫夫曼树的结构体*/

using namespace std;

typedef struct Node
{
	int weight;
	key_type key;
	struct Node *lchild, *rchild;
}HFMNode,*HFMTree;
/*用于链表的结构体*/
typedef struct link_node
{
	HFMTree data;
	struct link_node *next;
}LNode,*Link;
/*对链表的操作*/
void init_link(Link *head);//初始化链表
void insert_link(Link head, HFMTree hfm);//向链表中插入一个元素。并依照权重排序
int delete_link(Link head,HFMTree *hfm);//依次删除链表中的数据,成功返回1。失败返回0
/*创建赫夫曼树,str为keyword,w为相应的权重*/
int creat_hfmTree(HFMTree *root,char str[],int w[]);
/*获取赫夫曼编码表,存储在数组code中*/
void hfmTree_code(HFMTree head, int a,char code[]);
/*译码,译码结果存储在decode数组中,code输入的报文*/
int hfmTree_decode(HFMTree head,const char code[],char decode[]);
int main()
{
	HFMTree root;
	char str[] = "abcdefg";
	int w[] = { 12, 18, 36, 79,85,32,40};
	creat_hfmTree(&root, str, w);
	
	char code[1024] = { 0 };//用来存放编码表
	hfmTree_code(root, 1,code);
	cout <<"编码结果为:"<<endl<< code << endl;
	
	char decode[90] = { 0 };
	hfmTree_decode(root, "00001100010010100111011", decode);
	cout <<"译码结果为:"<<endl<< decode << endl;

}

void init_link(Link *head)
{
	(*head) = (Link)malloc(sizeof(LNode));
	(*head)->next = NULL;
	(*head)->data = NULL;
}

void insert_link(Link head, HFMTree hfm)
{
	assert(head != NULL);
	/*先构造链表节点*/
	Link temp = (Link)malloc(sizeof(LNode));
	temp->next = NULL;
	temp->data = hfm;

	while (head->next != NULL && head->next->data->weight < hfm->weight)
	{
		head = head->next;
	}
	if (head->next == NULL)
	{
		head->next = temp;
	}
	else
	{
		temp->next = head->next;
		head->next = temp;
	}
}

int delete_link(Link head, HFMTree *hfm)
{
	assert(head != NULL);
	if (head->next == NULL)
		return 0;
	Link temp = head->next;
	head->next = temp->next;

	*hfm = temp->data;

	free(temp);
	return 1;
}

int creat_hfmTree(HFMTree *root, char str[], int w[])
{
	int n = strlen(str);
	HFMTree temp;

	Link head;
	init_link(&head);
	/*构造节点,依照权重的递增顺序插入到链表中*/
	for (int i = 0; i < n;i++)
	{
		/*构造节点*/
		temp = (HFMTree)malloc(sizeof(HFMNode));
		
		temp->key = str[i];
		temp->weight = w[i];
		temp->lchild = NULL;
		temp->rchild = NULL;

		insert_link(head, temp);
	}
	if (head->next == NULL)//链表中没有元素
		return 0;
	/*从链表中取出两个最小的节点构造赫夫曼树*/
	HFMTree temp1, temp2, temp3;
	/*链表中存在1个元素则退出循环*/
	while ( delete_link(head,&temp1) && delete_link(head,&temp2) )
	{
		temp3 = (HFMTree)malloc(sizeof(HFMNode));

		temp3->weight = temp1->weight + temp2->weight;
		temp3->lchild = temp1;
		temp3->rchild = temp2;

		insert_link(head, temp3);
	}
	*root = temp1;
	return 1;
}

void hfmTree_code(HFMTree head, int a, char code[])
{
	static int i = 0;
	if (head != NULL)
	{
		if (head->lchild == NULL && head->rchild == NULL)//訪问到了叶子节点
		{
			code[i++] = head->key;
			code[i++] = ':';//在keyword后面加":",其后面的"0"、"1"表示编码
			int count = 0;
			/*对a做处理。a最前面的1要舍去*/
			while (a > 1)
			{
				if (a % 2 == 0)
					code[i++] = '0';
				else
					code[i++] = '1';
				a = a>>1;
				count++;
			}
			/*对字符串反转,这样才和我们的赫夫曼编码一直*/
			strrev( code + i - count);
			/*keyword之间的编码用空格分开*/
			code[i++] = ' ';
		}
		hfmTree_code(head->lchild, a * 2, code);
		hfmTree_code(head->rchild, a * 2 + 1, code);
	}
}

int hfmTree_decode(HFMTree head, const char code[], char decode[])
{
	assert(head != NULL);
	HFMTree root = head,pre;
	int i = 0;
	int j = 0;
	
	while (code[i] == '0' || code[i] == '1')
	{
		if (code[i] == '0')
			head = head->lchild;
		else
			head = head->rchild;
		if (head->rchild == NULL )//訪问到了叶子节点
		{
			decode[j] = head->key;
			j++;
			head = root;
		}
		i++;
	}
	if (code[i] != '\0')//没有訪问到叶子节点
	{
		cout << "报文错误" << endl;
		return 0;
	}
	return 1;
}
输出结果为:

技术分享

















以上是关于赫夫曼树的构建编码译码解析的主要内容,如果未能解决你的问题,请参考以下文章

哈夫曼编码译码

哈夫曼编码译码

哈夫曼编码译码

Huffman树的构造及编码与译码的实现

哈夫曼编码和译码

数据结构===哈夫曼编码实现/C或者C++