加载中...

第十三天 树操作【下】


今天说下最后一种树,大家可否知道,文件压缩程序里面的核心结构,核心算法是什么?或许你知道,他就运用了赫夫曼树。

听说赫夫曼胜过了他的导师,被认为”青出于蓝而胜于蓝“,这句话也是我比较欣赏的,嘻嘻。

一  概念

    了解”赫夫曼树“之前,几个必须要知道的专业名词可要熟练记住啊。

    1: 结点的权

            “权”就相当于“重要度”,我们形象的用一个具体的数字来表示,然后通过数字的大小来决定谁重要,谁不重要。

    2: 路径

             树中从“一个结点"到“另一个结点“之间的分支。

    3: 路径长度

             一个路径上的分支数量。

    4: 树的路径长度

             从树的根节点到每个节点的路径长度之和。

    5: 节点的带权路径路劲长度

             其实也就是该节点到根结点的路径长度*该节点的权。

    6:   树的带权路径长度

             树中各个叶节点的路径长度*该叶节点的权的和,常用WPL(Weight Path Length)表示。

二: 构建赫夫曼树

        上面说了那么多,肯定是为下面做铺垫,这里说赫夫曼树,肯定是要说赫夫曼树咋好咋好,赫夫曼树是一种最优二叉树,

         因为他的WPL是最短的,何以见得?我们可以上图说话。

   

现在我们做一个WPL的对比:

图A: WPL= 5*2 + 7*2 +2*2+13*2=54

图B:WPL=5*3+2*3+7*2+13*1=48

 

我们对比一下,图B的WPL最短的,地球人已不能阻止WPL还能比“图B”的小,所以,“图B"就是一颗赫夫曼树,那么大家肯定

要问,如何构建一颗赫夫曼树,还是上图说话。

 

第一步: 我们将所有的节点都作为独根结点。

第二步:   我们将最小的C和A组建为一个新的二叉树,权值为左右结点之和。

第三步: 将上一步组建的新节点加入到剩下的节点中,排除上一步组建过的左右子树,我们选中B组建新的二叉树,然后取权值。

第四步: 同上。

 

三: 赫夫曼编码

      大家都知道,字符,汉字,数字在计算机中都是以0,1来表示的,相应的存储都是有一套编码方案来支撑的,比如ASC码。

 这样才能在"编码“和”解码“的过程中不会成为乱码,但是ASC码不理想的地方就是等长的,其实我们都想用较少的空间来存储

更多的东西,那么我们就要采用”不等长”的编码方案来存储,那么“何为不等长呢“?其实也就是出现次数比较多的字符我们采用短编码,

出现次数较少的字符我们采用长编码,恰好,“赫夫曼编码“就是不等长的编码。

    这里大家只要掌握赫夫曼树的编码规则:左子树为0,右子树为1,对应的编码后的规则是:从根节点到子节点

A: 111

B: 10

C: 110

D: 0

 

四: 实现

      不知道大家懂了没有,不懂的话多看几篇,下面说下赫夫曼的具体实现。

         第一步:构建赫夫曼树。

         第二步:对赫夫曼树进行编码。

         第三步:压缩操作。

         第四步:解压操作。

1:首先看下赫夫曼树的结构,这里字段的含义就不解释了。

  1. #region 赫夫曼树结构
  2. /// <summary>
  3. /// 赫夫曼树结构
  4. /// </summary>
  5. public class HuffmanTree
  6. {
  7. public int weight { get; set; }
  8.  
  9. public int parent { get; set; }
  10.  
  11. public int left { get; set; }
  12.  
  13. public int right { get; set; }
  14. }
  15. #endregion

2: 创建赫夫曼树,原理在上面已经解释过了,就是一步一步的向上搭建,这里要注意的二个性质定理:

         当叶子节点为N个,则需要N-1步就能搭建赫夫曼树。

         当叶子节点为N个,则赫夫曼树的节点总数为:(2*N)-1个。

  1. #region 赫夫曼树的创建
  2. /// <summary>
  3. /// 赫夫曼树的创建
  4. /// </summary>
  5. /// <param name="huffman">赫夫曼树</param>
  6. /// <param name="leafNum">叶子节点</param>
  7. /// <param name="weight">节点权重</param>
  8. public HuffmanTree[] CreateTree(HuffmanTree[] huffman, int leafNum, int[] weight)
  9. {
  10. //赫夫曼树的节点总数
  11. int huffmanNode = 2 * leafNum - 1;
  12.  
  13. //初始化节点,赋予叶子节点值
  14. for (int i = 0; i < huffmanNode; i++)
  15. {
  16. if (i < leafNum)
  17. {
  18. huffman[i].weight = weight[i];
  19. }
  20. }
  21.  
  22. //这里面也要注意,4个节点,其实只要3步就可以构造赫夫曼树
  23. for (int i = leafNum; i < huffmanNode; i++)
  24. {
  25. int minIndex1;
  26. int minIndex2;
  27. SelectNode(huffman, i, out minIndex1, out minIndex2);
  28.  
  29. //最后得出minIndex1和minindex2中实体的weight最小
  30. huffman[minIndex1].parent = i;
  31. huffman[minIndex2].parent = i;
  32.  
  33. huffman[i].left = minIndex1;
  34. huffman[i].right = minIndex2;
  35.  
  36. huffman[i].weight = huffman[minIndex1].weight + huffman[minIndex2].weight;
  37. }
  38.  
  39. return huffman;
  40. }
  41. #endregion
  42.  
  43. #region 选出叶子节点中最小的二个节点
  44. /// <summary>
  45. /// 选出叶子节点中最小的二个节点
  46. /// </summary>
  47. /// <param name="huffman"></param>
  48. /// <param name="searchNodes">要查找的结点数</param>
  49. /// <param name="minIndex1"></param>
  50. /// <param name="minIndex2"></param>
  51. public void SelectNode(HuffmanTree[] huffman, int searchNodes, out int minIndex1, out int minIndex2)
  52. {
  53. HuffmanTree minNode1 = null;
  54.  
  55. HuffmanTree minNode2 = null;
  56.  
  57. //最小节点在赫夫曼树中的下标
  58. minIndex1 = minIndex2 = 0;
  59.  
  60. //查找范围
  61. for (int i = 0; i < searchNodes; i++)
  62. {
  63. ///只有独根树才能进入查找范围
  64. if (huffman[i].parent == 0)
  65. {
  66. //如果为null,则认为当前实体为最小
  67. if (minNode1 == null)
  68. {
  69. minIndex1 = i;
  70.  
  71. minNode1 = huffman[i];
  72.  
  73. continue;
  74. }
  75.  
  76. //如果为null,则认为当前实体为最小
  77. if (minNode2 == null)
  78. {
  79. minIndex2 = i;
  80.  
  81. minNode2 = huffman[i];
  82.  
  83. //交换一个位置,保证minIndex1为最小,为后面判断做准备
  84. if (minNode1.weight > minNode2.weight)
  85. {
  86. //节点交换
  87. var temp = minNode1;
  88. minNode1 = minNode2;
  89. minNode2 = temp;
  90.  
  91. //下标交换
  92. var tempIndex = minIndex1;
  93. minIndex1 = minIndex2;
  94. minIndex2 = tempIndex;
  95.  
  96. continue;
  97. }
  98. }
  99. if (minNode1 != null && minNode2 != null)
  100. {
  101. if (huffman[i].weight <= minNode1.weight)
  102. {
  103. //将min1临时转存给min2
  104. minNode2 = minNode1;
  105. minNode1 = huffman[i];
  106.  
  107. //记录在数组中的下标
  108. minIndex2 = minIndex1;
  109. minIndex1 = i;
  110. }
  111. else
  112. {
  113. if (huffman[i].weight < minNode2.weight)
  114. {
  115. minNode2 = huffman[i];
  116.  
  117. minIndex2 = i;
  118. }
  119. }
  120. }
  121. }
  122. }
  123. }
  124. #endregion
3:对哈夫曼树进行编码操作,形成一套“模板”,效果跟ASC模板一样,不过一个是不等长,一个是等长。
  1. #region 赫夫曼编码
  2. /// <summary>
  3. /// 赫夫曼编码
  4. /// </summary>
  5. /// <param name="huffman"></param>
  6. /// <param name="leafNum"></param>
  7. /// <param name="huffmanCode"></param>
  8. public string[] HuffmanCoding(HuffmanTree[] huffman, int leafNum)
  9. {
  10. int current = 0;
  11.  
  12. int parent = 0;
  13.  
  14. string[] huffmanCode = new string[leafNum];
  15.  
  16. //四个叶子节点的循环
  17. for (int i = 0; i < leafNum; i++)
  18. {
  19. //单个字符的编码串
  20. string codeTemp = string.Empty;
  21.  
  22. current = i;
  23.  
  24. //第一次获取最左节点
  25. parent = huffman[current].parent;
  26.  
  27. while (parent != 0)
  28. {
  29. //如果父节点的左子树等于当前节点就标记为0
  30. if (current == huffman[parent].left)
  31. codeTemp += "0";
  32. else
  33. codeTemp += "1";
  34.  
  35. current = parent;
  36. parent = huffman[parent].parent;
  37. }
  38.  
  39. huffmanCode[i] = new string(codeTemp.Reverse().ToArray());
  40. }
  41. return huffmanCode;
  42. }
  43. #endregion

4:模板生成好了,我们就要对指定的测试数据进行压缩处理

  1. #region 对指定字符进行压缩
  2. /// <summary>
  3. /// 对指定字符进行压缩
  4. /// </summary>
  5. /// <param name="huffmanCode"></param>
  6. /// <param name="alphabet"></param>
  7. /// <param name="test"></param>
  8. public string Encode(string[] huffmanCode, string[] alphabet, string test)
  9. {
  10. //返回的0,1代码
  11. string encodeStr = string.Empty;
  12.  
  13. //对每个字符进行编码
  14. for (int i = 0; i < test.Length; i++)
  15. {
  16. //在模版里面查找
  17. for (int j = 0; j < alphabet.Length; j++)
  18. {
  19. if (test[i].ToString() == alphabet[j])
  20. {
  21. encodeStr += huffmanCode[j];
  22. }
  23. }
  24. }
  25.  
  26. return encodeStr;
  27. }
  28. #endregion

5: 数据进行还原操作。

  1. #region 对指定的二进制进行解压
  2. /// <summary>
  3. /// 对指定的二进制进行解压
  4. /// </summary>
  5. /// <param name="huffman"></param>
  6. /// <param name="leafNum"></param>
  7. /// <param name="alphabet"></param>
  8. /// <param name="test"></param>
  9. /// <returns></returns>
  10. public string Decode(HuffmanTree[] huffman, int huffmanNodes, string[] alphabet, string test)
  11. {
  12. string decodeStr = string.Empty;
  13.  
  14. //所有要解码的字符
  15. for (int i = 0; i < test.Length; )
  16. {
  17. int j = 0;
  18. //赫夫曼树结构模板(用于循环的解码单个字符)
  19. for (j = huffmanNodes - 1; (huffman[j].left != 0 || huffman[j].right != 0); )
  20. {
  21. if (test[i].ToString() == "0")
  22. {
  23. j = huffman[j].left;
  24. }
  25. if (test[i].ToString() == "1")
  26. {
  27. j = huffman[j].right;
  28. }
  29. i++;
  30. }
  31. decodeStr += alphabet[j];
  32. }
  33. return decodeStr;
  34. }
  35.  
  36. #endregion

最后上一下总的运行代码

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace HuffmanTree
  7. {
  8. class Program
  9. {
  10. static void Main(string[] args)
  11. {
  12. //有四个叶节点
  13. int leafNum = 4;
  14.  
  15. //赫夫曼树中的节点总数
  16. int huffmanNodes = 2 * leafNum - 1;
  17.  
  18. //各节点的权值
  19. int[] weight = { 5, 7, 2, 13 };
  20.  
  21. string[] alphabet = { "A", "B", "C", "D" };
  22.  
  23. string testCode = "DBDBDABDCDADBDADBDADACDBDBD";
  24.  
  25. //赫夫曼树用数组来保存,每个赫夫曼都作为一个实体存在
  26. HuffmanTree[] huffman = new HuffmanTree[huffmanNodes].Select(i => new HuffmanTree() { }).ToArray();
  27.  
  28. HuffmanTreeManager manager = new HuffmanTreeManager();
  29.  
  30. manager.CreateTree(huffman, leafNum, weight);
  31.  
  32. string[] huffmanCode = manager.HuffmanCoding(huffman, leafNum);
  33.  
  34. for (int i = 0; i < leafNum; i++)
  35. {
  36. Console.WriteLine("字符:{0},权重:{1},编码为:{2}", alphabet[i], huffman[i].weight, huffmanCode[i]);
  37. }
  38.  
  39. Console.WriteLine("原始的字符串为:" + testCode);
  40.  
  41. string encode = manager.Encode(huffmanCode, alphabet, testCode);
  42.  
  43. Console.WriteLine("被编码的字符串为:" + encode);
  44.  
  45. string decode = manager.Decode(huffman, huffmanNodes, alphabet, encode);
  46.  
  47. Console.WriteLine("解码后的字符串为:" + decode);
  48. }
  49. }
  50.  
  51. #region 赫夫曼树结构
  52. /// <summary>
  53. /// 赫夫曼树结构
  54. /// </summary>
  55. public class HuffmanTree
  56. {
  57. public int weight { get; set; }
  58.  
  59. public int parent { get; set; }
  60.  
  61. public int left { get; set; }
  62.  
  63. public int right { get; set; }
  64. }
  65. #endregion
  66.  
  67. /// <summary>
  68. /// 赫夫曼树的操作类
  69. /// </summary>
  70. public class HuffmanTreeManager
  71. {
  72. #region 赫夫曼树的创建
  73. /// <summary>
  74. /// 赫夫曼树的创建
  75. /// </summary>
  76. /// <param name="huffman">赫夫曼树</param>
  77. /// <param name="leafNum">叶子节点</param>
  78. /// <param name="weight">节点权重</param>
  79. public HuffmanTree[] CreateTree(HuffmanTree[] huffman, int leafNum, int[] weight)
  80. {
  81. //赫夫曼树的节点总数
  82. int huffmanNode = 2 * leafNum - 1;
  83.  
  84. //初始化节点,赋予叶子节点值
  85. for (int i = 0; i < huffmanNode; i++)
  86. {
  87. if (i < leafNum)
  88. {
  89. huffman[i].weight = weight[i];
  90. }
  91. }
  92.  
  93. //这里面也要注意,4个节点,其实只要3步就可以构造赫夫曼树
  94. for (int i = leafNum; i < huffmanNode; i++)
  95. {
  96. int minIndex1;
  97. int minIndex2;
  98. SelectNode(huffman, i, out minIndex1, out minIndex2);
  99.  
  100. //最后得出minIndex1和minindex2中实体的weight最小
  101. huffman[minIndex1].parent = i;
  102. huffman[minIndex2].parent = i;
  103.  
  104. huffman[i].left = minIndex1;
  105. huffman[i].right = minIndex2;
  106.  
  107. huffman[i].weight = huffman[minIndex1].weight + huffman[minIndex2].weight;
  108. }
  109.  
  110. return huffman;
  111. }
  112. #endregion
  113.  
  114. #region 选出叶子节点中最小的二个节点
  115. /// <summary>
  116. /// 选出叶子节点中最小的二个节点
  117. /// </summary>
  118. /// <param name="huffman"></param>
  119. /// <param name="searchNodes">要查找的结点数</param>
  120. /// <param name="minIndex1"></param>
  121. /// <param name="minIndex2"></param>
  122. public void SelectNode(HuffmanTree[] huffman, int searchNodes, out int minIndex1, out int minIndex2)
  123. {
  124. HuffmanTree minNode1 = null;
  125.  
  126. HuffmanTree minNode2 = null;
  127.  
  128. //最小节点在赫夫曼树中的下标
  129. minIndex1 = minIndex2 = 0;
  130.  
  131. //查找范围
  132. for (int i = 0; i < searchNodes; i++)
  133. {
  134. ///只有独根树才能进入查找范围
  135. if (huffman[i].parent == 0)
  136. {
  137. //如果为null,则认为当前实体为最小
  138. if (minNode1 == null)
  139. {
  140. minIndex1 = i;
  141.  
  142. minNode1 = huffman[i];
  143.  
  144. continue;
  145. }
  146.  
  147. //如果为null,则认为当前实体为最小
  148. if (minNode2 == null)
  149. {
  150. minIndex2 = i;
  151.  
  152. minNode2 = huffman[i];
  153.  
  154. //交换一个位置,保证minIndex1为最小,为后面判断做准备
  155. if (minNode1.weight > minNode2.weight)
  156. {
  157. //节点交换
  158. var temp = minNode1;
  159. minNode1 = minNode2;
  160. minNode2 = temp;
  161.  
  162. //下标交换
  163. var tempIndex = minIndex1;
  164. minIndex1 = minIndex2;
  165. minIndex2 = tempIndex;
  166.  
  167. continue;
  168. }
  169. }
  170. if (minNode1 != null && minNode2 != null)
  171. {
  172. if (huffman[i].weight <= minNode1.weight)
  173. {
  174. //将min1临时转存给min2
  175. minNode2 = minNode1;
  176. minNode1 = huffman[i];
  177.  
  178. //记录在数组中的下标
  179. minIndex2 = minIndex1;
  180. minIndex1 = i;
  181. }
  182. else
  183. {
  184. if (huffman[i].weight < minNode2.weight)
  185. {
  186. minNode2 = huffman[i];
  187.  
  188. minIndex2 = i;
  189. }
  190. }
  191. }
  192. }
  193. }
  194. }
  195. #endregion
  196.  
  197. #region 赫夫曼编码
  198. /// <summary>
  199. /// 赫夫曼编码
  200. /// </summary>
  201. /// <param name="huffman"></param>
  202. /// <param name="leafNum"></param>
  203. /// <param name="huffmanCode"></param>
  204. public string[] HuffmanCoding(HuffmanTree[] huffman, int leafNum)
  205. {
  206. int current = 0;
  207.  
  208. int parent = 0;
  209.  
  210. string[] huffmanCode = new string[leafNum];
  211.  
  212. //四个叶子节点的循环
  213. for (int i = 0; i < leafNum; i++)
  214. {
  215. //单个字符的编码串
  216. string codeTemp = string.Empty;
  217.  
  218. current = i;
  219.  
  220. //第一次获取最左节点
  221. parent = huffman[current].parent;
  222.  
  223. while (parent != 0)
  224. {
  225. //如果父节点的左子树等于当前节点就标记为0
  226. if (current == huffman[parent].left)
  227. codeTemp += "0";
  228. else
  229. codeTemp += "1";
  230.  
  231. current = parent;
  232. parent = huffman[parent].parent;
  233. }
  234.  
  235. huffmanCode[i] = new string(codeTemp.Reverse().ToArray());
  236. }
  237. return huffmanCode;
  238. }
  239. #endregion
  240.  
  241. #region 对指定字符进行压缩
  242. /// <summary>
  243. /// 对指定字符进行压缩
  244. /// </summary>
  245. /// <param name="huffmanCode"></param>
  246. /// <param name="alphabet"></param>
  247. /// <param name="test"></param>
  248. public string Encode(string[] huffmanCode, string[] alphabet, string test)
  249. {
  250. //返回的0,1代码
  251. string encodeStr = string.Empty;
  252.  
  253. //对每个字符进行编码
  254. for (int i = 0; i < test.Length; i++)
  255. {
  256. //在模版里面查找
  257. for (int j = 0; j < alphabet.Length; j++)
  258. {
  259. if (test[i].ToString() == alphabet[j])
  260. {
  261. encodeStr += huffmanCode[j];
  262. }
  263. }
  264. }
  265.  
  266. return encodeStr;
  267. }
  268. #endregion
  269.  
  270. #region 对指定的二进制进行解压
  271. /// <summary>
  272. /// 对指定的二进制进行解压
  273. /// </summary>
  274. /// <param name="huffman"></param>
  275. /// <param name="leafNum"></param>
  276. /// <param name="alphabet"></param>
  277. /// <param name="test"></param>
  278. /// <returns></returns>
  279. public string Decode(HuffmanTree[] huffman, int huffmanNodes, string[] alphabet, string test)
  280. {
  281. string decodeStr = string.Empty;
  282.  
  283. //所有要解码的字符
  284. for (int i = 0; i < test.Length; )
  285. {
  286. int j = 0;
  287. //赫夫曼树结构模板(用于循环的解码单个字符)
  288. for (j = huffmanNodes - 1; (huffman[j].left != 0 || huffman[j].right != 0); )
  289. {
  290. if (test[i].ToString() == "0")
  291. {
  292. j = huffman[j].left;
  293. }
  294. if (test[i].ToString() == "1")
  295. {
  296. j = huffman[j].right;
  297. }
  298. i++;
  299. }
  300. decodeStr += alphabet[j];
  301. }
  302. return decodeStr;
  303. }
  304.  
  305. #endregion
  306. }
  307. }


还没有评论.