zoukankan      html  css  js  c++  java
  • 算法系列15天速成——第十三天 树操作【下】

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

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

    一  概念

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

        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

    最后上一下总的运行代码

    View Code
      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 }



  • 相关阅读:
    [转载]windows下mongodb安装与使用整理
    CentOS SVN 服务器搭建
    linux下expect使用教程
    PHP时间格式控制符对照表
    WebGoat学习——SQL注入(SQL Injection)
    跨站脚本攻击(Cross‐Site Scripting (XSS))实践
    WebGoat学习——跨站请求伪造(Cross Site Request Forgery (CSRF))
    WebGoat学习——跨站脚本攻击(Cross‐Site Scripting (XSS))
    编程方式取得Spring上下文的Properties
    网站页面打开浏览器table中显示图片
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/2294099.html
Copyright © 2011-2022 走看看