1. 程式人生 > >哈夫曼樹及哈夫曼編碼和解碼

哈夫曼樹及哈夫曼編碼和解碼

哈夫曼樹,又稱最優樹,是帶權路徑最小的樹。
基本概念:
節點間的路徑長度:兩個節點間所包含的邊的數目。
樹的路徑長度:從根到樹中任意節點的路徑長度之和。
權:將節點賦予一定的量值,該量值成為權。
樹的帶權路徑長度:樹中所有葉子結點的帶權路徑長度。

哈夫曼演算法:給定一個儲存權值的陣列,求最優樹的演算法。對於此權值陣列找出其中最小的權值和第二小的權值,用這兩個權值建立樹,並把這兩個權值相加所得作為一個新權值放入到原陣列中(注意:此時陣列中已經去掉了剛才用過的權值),重複以上操作即可建立最優樹。

哈弗曼編碼和解碼的優點不再贅述。

哈夫曼樹的實現
1.建立結構體,比較簡單

typedef
struct Tree{ struct Tree *left; struct Tree *right; int data; }Tree;

2.利用權值陣列建立哈夫曼樹(程式碼註釋已相對清晰)

Tree *create(int *a,int n){//對陣列 a 進行實現哈夫曼樹 a 中存放的為權值, n 為陣列的長度
	Tree *tree;
	Tree **b;
	int i,j; 
	b = malloc(sizeof(Tree)*n);//動態一維陣列的申請來儲存權值 
	for(i = 0; i < n; i++){
		b[i] = malloc(sizeof(Tree)
); b[i]->data = a[i]; b[i]->left = b[i]->right = NULL; } //建立哈夫曼樹 for(i = 1; i < n; i++){ int small1 = -1,small2;//small1指向權值最小,small2是第二小,其初始指向分別是陣列的前兩個元素 //注意前兩個元素並不一定是最小的和第二小的 //下面一個 for 迴圈是讓small1指向第一個權值,small2指向第二個權值 for(j = 0; j < n; j++){ if(b[j] != NULL &&
small1 == -1){ small1 = j; continue; } if(b[j] != NULL){ small2 = j; break; } } //接下來就是對陣列剩下的權值逐個與small1、small2比較,找出最小與第二小的權值 for(j = small2; j < n; j++){ if(b[j] != NULL){ if(b[j]->data < b[small1]->data){ small2 = small1; small1 = j; } else if(b[small2]->data > b[j]->data){ small2 = j; } } } //由兩個最小權值建立新樹,tree 指向根節點 tree = malloc(sizeof(Tree)); tree->data = b[small1]->data + b[small2]->data; tree->left = b[small1]; tree->right = b[small2]; //以下兩步是用於重複執行 b[small1] = tree; b[small2] = NULL; } free(b); return tree; }

3.列印哈夫曼樹

//列印哈夫曼樹 
void print(Tree *tree){
	if(tree){
		printf("%d ",tree->data);
		if(tree->left && tree->right){
			printf("(");
			print(tree->left);
			if(tree->right)
			printf(",");
			print(tree->right);
			printf(")");
		}
	}
}

4.獲取哈夫曼樹的帶權路徑長度

//獲得哈夫曼樹的帶權路徑長度
int getWeight(Tree *tree,int len){
	if(!tree)
	return 0;
	if(!tree->left && !tree->right)//訪問到葉子結點 
	return tree->data * len;
	return getWeight(tree->left, len + 1) + getWeight(tree->right,len + 1);//訪問到非葉子結點 
}

5.下面便是哈夫曼編碼與解碼,思路也較為簡單

//哈夫曼編碼 
void getCoding(Tree *tree,int len){
	if(!tree)
	return;
	static int a[20]; //定義靜態陣列a,儲存每個葉子的編碼,陣列長度至少是樹深度減一
	int i;
	if(!tree->left && !tree->right){
		printf(" %d  的哈夫曼編碼為:",tree->data);
		for(i = 0; i < len; i++)
		printf("%d",a[i]);
		printf("\n");
	}
	else{//訪問到非葉子結點時分別向左右子樹遞迴呼叫,並把分支上的0、1編碼儲存到陣列a ,的對應元素中,向下深入一層時len值增1 
		a[len] = 0;
		getCoding(tree->left, len + 1);
		a[len] = 1;
		getCoding(tree->right, len + 1);
		
	}
}

6.哈夫曼解碼,比較容易實現
節點與左子節點之間記為 0 ,節點與右節點之間記為 1 ,如圖
在這裡插入圖片描述

//哈夫曼解碼
void Decoding(Tree *tree){
	printf("請輸入要解碼的字串\n");
	char ch[100];//輸入的待解碼的字串 
	gets(ch);
	int i;
	int num[100];//用於儲存字串對應的0 1 編碼對應的節點 
	Tree *cur;
	for(i = 0; i < strlen(ch); i++){
		if(ch[i] == '0')
		num[i] = 0;
		else
		num[i] = 1;
	}
	
	if(tree){
		i = 0;
		while(i < strlen(ch)){
			cur = tree;
			while(cur->left && cur->right){
				
				if(num[i] == 0)
				cur = cur->left;
				else
				cur = cur->right;
				i++;
			}
			printf("%d",cur->data);
		}
		
	}
} 

完整程式碼如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Tree{
	struct Tree *left;
	struct Tree *right;
	int data;
}Tree;


Tree *create(int *a,int n){//對陣列 a 進行實現哈夫曼樹 a 中存放的為權值, n 為陣列的長度
	Tree *tree;
	Tree **b;
	int i,j; 
	b = malloc(sizeof(Tree)*n);//動態一維陣列的申請來儲存權值 
	for(i = 0; i < n; i++){
		b[i] = malloc(sizeof(Tree));
		b[i]->data = a[i];
		b[i]->left = b[i]->right = NULL;
	} 
	//建立哈夫曼樹
	for(i = 1; i < n; i++){
		int small1 = -1,small2;//small1指向權值最小,small2是第二小,其初始指向分別是陣列的前兩個元素
								//注意前兩個元素並不一定是最小的和第二小的
		//下面一個 for 迴圈是讓small1指向第一個權值,small2指向第二個權值 
		for(j = 0; j < n; j++){  
			if(b[j] != NULL && small1 == -1){
				small1 = j;
				continue;
			}
			if(b[j] != NULL){
				small2 = j;
				break;
			}
		} 
		//接下來就是對陣列剩下的權值逐個與small1、small2比較,找出最小與第二小的權值 
		for(j = small2; j < n; j++){
			if(b[j] != NULL){
				if(b[j]->data < b[small1]->data){
					small2 = small1;
					small1 = j;
				}
				else if(b[small2]->data > b[j]->data){
					small2 = j;
				}
			}
		} 
		
		//由兩個最小權值建立新樹,tree 指向根節點 
		tree = malloc(sizeof(Tree));
		tree->data = b[small1]->data + b[small2]->data;
		tree->left = b[small1];
		tree->right = b[small2];
		
		//以下兩步是用於重複執行 
		b[small1] = tree;
		b[small2] = NULL; 
		
	} 
	
	free(b);
	return tree;
}

//列印哈夫曼樹 
void print(Tree *tree){
	if(tree){
		printf("%d ",tree->data);
		if(tree->left && tree->right){
			printf("(");
			print(tree->left);
			if(tree->right)
			printf(",");
			print(tree->right);
			printf(")");
		}
	}
}

//獲得哈夫曼樹的帶權路徑長度
int getWeight(Tree *tree,int len){
	if(!tree)
	return 0;
	if(!tree->left && !tree->right)//訪問到葉子結點 
	return tree->data * len;
	return getWeight(tree->left, len + 1) + getWeight(tree->right,len + 1);//訪問到非葉子結點 
}

//哈夫曼編碼 
void getCoding(Tree *tree,int len){
	if(!tree)
	return;
	static int a[20]; //定義靜態陣列a,儲存每個葉子的編碼,陣列長度至少是樹深度減一
	int i;
	if(!tree->left && !tree->right){
		printf(" %d  的哈夫曼編碼為:",tree->data);
		for(i = 0; i < len; i++)
		printf("%d",a[i]);
		printf("\n");
	}
	else{//訪問到非葉子結點時分別向左右子樹遞迴呼叫,並把分支上的0、1編碼儲存到陣列a ,的對應元素中,向下深入一層時len值增1 
		a[len] = 0;
		getCoding(tree->left, len + 1);
		a[len] = 1;
		getCoding(tree->right, len + 1);
		
	}
}

//哈夫曼解碼
void Decoding(Tree *tree){
	printf("請輸入要解碼的字串\n");
	char ch[100];//輸入的待解碼的字串 
	gets(ch);
	int i;
	int num[100];//用於儲存字串對應的0 1 編碼對應的節點 
	Tree *cur;
	for(i = 0; i < strlen(ch); i++){
		if(ch[i] == '0')
		num[i] = 0;
		else
		num[i] = 1;
	}
	
	if(tree){
		i = 0;
		while(i < strlen(ch)){
			cur = tree;
			while(cur->left && cur->right){
				
				if(num[i] == 0)
				cur = cur->left;
				else
				cur = cur->right;
				i++;
			}
			printf("%d",cur->data);
		}
		
	}
} 

int main(int argc, char *argv[]) {
	int a[4] = {2,6,7,3};
	Tree *tree = create(a,4);
	print(tree);
	printf("\n哈夫曼樹的權值為:");
	printf("%d\n",getWeight(tree,0));
	getCoding(tree,0);
	printf("解碼時請參照上方編碼\n");
	Decoding(tree); 
}

執行結果如下
在這裡插入圖片描述