哈夫曼树定义为:给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree)。
1、那么什么是权值?什么是路径长度?什么是带权路径长度呢?
权值:哈夫曼树的权值是自己定义的,他的物理意义表示数据出现的次数、频率。可以用树的每个结点数据域data存放一个特定的数表示它的值。
路径长度:在一棵树中,从一个结点往下可以达到的孩子或子孙结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。
结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。 树中所有叶子节点的带权路径长度之和,WPL=sigma(w*l)
2、哈夫曼树的构造过程。(结合图例)
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树
3、哈夫曼树的应用:哈夫曼编码(前缀编码)
哈夫曼编码
在数据通信中,通常需要把要传送的文字转换为由二进制字符0和1组成的二进制串,这个过程被称之为编码(Encoding)。例如,假设要传送的电文为DCBBADD,电文中只有A、B、C、D四种字符,若这四个字符采用表(a)所示的编码方案,则电文的代码为11100101001111,代码总长度为14。若采用表(b) 所示的编码方案,则电文的代码为0110101011100,代码总长度为13。
字符集的不同编码方案
哈夫曼树可用于构造总长度最短的编码方案。具体构造方法如下:
设需要编码的字符集为{d1,d2,…,dn},各个字符在电文中出现的次数或频率集合为{w1,w2,…,wn}。以d1,d2,…,dn作为叶子结点,以w1,w2,…,wn作为相应叶子结点的权值来构造一棵哈夫曼树。规定哈夫曼树中的左分支代表0,右分支代表1,则从根结点到叶子结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码就是哈夫曼编码(Huffman Encoding)。
在建立不等长编码中,必须使任何一个字符的编码都不是另一个编码的前缀,这样才能保证译码的唯一性。例如,若字符A的编码是00,字符B的编码是001,那么字符A的编码就成了字符B的编码的后缀。这样,对于代码串001001,在译码时就无法判定是将前两位码00译成字符A还是将前三位码001译成B。这样的编码被称之为具有二义性的编码,二义性编码是不唯一的。而在哈夫曼树中,每个字符结点都是叶子结点,它们不可能在根结点到其它字符结点的路径上,所以一个字符的哈夫曼编码不可能是另一个字符的哈夫曼编码的前缀,从而保证了译码的非二义性。
下图就是电文DCBBADD的哈夫曼树:
4、哈夫曼树的实现
由哈夫曼树的构造算法可知,用一个数组存放原来的n个叶子结点和构造过程中临时生成的结点,数组的大小为2n-1。所以,哈夫曼树类HuffmanTree中有两个成员字段:data数组用于存放结点,leafNum表示哈夫曼树叶子结点的数目。结点有四个域,一个域weight,用于存放该结点的权值;一个域lChild,用于存放该结点的左孩子结点在数组中的序号;一个域rChild,用于存放该结点的右孩子结点在数组中的序号;一个域parent,用于判定该结点是否已加入哈夫曼树中。
哈夫曼树结点的结构为:| 数据 | weight | lChild | rChild | parent |
public class Node { char c; //存储的数据,为一个字符 private double weight; //结点权值 private int lChild; //左孩子结点 private int rChild; //右孩子结点 private int parent; //父结点 //结点权值属性 public double Weight { get { return weight; } set { weight = value; } } //左孩子结点属性 public int LChild { get { return lChild; } set { lChild = value; } } //右孩子结点属性 public int RChild { get { return rChild; } set { rChild = value; } } //父结点属性 public int Parent { get { return parent; } set { parent = value; } } //构造器 public Node() { weight = 0; lChild = -1; rChild = -1; parent = -1; } //构造器 public Node(double weitht) { this.weight = weitht; lChild = -1; rChild = -1; parent = -1; } //构造器 public Node(int w, int lc, int rc, int p) { weight = w; lChild = lc; rChild = rc; parent = p; } } public class HuffmanTree { private Node[] data; //结点数组 private int leafNum; //叶子结点数目 //索引器 public Node this[int index] { get { return data[index]; } set { data[index] = value; } } //叶子结点数目属性 public int LeafNum { get { return leafNum; } set { leafNum = value; } } //构造器 public HuffmanTree(int n) { data = new Node[2 * n - 1]; leafNum = n; } //创建哈夫曼树 public void Create(Node[] datain) { double minL, minR; minL = minR = double.MaxValue; int lchild, rchild; lchild = rchild = -1; int length = data.Length; //初始化哈夫曼树 for (int i = 0; i < length; i++) data[i] = new Node(); for (int i = 0; i < datain.Length; i++) data[i] = datain[i]; //处理n个叶子结点,建立哈夫曼树 for (int i = leafNum; i < length; i++) { //在全部结点中找权值最小的两个结点 for (int j = 0; j < i; j++) { if (data[j].Parent == -1) { if (data[j].Weight < minL) { minR = minL; minL = data[j].Weight; rchild = lchild; lchild = j; } else if (data[j].Weight < minR) { minR = data[j].Weight; rchild = j; } } } data[lchild].Parent = data[rchild].Parent = i; data[i].Weight = minL + minR; data[i].LChild = lchild; data[i].RChild = rchild; } } } class Program { static void Main(string[] args) { HuffmanTree tree = new HuffmanTree(5); Node[] nodes = new Node[] { new Node(1), new Node(2), new Node(2.5d), new Node(3), new Node(2.6d) }; tree.Create(nodes); Console.ReadLine(); } }