1. 程式人生 > >赫夫曼樹的構建、編碼、譯碼解析

赫夫曼樹的構建、編碼、譯碼解析

fmt 獲取 插入 typedef child 構造方法 clas name lin

當你開始看這篇博文的時候。我相信你對樹及二叉樹的基本概念已有所了解。我在這裏就不再贅述。

我們主要對赫

夫曼樹的特點、構建、編碼、譯碼做一個具體的介紹,並附有代碼,全部函數代碼都通過了測試。我不保證全部代碼是最優的(畢竟是我一個人苦思冥想出來的,我相信在大家的集思廣益之下還有優化的空間),但我保證全部代碼是正確的。

一、赫夫曼樹的特點

赫夫曼樹又稱作最優二叉樹,是一類帶權路徑長度最短的樹。

首先給出路徑和路徑長度的概念。從樹中一個節點到

還有一個節點之間的分支構成這兩個節點之間的路徑。路徑上的分支數目稱作路徑長度樹的路徑長度是從樹根到每個葉子節點的路徑長度之和。

節點的帶全路徑長度

為該節點到樹根的路徑長度乘以該節點上的權值。樹的帶權路徑長度為樹中全部葉子節點的帶權路徑長度之和,通常記做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;
}
輸出結果為:

技術分享

赫夫曼樹的構建、編碼、譯碼解析