這一篇要總結的是樹中的最后一種,即哈夫曼樹,我想從以下幾點對其進行總結:
1,什么是哈夫曼樹?
2,如何構建哈夫曼樹?
3,哈夫曼編碼?
4,算法實現?
一,什么是哈夫曼樹
什么是哈夫曼樹呢?
哈夫曼樹是一種帶權路徑長度最短的二叉樹,也稱為最優二叉樹。下面用一幅圖來說明。
它們的帶權路徑長度分別為:
圖a: WPL=5*2+7*2+2*2+13*2=54
圖b: WPL=5*3+2*3+7*2+13*1=48
可見,圖b的帶權路徑長度較小,我們可以證明圖b就是哈夫曼樹(也稱為最優二叉樹)。
二,如何構建哈夫曼樹
一般可以按下面步驟構建:
1,將所有左,右子樹都為空的作為根節點。
2,在森林中選出兩棵根節點的權值最小的樹作為一棵新樹的左,右子樹,且置新樹的附加根節點的權值為其左,右子樹上根節點的權值之和。注意,左子樹的權值應小於右子樹的權值。
3,從森林中刪除這兩棵樹,同時把新樹加入到森林中。
4,重復2,3步驟,直到森林中只有一棵樹為止,此樹便是哈夫曼樹。
下面是構建哈夫曼樹的圖解過程:
三,哈夫曼編碼
利用哈夫曼樹求得的用於通信的二進制編碼稱為哈夫曼編碼。樹中從根到每個葉子節點都有一條路徑,對路徑上的各分支約定指向左子樹的分支表示”0”碼,指向右子樹的分支表示“1”碼,取每條路徑上的“0”或“1”的序列作為各個葉子節點對應的字符編碼,即是哈夫曼編碼。
就拿上圖例子來說:
A,B,C,D對應的哈夫曼編碼分別為:111,10,110,0
用圖說明如下:
記住,設計電文總長最短的二進制前綴編碼,就是以n個字符出現的頻率作為權構造一棵哈夫曼樹,由哈夫曼樹求得的編碼就是哈夫曼編碼。
四,算法實現
C#版:
namespace HuffTree.CSharp { class Program { static void Main(string[] args) { //四個葉子節點 int leafNum = 4; //赫夫曼樹的節點總數 int totalNodes = 2 * leafNum - 1; //各葉子節點的權值 int[] weight = new int[] { 5,7,2,13}; //各葉子節點的值 string[] alphabet = new string[] { "A","B","C","D"}; //初始化赫夫曼樹 HuffmanTree[] huffmanTree = new HuffmanTree[totalNodes].Select(p => new HuffmanTree() { }).ToArray(); //構建赫夫曼樹 HuffmanTreeBLL.Create(huffmanTree,leafNum,weight); //赫夫曼編碼 string[] huffmanCode = HuffmanTreeBLL.Coding(huffmanTree,leafNum); //打印結果 PrintResult(alphabet,huffmanTree,huffmanCode,leafNum); Console.ReadKey(); } /// <summary> /// 打印結果 /// </summary> /// <param name="alphabet"></param> /// <param name="huffmanTree"></param> /// <param name="huffmanCode"></param> /// <param name="leafNum"></param> private static void PrintResult(string[] alphabet,HuffmanTree[] huffmanTree,string[] huffmanCode,int leafNum) { if (alphabet.Count() < 1 || huffmanTree.Count() < 1 || huffmanCode.Count() < 1) return; for (int i = 0; i < leafNum; i++) { Console.WriteLine("字符:{0},權重值:{1},赫夫曼編碼:{2}",alphabet[i],huffmanTree[i].Weight,huffmanCode[i]); } } } } namespace DS.BLL { /// <summary> /// 描述:赫夫曼樹操作類 /// 作者:魯寧 /// 時間:2013/9/17 18:14:33 /// </summary> public class HuffmanTreeBLL { /// <summary> /// 構建赫夫曼樹 /// 思路:一步一步向上搭建 /// </summary> /// <param name="huffmanTree">待操作的赫夫曼樹</param> /// <param name="leafNum">葉節點數量</param> /// <param name="weight">節點權重值</param> /// <returns>構建好的赫夫曼樹</returns> public static HuffmanTree[] Create(HuffmanTree[] huffmanTree, int leafNum, int[] weight) { //獲取赫夫曼樹結點總數 int totalNodes = 2 * leafNum - 1; InitLeafNode(huffmanTree,leafNum,weight); //構造赫夫曼樹(4個節點只需要3步就可以完成構建) for (int i = leafNum; i < totalNodes; i++) { //獲取權重最小的兩個葉子節點的下標 int minIndex1 = -1; int minIndex2 = -1; SelectNode(huffmanTree,i,ref minIndex1,ref minIndex2); huffmanTree[minIndex1].Parent = i; huffmanTree[minIndex2].Parent = i; huffmanTree[i].Left = minIndex1; huffmanTree[i].Right = minIndex2; huffmanTree[i].Weight = huffmanTree[minIndex1].Weight + huffmanTree[minIndex2].Weight; } return huffmanTree; } /// <summary> /// 赫夫曼編碼 /// 思路:左子樹為0,右子樹為1,對應的編碼后的規則是:從根節點到子節點 /// </summary> /// <param name="huffmanTree">待操作的赫夫曼樹</param> /// <param name="leafNum">葉子節點的數量</param> /// <returns>赫夫曼編碼</returns> public static string[] Coding(HuffmanTree[] huffmanTree, int leafNum) { string[] huffmanCode= new string[leafNum]; //當前節點下標 int current = 0; //父節點下標 int parent = 0; for (int i = 0; i < leafNum; i++) { string codeTemp = string.Empty; current = i; //第一次獲取最左節點 parent = huffmanTree[current].Parent; while (parent != 0) { if (huffmanTree[parent].Left == current) codeTemp += "0"; else codeTemp += "1"; current = parent; parent = huffmanTree[parent].Parent; } huffmanCode[i] = new string(codeTemp.Reverse().ToArray()); } return huffmanCode; } /// <summary> /// 初始化葉節點 /// </summary> /// <param name="huffmanTree"></param> /// <param name="leafNum"></param> /// <param name="weight"></param> private static void InitLeafNode(HuffmanTree[] huffmanTree, int leafNum, int[] weight) { if (huffmanTree == null || leafNum<1 || weight.Count()<1) return; for (int i = 0; i < leafNum; i++) { huffmanTree[i].Weight = weight[i]; } } /// <summary> /// 獲取葉子節點中權重最小的兩個節點 /// </summary> /// <param name="huffmanTree">待操作的赫夫曼</param> /// <param name="searchNode">要查找的節點數</param> /// <param name="minIndex1"></param> /// <param name="minIndex2"></param> private static void SelectNode(HuffmanTree[] huffmanTree, int searchNode, ref int minIndex1, ref int minIndex2) { HuffmanTree minNode1 = null; HuffmanTree minNode2 = null; for (int i = 0; i < searchNode; i++) { //只查找獨根樹葉子節點 if (huffmanTree[i].Parent == 0) { //如果為null,則表示當前節葉子節點最小 if (minNode1 == null) { minIndex1 = i; minNode1= huffmanTree[i]; continue; } if (minNode2 == null) { minIndex2 = i; minNode2= huffmanTree[i]; //交換位置,確保minIndex1為最小 if (minNode1.Weight >= minNode2.Weight) { //節點交換 var temp = minNode1; minNode1 = minNode2; minNode2 = temp; //交換下標 var tempIndex = minIndex1; minIndex1 = minIndex2; minIndex2 = tempIndex; continue; } } if (minNode1 != null && minNode2 != null) { if (huffmanTree[i].Weight < minNode1.Weight) //注意,不能是“<=” { //將min1臨時轉存給min2 minNode2 = minNode1; minNode1 = huffmanTree[i]; //記錄在數組中的下標 minIndex2 = minIndex1; minIndex1 = i; } else { if (huffmanTree[i].Weight < minNode2.Weight) { minNode2= huffmanTree[i]; minIndex2 = i; } } } } } } } /// <summary> /// 赫夫曼樹存儲結構 /// </summary> public class HuffmanTree { public int Weight { get; set; } //權值 public int Parent { get; set; } //父節點 public int Left { get; set; } //左孩子節點 public int Right { get; set; } //右孩子節點 } }
程序輸出結果為: