上一篇跟大伙分享了 哈夫曼算法的理论, 今儿我们一起实战一下,如何实现用哈夫曼算法来进行压缩。 哈夫曼理论篇
实战思路
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;
}
}
今天的压缩就分享到这了 ,下一篇我们来实现 解压!!!!