哈夫曼算法 原理解析--通俗易懂篇(实战压缩篇)(二)

上一篇跟大伙分享了 哈夫曼算法的理论, 今儿我们一起实战一下,如何实现用哈夫曼算法来进行压缩。 哈夫曼理论篇

实战思路

1.先定义哈夫曼树的类 

   public class HfNode
    {
        //当前节点的权重
        public int nodevalue { set; get; }       
         
        /// <summary>
        /// 元素的值 父节点此值为空
        /// </summary>
        public string Text { set; get; }

        /// <summary>
        /// 当前树枝的值   左0右1 如果是root 则为空
        /// </summary>
        public int? setmsvalue { set; get; }

        /// <summary>
        /// 右节点
        /// </summary>
        public HfNode rightnode { set; get; }

        /// <summary>
        /// 左节点
        /// </summary>
        public HfNode leftnode { set; get; }
    }

2.将压缩的原数据转换成字符串进行 分组统计

 



        /// <summary>
        /// 字符串分组
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        List<HfNode> GroupbyData(string str)
        {
            //对数据进行分组 
            return str.ToCharArray().GroupBy(i => i).Select(i => new HfNode
            {
                nodevalue = i.Count(),
                Text = i.Key.ToString()
            }).ToList();
        }

3.根据分组后的集合 构建哈夫曼树对象

  HfNode AssemblyNode(IList<HfNode> list)
        {

            while (list.Count > 1)
            {
                list = list.OrderBy(i => i.nodevalue).ToList();
                var left = list[0];
                var right = list[1];
                list.RemoveAt(0);
                list.RemoveAt(0);
                list.Add(Assemblychildren(left, right));

            }

            return list[0];
        }

        /// <summary>
        /// 组装子节点
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        HfNode Assemblychildren(HfNode left, HfNode right)
        {
            left.setmsvalue = 0;
            right.setmsvalue = 1;

            return new HfNode
            {
                leftnode = left,
                rightnode = right,
                nodevalue = left.nodevalue + right.nodevalue
            };

        }

4.找出哈夫曼树的每个元素树枝的路径

  void Assemblyelementcollection(HfNode node, Dictionary<string, string> dic, string setmtext)
        {
            if (node.setmsvalue != null)
            {
                setmtext += node.setmsvalue.ToString();
            }
            if (node.rightnode != null)
            {
                Assemblyelementcollection(node.rightnode, dic, setmtext);
            }

            if (node.leftnode != null)
            {
                Assemblyelementcollection(node.leftnode, dic, setmtext);
            }

            if (!string.IsNullOrWhiteSpace(node.Text))
            {
                dic.Add(node.Text, setmtext);
            }
        }

 

5.拿到树枝路径后,根据元数据的字符串顺序进行替换,并且将得到的结构装入byte对象中

 

   byte[] Changebyte(string str)
        {
            int count = (str.Length / 8) + ((str.Length % 8) > 0 ? 1 : 0);

            byte[] b = new byte[count];

            for (int i = 0; i < count; i++)
            {
                var v = str.Substring(i * 8, (str.Length - i * 8) > 8 ? 8 : (str.Length - i * 8)).PadRight(8, '0');
                b[i] = Convert.ToByte(v, 2);
            }
            return b;
        }

6.上面只是对元数据进行处理,大伙想一下,如果单单把这个对象传给其他客户端,他们能解压吗 ?答案肯定不行!所有我们还得需要将分组统计后的集合也得传过去!

  var collectionb = new Compression().CompressionContext(JsonConvert.SerializeObject(obj.elementcollection));

好了,压缩的代码都实现完了,我们看下结果。

 我丢,我们来实现压缩的,咋越压越大了呢,还记的之前我们说过的吗,文件越小,压缩可能没啥用,有可能越大。 那我们把数据多加一些来看看:

 这个就明显的看出了把,压缩了1.8倍。 所以如果按照上面实现的算法 我们可以得出一个结论:

字符越多,重复性越多,压缩的效果就越好!

在实现过程中 ,我感觉这并不是最优的压缩方案  ,设想一下:如果我们不要以单个字符进行统计,我们以出现次数最多的词组来分组统计。我们的树深度是不是更浅,树枝就越短。压缩效果是不是会更好呢 ,感兴趣的小伙伴可以动手实现一下,欢迎留言一起讨论,后面我也会跟大伙分享用词组的方式进行统计!

完整代码


    class Program
    {
        static void Main(string[] args)
        {
           
            var str = @"hello word hello word hello word hello word hello word hello word hello word hello word hello word hello word";

            var orgin = System.Text.Encoding.Default.GetBytes(str);
            var obj=  new Compression();
            var b = obj.CompressionContext(str);

            var collectionb = new Compression().CompressionContext(JsonConvert.SerializeObject(obj.elementcollection));

            Console.WriteLine($"");
            Console.WriteLine($"");
            Console.WriteLine($"");

            Console.WriteLine($"元数据:{str}");

            Console.WriteLine($"原字节长度:{orgin.Length}");
        
            Console.WriteLine($"压缩后字节长度:{b.Length + collectionb.Length}");

            Console.WriteLine($"压缩率:{((double)orgin.Length / (double)(b.Length + collectionb.Length))}");

            Console.ReadKey();
        }

    }

  public class HfNode
    {

        //当前节点的权重
        public int nodevalue { set; get; }       
         
        /// <summary>
        /// 元素的值 父节点此值为空
        /// </summary>
        public string Text { set; get; }

        /// <summary>
        /// 当前树枝的值   左0右1 如果是root 则为空
        /// </summary>
        public int? setmsvalue { set; get; }

        /// <summary>
        /// 右节点
        /// </summary>
        public HfNode rightnode { set; get; }

        /// <summary>
        /// 左节点
        /// </summary>
        public HfNode leftnode { set; get; }
    }


    /// <summary>
    /// 压缩类
    /// </summary>
    public class Compression
    {
        public Dictionary<string, string> elementcollection = new Dictionary<string, string>();
        public byte[] CompressionContext(string str)
        {

            var list = GroupbyData(str);
            Console.WriteLine("-------------元素统计------------------");
            foreach (var item in list)
            {
                Console.WriteLine($"[{item.Text},{item.nodevalue}]");
            }
            Console.WriteLine("-------------元素统计------------------");
            var result = AssemblyNode(list);
            Assemblyelementcollection(result, elementcollection, "");
            Console.WriteLine("-------------树枝路径------------------");
            foreach (var item in elementcollection)
            {
                Console.WriteLine($"[{item.Key},{item.Value}]");
            }
            Console.WriteLine("-------------树枝路径------------------");
            var resulttext = string.Join("", str.ToCharArray().Select(i => elementcollection.Where(j => j.Key == i.ToString()).FirstOrDefault().Value).ToList());

            Console.WriteLine("-------------替换路径后的字符串------------------");
            Console.WriteLine(resulttext);
            Console.WriteLine("-------------替换路径后的字符串------------------");

            return Changebyte(resulttext);
        }

        /// <summary>
        /// 字符串分组
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        List<HfNode> GroupbyData(string str)
        {
            //对数据进行分组 
            return str.ToCharArray().GroupBy(i => i).Select(i => new HfNode
            {
                nodevalue = i.Count(),
                Text = i.Key.ToString()
            }).ToList();
        }

        HfNode AssemblyNode(IList<HfNode> list)
        {

            while (list.Count > 1)
            {
                list = list.OrderBy(i => i.nodevalue).ToList();
                var left = list[0];
                var right = list[1];
                list.RemoveAt(0);
                list.RemoveAt(0);
                list.Add(Assemblychildren(left, right));

            }

            return list[0];
        }

        /// <summary>
        /// 组装子节点
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        HfNode Assemblychildren(HfNode left, HfNode right)
        {
            left.setmsvalue = 0;
            right.setmsvalue = 1;

            return new HfNode
            {
                leftnode = left,
                rightnode = right,
                nodevalue = left.nodevalue + right.nodevalue
            };

        }

        void Assemblyelementcollection(HfNode node, Dictionary<string, string> dic, string setmtext)
        {
            if (node.setmsvalue != null)
            {
                setmtext += node.setmsvalue.ToString();
            }
            if (node.rightnode != null)
            {
                Assemblyelementcollection(node.rightnode, dic, setmtext);
            }

            if (node.leftnode != null)
            {
                Assemblyelementcollection(node.leftnode, dic, setmtext);
            }

            if (!string.IsNullOrWhiteSpace(node.Text))
            {
                dic.Add(node.Text, setmtext);
            }
        }

        byte[] Changebyte(string str)
        {
            int count = (str.Length / 8) + ((str.Length % 8) > 0 ? 1 : 0);

            byte[] b = new byte[count];

            for (int i = 0; i < count; i++)
            {
                var v = str.Substring(i * 8, (str.Length - i * 8) > 8 ? 8 : (str.Length - i * 8)).PadRight(8, '0');
                b[i] = Convert.ToByte(v, 2);
            }
            return b;
        }

    }

今天的压缩就分享到这了 ,下一篇我们来实现 解压!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值