David A. Huffman
1 哈夫曼编码简史(Huffman code)
1951年,哈夫曼和他在MIT信息论的同学需要选择是完成学期报告还是期末考试。导师Robert M. Fano给他们的学期报告的题目是,寻找最有效的二进制编码。由于无法证明哪个已有编码是最有效的,哈夫曼放弃对已有编码的研究,转向新的探索,最终发现了基于有序频率二叉树编码的想法,并很快证明了这个方法是最有效的。由于这个算法,学生终于青出于蓝,超过了他那曾经和信息论创立者香农共同研究过类似编码的导师。哈夫曼使用自底向上的方法构建二叉树,避免了次优算法Shannon-Fano编码的最大弊端──自顶向下构建树。
1952年,David A. Huffman在麻省理工攻读博士时发表了《一种构建极小多余编码的方法》,一文,它一般就叫做Huffman编码。A Method for the Construction of Minimum-Redundancy Codeshttps://www.ias.ac.in/article/fulltext/reso/011/02/0091-0099
Huffman在1952年根据香农(Shannon)在1948年和范若(Fano)在1949年阐述的这种编码思想提出了一种不定长编码的方法,也称霍夫曼(Huffman)编码。霍夫曼编码的基本方法是先对图像数据扫描一遍,计算出各种像素出现的概率,按概率的大小指定不同长度的唯一码字,由此得到一张该图像的霍夫曼码表。编码后的图像数据记录的是每个像素的码字,而码字与实际像素值的对应关系记录在码表中。
2 赫夫曼编码
赫夫曼编码是可变字长编码(VLC)的一种。 Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长 度最短的码字,有时称之为最佳编码,一般就称Huffman编码。下面引证一个定理,该定理保证了按字符出现概率分配码长,可使平均码长最短。
3 源程序
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
namespace Legalsoft.Truffer.Algorithm
{
/// <summary>
/// 哈夫曼编码的压缩与解压缩
/// </summary>
public class HuffmanCompressor
{
private HuffmanNode oRootHuffmanNode { get; set; } = null;
private List<HuffmanNode> oValueHuffmanNodes { get; set; } = null;
private List<HuffmanNode> BuildBinaryTree(string Value)
{
List<HuffmanNode> oHuffmanNodes = GetInitialNodeList();
Value.ToList().ForEach(m => oHuffmanNodes[m].Weight++);
oHuffmanNodes = oHuffmanNodes
.Where(m => (m.Weight > 0))
.OrderBy(m => (m.Weight))
.ThenBy(m => (m.Value))
.ToList();
oHuffmanNodes = UpdateNodeParents(oHuffmanNodes);
oRootHuffmanNode = oHuffmanNodes[0];
oHuffmanNodes.Clear();
SortNodes(oRootHuffmanNode, oHuffmanNodes);
return oHuffmanNodes;
}
public void Compress(string FileName)
{
FileInfo oFileInfo = new FileInfo(FileName);
if (oFileInfo.Exists == true)
{
string sFileContents = "";
using (StreamReader oStreamReader = new StreamReader(File.OpenRead(FileName)))
{
sFileContents = oStreamReader.ReadToEnd();
}
List<HuffmanNode> oHuffmanNodes = BuildBinaryTree(sFileContents);
oValueHuffmanNodes = oHuffmanNodes
.Where(m => (m.Value.HasValue == true))
.OrderBy(m => (m.BinaryWord))
.ToList();
Dictionary<char, string> oCharToBinaryWordDictionary = new Dictionary<char, string>();
foreach (HuffmanNode oHuffmanNode in oValueHuffmanNodes)
{
oCharToBinaryWordDictionary.Add(oHuffmanNode.Value.Value, oHuffmanNode.BinaryWord);
}
StringBuilder oStringBuilder = new StringBuilder();
List<byte> oByteList = new List<byte>();
for (int i = 0; i < sFileContents.Length; i++)
{
string sWord = "";
oStringBuilder.Append(oCharToBinaryWordDictionary[sFileContents[i]]);
while (oStringBuilder.Length >= 8)
{
sWord = oStringBuilder.ToString().Substring(0, 8);
oStringBuilder.Remove(0, sWord.Length);
}
if (String.IsNullOrEmpty(sWord) == false)
{
oByteList.Add(Convert.ToByte(sWord, 2));
}
}
if (oStringBuilder.Length > 0)
{
string sWord = oStringBuilder.ToString();
if (String.IsNullOrEmpty(sWord) == false)
{
oByteList.Add(Convert.ToByte(sWord, 2));
}
}
string sCompressedFileName = Path.Combine(oFileInfo.Directory.FullName, String.Format("{0}.compressed", oFileInfo.Name.Replace(oFileInfo.Extension, "")));
if (File.Exists(sCompressedFileName) == true)
{
File.Delete(sCompressedFileName);
}
using (FileStream oFileStream = File.OpenWrite(sCompressedFileName))
{
oFileStream.Write(oByteList.ToArray(), 0, oByteList.Count);
}
}
}
public void Decompress(string FileName)
{
FileInfo oFileInfo = new FileInfo(FileName);
if (oFileInfo.Exists == true)
{
string sCompressedFileName = String.Format("{0}.compressed", oFileInfo.FullName.Replace(oFileInfo.Extension, ""));
byte[] oBuffer = null;
using (FileStream oFileStream = File.OpenRead(sCompressedFileName))
{
oBuffer = new byte[oFileStream.Length];
oFileStream.Read(oBuffer, 0, oBuffer.Length);
}
HuffmanNode oZeroHuffmanNode = oRootHuffmanNode;
while (oZeroHuffmanNode.Left != null)
{
oZeroHuffmanNode = oZeroHuffmanNode.Left;
}
HuffmanNode oCurrentHuffmanNode = null;
StringBuilder oStringBuilder = new StringBuilder();
for (int i = 0; i < oBuffer.Length; i++)
{
string sBinaryWord = "";
byte oByte = oBuffer[i];
if (oByte == 0)
{
sBinaryWord = oZeroHuffmanNode.BinaryWord;
}
else
{
sBinaryWord = Convert.ToString(oByte, 2);
}
if ((sBinaryWord.Length < 8) && (i < (oBuffer.Length - 1)))
{
StringBuilder oBinaryStringBuilder = new StringBuilder(sBinaryWord);
while (oBinaryStringBuilder.Length < 8)
{
oBinaryStringBuilder.Insert(0, "0");
}
sBinaryWord = oBinaryStringBuilder.ToString();
}
for (int j = 0; j < sBinaryWord.Length; j++)
{
char cValue = sBinaryWord[j];
if (oCurrentHuffmanNode == null)
{
oCurrentHuffmanNode = oRootHuffmanNode;
}
if (cValue == '0')
{
oCurrentHuffmanNode = oCurrentHuffmanNode.Left;
}
else
{
oCurrentHuffmanNode = oCurrentHuffmanNode.Right;
}
if ((oCurrentHuffmanNode.Left == null) && (oCurrentHuffmanNode.Right == null))
{
oStringBuilder.Append(oCurrentHuffmanNode.Value.Value);
oCurrentHuffmanNode = null;
}
}
}
string sUncompressedFileName = Path.Combine(oFileInfo.Directory.FullName, String.Format("{0}.uncompressed", oFileInfo.Name.Replace(oFileInfo.Extension, "")));
if (File.Exists(sUncompressedFileName) == true)
{
File.Delete(sUncompressedFileName);
}
using (StreamWriter oStreamWriter = new StreamWriter(File.OpenWrite(sUncompressedFileName)))
{
oStreamWriter.Write(oStringBuilder.ToString());
}
}
}
private static List<HuffmanNode> GetInitialNodeList()
{
List<HuffmanNode> oGetInitialNodeList = new List<HuffmanNode>();
for (int i = Char.MinValue; i < Char.MaxValue; i++)
{
oGetInitialNodeList.Add(new HuffmanNode((char)(i)));
}
return oGetInitialNodeList;
}
private static void SortNodes(HuffmanNode Node, List<HuffmanNode> Nodes)
{
if (Nodes.Contains(Node) == false)
{
Nodes.Add(Node);
}
if (Node.Left != null)
{
SortNodes(Node.Left, Nodes);
}
if (Node.Right != null)
{
SortNodes(Node.Right, Nodes);
}
}
private static List<HuffmanNode> UpdateNodeParents(List<HuffmanNode> Nodes)
{
while (Nodes.Count > 1)
{
int iOperations = (Nodes.Count / 2);
for (int iOperation = 0, i = 0, j = 1; iOperation < iOperations; iOperation++, i += 2, j += 2)
{
if (j < Nodes.Count)
{
HuffmanNode oParentHuffmanNode = new HuffmanNode(Nodes[i], Nodes[j]);
Nodes.Add(oParentHuffmanNode);
Nodes[i] = null;
Nodes[j] = null;
}
}
Nodes = Nodes
.Where(m => (m != null))
.OrderBy(m => (m.Weight))
.ToList();
}
return Nodes;
}
}
public class HuffmanNode
{
private string sBinaryWord { get; set; } = "";
private bool bIsLeftNode { get; set; } = false;
private bool bIsRightNode { get; set; } = false;
private HuffmanNode oLeft { get; set; } = null;
private HuffmanNode oParent { get; set; } = null;
private HuffmanNode oRight { get; set; } = null;
private char? cValue { get; set; } = ' ';
private int iWeight { get; set; } = 0;
public HuffmanNode()
{
}
public HuffmanNode(char Value)
{
cValue = Value;
}
public HuffmanNode(HuffmanNode Left, HuffmanNode Right)
{
oLeft = Left;
oLeft.oParent = this;
oLeft.bIsLeftNode = true;
oRight = Right;
oRight.oParent = this;
oRight.bIsRightNode = true;
iWeight = (oLeft.Weight + oRight.Weight);
}
public string BinaryWord
{
get
{
string sReturnValue = "";
if (String.IsNullOrEmpty(sBinaryWord) == true)
{
StringBuilder oStringBuilder = new StringBuilder();
HuffmanNode oHuffmanNode = this;
while (oHuffmanNode != null)
{
if (oHuffmanNode.bIsLeftNode == true)
{
oStringBuilder.Insert(0, "0");
}
if (oHuffmanNode.bIsRightNode == true)
{
oStringBuilder.Insert(0, "1");
}
oHuffmanNode = oHuffmanNode.oParent;
}
sReturnValue = oStringBuilder.ToString();
sBinaryWord = sReturnValue;
}
else
{
sReturnValue = sBinaryWord;
}
return sReturnValue;
}
}
public HuffmanNode Left
{
get
{
return oLeft;
}
}
public HuffmanNode Parent
{
get
{
return oParent;
}
}
public HuffmanNode Right
{
get
{
return oRight;
}
}
public char? Value
{
get
{
return cValue;
}
}
public int Weight
{
get
{
return iWeight;
}
set
{
iWeight = value;
}
}
public static int BinaryStringToInt32(string Value)
{
int iBinaryStringToInt32 = 0;
for (int i = (Value.Length - 1), j = 0; i >= 0; i--, j++)
{
iBinaryStringToInt32 += ((Value[j] == '0' ? 0 : 1) * (int)(Math.Pow(2, i)));
}
return iBinaryStringToInt32;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
if (cValue.HasValue == true)
{
sb.AppendFormat("'{0}' ({1}) - {2} ({3})", cValue.Value, iWeight, BinaryWord, BinaryStringToInt32(BinaryWord));
}
else
{
if ((oLeft != null) && (oRight != null))
{
if ((oLeft.Value.HasValue == true) && (oRight.Value.HasValue == true))
{
sb.AppendFormat("{0} + {1} ({2})", oLeft.Value, oRight.Value, iWeight);
}
else
{
sb.AppendFormat("{0}, {1} - ({2})", oLeft, oRight, iWeight);
}
}
else
{
sb.Append(iWeight);
}
}
return sb.ToString();
}
}
}
4 源代码
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
namespace Legalsoft.Truffer.Algorithm
{
/// <summary>
/// 哈夫曼编码的压缩与解压缩
/// </summary>
public class HuffmanCompressor
{
private HuffmanNode oRootHuffmanNode { get; set; } = null;
private List<HuffmanNode> oValueHuffmanNodes { get; set; } = null;
private List<HuffmanNode> BuildBinaryTree(string Value)
{
List<HuffmanNode> oHuffmanNodes = GetInitialNodeList();
Value.ToList().ForEach(m => oHuffmanNodes[m].Weight++);
oHuffmanNodes = oHuffmanNodes
.Where(m => (m.Weight > 0))
.OrderBy(m => (m.Weight))
.ThenBy(m => (m.Value))
.ToList();
oHuffmanNodes = UpdateNodeParents(oHuffmanNodes);
oRootHuffmanNode = oHuffmanNodes[0];
oHuffmanNodes.Clear();
SortNodes(oRootHuffmanNode, oHuffmanNodes);
return oHuffmanNodes;
}
public void Compress(string FileName)
{
FileInfo oFileInfo = new FileInfo(FileName);
if (oFileInfo.Exists == true)
{
string sFileContents = "";
using (StreamReader oStreamReader = new StreamReader(File.OpenRead(FileName)))
{
sFileContents = oStreamReader.ReadToEnd();
}
List<HuffmanNode> oHuffmanNodes = BuildBinaryTree(sFileContents);
oValueHuffmanNodes = oHuffmanNodes
.Where(m => (m.Value.HasValue == true))
.OrderBy(m => (m.BinaryWord))
.ToList();
Dictionary<char, string> oCharToBinaryWordDictionary = new Dictionary<char, string>();
foreach (HuffmanNode oHuffmanNode in oValueHuffmanNodes)
{
oCharToBinaryWordDictionary.Add(oHuffmanNode.Value.Value, oHuffmanNode.BinaryWord);
}
StringBuilder oStringBuilder = new StringBuilder();
List<byte> oByteList = new List<byte>();
for (int i = 0; i < sFileContents.Length; i++)
{
string sWord = "";
oStringBuilder.Append(oCharToBinaryWordDictionary[sFileContents[i]]);
while (oStringBuilder.Length >= 8)
{
sWord = oStringBuilder.ToString().Substring(0, 8);
oStringBuilder.Remove(0, sWord.Length);
}
if (String.IsNullOrEmpty(sWord) == false)
{
oByteList.Add(Convert.ToByte(sWord, 2));
}
}
if (oStringBuilder.Length > 0)
{
string sWord = oStringBuilder.ToString();
if (String.IsNullOrEmpty(sWord) == false)
{
oByteList.Add(Convert.ToByte(sWord, 2));
}
}
string sCompressedFileName = Path.Combine(oFileInfo.Directory.FullName, String.Format("{0}.compressed", oFileInfo.Name.Replace(oFileInfo.Extension, "")));
if (File.Exists(sCompressedFileName) == true)
{
File.Delete(sCompressedFileName);
}
using (FileStream oFileStream = File.OpenWrite(sCompressedFileName))
{
oFileStream.Write(oByteList.ToArray(), 0, oByteList.Count);
}
}
}
public void Decompress(string FileName)
{
FileInfo oFileInfo = new FileInfo(FileName);
if (oFileInfo.Exists == true)
{
string sCompressedFileName = String.Format("{0}.compressed", oFileInfo.FullName.Replace(oFileInfo.Extension, ""));
byte[] oBuffer = null;
using (FileStream oFileStream = File.OpenRead(sCompressedFileName))
{
oBuffer = new byte[oFileStream.Length];
oFileStream.Read(oBuffer, 0, oBuffer.Length);
}
HuffmanNode oZeroHuffmanNode = oRootHuffmanNode;
while (oZeroHuffmanNode.Left != null)
{
oZeroHuffmanNode = oZeroHuffmanNode.Left;
}
HuffmanNode oCurrentHuffmanNode = null;
StringBuilder oStringBuilder = new StringBuilder();
for (int i = 0; i < oBuffer.Length; i++)
{
string sBinaryWord = "";
byte oByte = oBuffer[i];
if (oByte == 0)
{
sBinaryWord = oZeroHuffmanNode.BinaryWord;
}
else
{
sBinaryWord = Convert.ToString(oByte, 2);
}
if ((sBinaryWord.Length < 8) && (i < (oBuffer.Length - 1)))
{
StringBuilder oBinaryStringBuilder = new StringBuilder(sBinaryWord);
while (oBinaryStringBuilder.Length < 8)
{
oBinaryStringBuilder.Insert(0, "0");
}
sBinaryWord = oBinaryStringBuilder.ToString();
}
for (int j = 0; j < sBinaryWord.Length; j++)
{
char cValue = sBinaryWord[j];
if (oCurrentHuffmanNode == null)
{
oCurrentHuffmanNode = oRootHuffmanNode;
}
if (cValue == '0')
{
oCurrentHuffmanNode = oCurrentHuffmanNode.Left;
}
else
{
oCurrentHuffmanNode = oCurrentHuffmanNode.Right;
}
if ((oCurrentHuffmanNode.Left == null) && (oCurrentHuffmanNode.Right == null))
{
oStringBuilder.Append(oCurrentHuffmanNode.Value.Value);
oCurrentHuffmanNode = null;
}
}
}
string sUncompressedFileName = Path.Combine(oFileInfo.Directory.FullName, String.Format("{0}.uncompressed", oFileInfo.Name.Replace(oFileInfo.Extension, "")));
if (File.Exists(sUncompressedFileName) == true)
{
File.Delete(sUncompressedFileName);
}
using (StreamWriter oStreamWriter = new StreamWriter(File.OpenWrite(sUncompressedFileName)))
{
oStreamWriter.Write(oStringBuilder.ToString());
}
}
}
private static List<HuffmanNode> GetInitialNodeList()
{
List<HuffmanNode> oGetInitialNodeList = new List<HuffmanNode>();
for (int i = Char.MinValue; i < Char.MaxValue; i++)
{
oGetInitialNodeList.Add(new HuffmanNode((char)(i)));
}
return oGetInitialNodeList;
}
private static void SortNodes(HuffmanNode Node, List<HuffmanNode> Nodes)
{
if (Nodes.Contains(Node) == false)
{
Nodes.Add(Node);
}
if (Node.Left != null)
{
SortNodes(Node.Left, Nodes);
}
if (Node.Right != null)
{
SortNodes(Node.Right, Nodes);
}
}
private static List<HuffmanNode> UpdateNodeParents(List<HuffmanNode> Nodes)
{
while (Nodes.Count > 1)
{
int iOperations = (Nodes.Count / 2);
for (int iOperation = 0, i = 0, j = 1; iOperation < iOperations; iOperation++, i += 2, j += 2)
{
if (j < Nodes.Count)
{
HuffmanNode oParentHuffmanNode = new HuffmanNode(Nodes[i], Nodes[j]);
Nodes.Add(oParentHuffmanNode);
Nodes[i] = null;
Nodes[j] = null;
}
}
Nodes = Nodes
.Where(m => (m != null))
.OrderBy(m => (m.Weight))
.ToList();
}
return Nodes;
}
}
public class HuffmanNode
{
private string sBinaryWord { get; set; } = "";
private bool bIsLeftNode { get; set; } = false;
private bool bIsRightNode { get; set; } = false;
private HuffmanNode oLeft { get; set; } = null;
private HuffmanNode oParent { get; set; } = null;
private HuffmanNode oRight { get; set; } = null;
private char? cValue { get; set; } = ' ';
private int iWeight { get; set; } = 0;
public HuffmanNode()
{
}
public HuffmanNode(char Value)
{
cValue = Value;
}
public HuffmanNode(HuffmanNode Left, HuffmanNode Right)
{
oLeft = Left;
oLeft.oParent = this;
oLeft.bIsLeftNode = true;
oRight = Right;
oRight.oParent = this;
oRight.bIsRightNode = true;
iWeight = (oLeft.Weight + oRight.Weight);
}
public string BinaryWord
{
get
{
string sReturnValue = "";
if (String.IsNullOrEmpty(sBinaryWord) == true)
{
StringBuilder oStringBuilder = new StringBuilder();
HuffmanNode oHuffmanNode = this;
while (oHuffmanNode != null)
{
if (oHuffmanNode.bIsLeftNode == true)
{
oStringBuilder.Insert(0, "0");
}
if (oHuffmanNode.bIsRightNode == true)
{
oStringBuilder.Insert(0, "1");
}
oHuffmanNode = oHuffmanNode.oParent;
}
sReturnValue = oStringBuilder.ToString();
sBinaryWord = sReturnValue;
}
else
{
sReturnValue = sBinaryWord;
}
return sReturnValue;
}
}
public HuffmanNode Left
{
get
{
return oLeft;
}
}
public HuffmanNode Parent
{
get
{
return oParent;
}
}
public HuffmanNode Right
{
get
{
return oRight;
}
}
public char? Value
{
get
{
return cValue;
}
}
public int Weight
{
get
{
return iWeight;
}
set
{
iWeight = value;
}
}
public static int BinaryStringToInt32(string Value)
{
int iBinaryStringToInt32 = 0;
for (int i = (Value.Length - 1), j = 0; i >= 0; i--, j++)
{
iBinaryStringToInt32 += ((Value[j] == '0' ? 0 : 1) * (int)(Math.Pow(2, i)));
}
return iBinaryStringToInt32;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
if (cValue.HasValue == true)
{
sb.AppendFormat("'{0}' ({1}) - {2} ({3})", cValue.Value, iWeight, BinaryWord, BinaryStringToInt32(BinaryWord));
}
else
{
if ((oLeft != null) && (oRight != null))
{
if ((oLeft.Value.HasValue == true) && (oRight.Value.HasValue == true))
{
sb.AppendFormat("{0} + {1} ({2})", oLeft.Value, oRight.Value, iWeight);
}
else
{
sb.AppendFormat("{0}, {1} - ({2})", oLeft, oRight, iWeight);
}
}
else
{
sb.Append(iWeight);
}
}
return sb.ToString();
}
}
}