哈夫曼編解碼原理與實現【轉載】

1. 哈夫曼編解碼原理
霍夫曼編碼(Huffman Coding)是一種編碼方法,霍夫曼編碼是可變字長編碼(VLC)的一種。

霍夫曼編碼使用變長編碼表對源符號(如文件中的一個字母)進行編碼,其中變長編碼表是通過一種評估來源符號出現機率的方法得到的,出現機率高的字母使用較短的編碼,反之出現機率低的則使用較長的編碼,這便使編碼之後的字符串的平均長度、期望值降低,從而達到無損壓縮數據的目的。

霍夫曼編碼的具體步驟如下:

  1. 將信源符號的概率按減小的順序排隊。
  2. 把兩個最小的概率相加,並繼續這一步驟,始終將較高的概率分支放在右邊,直到最後變成概率1。
  3. 畫出由概率1處到每個信源符號的路徑,順序記下沿路徑的0和1,所得就是該符號的霍夫曼碼字。
  4. 將每對組合的左邊一個指定爲0,右邊一個指定爲1(或相反)。

:現有一個由5個不同符號組成的30個符號的字符串:

BABACAC ADADABB CBABEBE DDABEEEBB

  • 首先計算出每個字符出現的次數(概率):
字符 次數
B 10
A 8
C 3
D 4
E 5
  • 把出現次數(概率)最小的兩個相加,並作爲左右子樹,重複此過程,直到概率值爲1
    在這裏插入圖片描述
    第一次:將概率最低值3和4相加,組合成7:
    在這裏插入圖片描述
    第二次:將最低值5和7相加,組合成12:
    在這裏插入圖片描述
    第三次:將8和10相加,組合成18:
    在這裏插入圖片描述
    第四次:將最低值12和18相加,結束組合:
    在這裏插入圖片描述
  • 將每個二叉樹的左邊指定爲0,右邊指定爲1。
    在這裏插入圖片描述
  • 沿二叉樹頂部到每個字符路徑,獲得每個符號的編碼。
字符 次數 編碼
B 10 11
A 8 10
C 3 010
D 4 011
E 5 00

我們可以看到出現次數(概率)越多的會越在上層,編碼也越短,出現頻率越少的就越在下層,編碼也越長。當我們編碼的時候,我們是按“bit”來編碼的,解碼也是通過bit來完成,如果我們有這樣的bitset “10111101100″ 那麼其解碼後就是 “ABBDE”。所以,我們需要通過這個二叉樹建立我們Huffman編碼和解碼的字典表。

這裏需要注意的是,Huffman編碼使得每一個字符的編碼都與另一個字符編碼的前一部分不同,不會出現像’A’:00, ’B’:001,這樣的情況,解碼也不會出現衝突。

霍夫曼編碼的侷限性
利用霍夫曼編碼,每個符號的編碼長度只能爲整數,所以如果源符號集的概率分佈不是2負n次方的形式,則無法達到熵極限;輸入符號數受限於可實現的碼錶尺寸;譯碼複雜;需要實現知道輸入符號集的概率分佈;沒有錯誤保護功能。

2.哈夫曼編解碼實現(C++)

#include<iostream>  
#include<string>  
using namespace std;

struct Node
{
	double weight;
	string ch;
	string code;
	int lchild, rchild, parent;
};

void Select(Node huffTree[], int *a, int *b, int n)//找權值最小的兩個a和b,且無父節點的兩個節點合併
{
	int i;
	double weight = 0; //找最小的數
	for (i = 0; i < n; i++)
	{
		if (huffTree[i].parent != -1)     //判斷節點是否已經選過
			continue;
		else
		{
			if (weight == 0)
			{
				weight = huffTree[i].weight;
				*a = i;
			}
			else
			{
				if (huffTree[i].weight < weight)
				{
					weight = huffTree[i].weight;
					*a = i;
				}
			}
		}
	}

	weight = 0; //找第二小的數
	for (i = 0; i < n; i++)
	{
		if (huffTree[i].parent != -1 || (i == *a))//排除已選過的數
			continue;
		else
		{
			if (weight == 0)
			{
				weight = huffTree[i].weight;
				*b = i;
			}
			else
			{
				if (huffTree[i].weight < weight)
				{
					weight = huffTree[i].weight;
					*b = i;
				}
			}
		}
	}

	//int temp;
	//if (huffTree[*a].weight > huffTree[*b].weight)  //小的數放左邊
	//{
	//	temp = *a;
	//	*a = *b;
	//	*b = temp;
	//}
}

void Huff_Tree(Node huffTree[], int w[], string ch[], int n)
{
	//初始過程 2倍的節點數
	for (int i = 0; i < 2 * n - 1; i++) 
	{
		huffTree[i].parent = -1;
		huffTree[i].lchild = -1;
		huffTree[i].rchild = -1;
		huffTree[i].code = "";
	}
	for (int i = 0; i < n; i++)
	{
		huffTree[i].weight = w[i];
		huffTree[i].ch = ch[i];
	}

	//構建哈夫曼樹
	for (int k = n; k < 2 * n - 1; k++)
	{
		int i1 = 0;
		int i2 = 0;
		Select(huffTree, &i1, &i2, k); //將i1,i2節點合成節點k
		huffTree[i1].parent = k;
		huffTree[i2].parent = k;
		huffTree[k].weight = huffTree[i1].weight + huffTree[i2].weight;
		huffTree[k].lchild = i1;
		huffTree[k].rchild = i2;
	}
}

void Huff_Code(Node huffTree[], int n)
{
	int i, j, k;
	string s = "";
	for (i = 0; i < n; i++)
	{
		s = "";
		j = i;
		while (huffTree[j].parent != -1) //從葉子往上找到根節點
		{
			k = huffTree[j].parent;
			if (j == huffTree[k].lchild) //如果是根的左孩子,則記爲0
			{
				s = s + "0";
			}
			else
			{
				s = s + "1";
			}
			j = huffTree[j].parent;
		}
		cout << "字符 " << huffTree[i].ch << " 的編碼:";
		for (int l = s.size() - 1; l >= 0; l--)
		{
			cout << s[l];
			huffTree[i].code += s[l]; //保存編碼
		}
		cout << endl;
	}
}

string Huff_Decode(Node huffTree[], int n, string s)
{
	cout << "解碼後爲:";
	string temp = "", str = "";//保存解碼後的字符串
	for (int i = 0; i < s.size(); i++)
	{
		temp = temp + s[i];
		for (int j = 0; j < n; j++)
		{
			if (temp == huffTree[j].code)
			{
				str = str + huffTree[j].ch;
				temp = "";
				break;
			}
			else if (i == s.size() - 1 && j == n - 1 && temp != "")//全部遍歷後沒有
			{
				str = "解碼錯誤!";
			}
		}
	}
	return str;
}

int main()
{
	//編碼過程
	const int n = 5;
	Node huffTree[2 * n];
	string str[] = { "A", "B", "C", "D", "E" };
	int w[] = { 8, 10, 3, 4, 5 };
	Huff_Tree(huffTree, w, str, n);
	Huff_Code(huffTree, n);
	//解碼過程
	string s;
	cout << "輸入編碼:";
	cin >> s;
	cout << Huff_Decode(huffTree, n, s) << endl;;
	system("pause");
	return 0;
}

結果:
在這裏插入圖片描述


以上博文整理自:

  1. 理論部分:https://www.cnblogs.com/gyk666/p/6851821.html
  2. 實現部分:https://blog.csdn.net/xgf415/article/details/52628073
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章