贪心算法-赫夫曼树

哈夫曼树 

1.  哈夫曼树的基本概念 
  哈夫曼树(  Huffman  )又称最优二叉树,是一类带权路径长度最短的树,有着广泛的应用。
  在讨论哈夫曼树之前首先需要弄清楚关于路径和路径长度的概念。树中两个结点之间的路径由一个结点到另一结点的分支构成。两结点之间的路径长度是路径上分支的数目。树的路径长度是从根结点到每一个结点的路径长度之和。 
  设一棵二叉树有  个叶子结点,每个叶子结点拥有一个权值W  ,W  ,  ......  W  ,从根结点到每个叶子结点的路径长度分别为  L1  ,  L2......Ln  ,那么树的带权路径长度为每个叶子的路径长度与该叶子权值乘积之和(以史为鉴,笔试错过一次了)。通常记作  WPL  = L  k.  W  。为了直观其见,在图中把带权的叶子结点画成方形,其他非叶子结点仍为圆形。请看图  6.21  中的三棵二叉树以及它们的带权路径长。
贪心算法-赫夫曼树_第1张图片
(a) wpl=38 (b) wpl=49 (c) wpl=36  图  6.21  具有不同带权路径长度的二叉树 
注意 :
这三棵二叉树叶子结点数相同,它们的权值也相同,但是它们的  wpl  带权路径长各不相同。图  6.21(c)wpl  最小。它就是哈曼树,最优树。哈夫曼树是,在具有同一组权值的叶子结点的不同二叉树中,带权路径长度最短的树。也称最优树。 
2.  哈夫曼树的构造 
构造哈夫曼树的方法 
对于已知的一组叶子的权值W  ,W  2......  ,W 
首先把  个叶子结点看做  棵树(仅有一个结点的二叉树),把它们看做一个森林。 
在森林中把权值最小和次小的两棵树合并成一棵树,该树根结点的权值是两棵子树权值之和。这时森林中还有  n-1  棵树。 
重复第 步直到森林中只有一棵为止。此树就是哈夫曼树。现给一组  (n=4)  具体的权值  ,  ,  ,  ,下边是构造具体过程: 
贪心算法-赫夫曼树_第2张图片
图  6.22  哈夫曼树构造过程 
图  6.22(a)  是一个拥有  棵小树的森林,图  6.22(b)  森林中还有  子棵树,图  6.22(c)  森林中剩下  棵树,图  6.22(d)  森林只有一棵树,这棵树就是哈夫曼树。这里或许会提出疑问,  个叶子构成的哈夫曼树其带权路径长度唯一吗?确实唯一。树形唯一吗?不唯一。因为将森林中两棵权值最小和次小的子棵合并时,哪棵做左子树,哪棵做右子树并不严格限制。图  6.22  之中的做法是把权值较小的当做左子树  权值较大的当做右子树。如果反过来也可以,画出的树形有所不同,但  WPL  值相同。为了便于讨论交流在此提倡权值较小的做左子树  权值较大的做右子。

代码:

#include<iostream>
#include<string>
#include<FLOAT.H>
using namespace std;
#define n 6     // 叶子节点数
#define m n*2-1 //总节点数
#define nil -1  //nil指针为-1

struct node
{
	int p;
	int left;
	int right;
	float weight;
	node():p(nil),left(nil),right(nil),weight(0){}
};
struct Haffuman_Tree
{
	node* Array;
	string str;
	Haffuman_Tree()//默认构造函数
	{
		Array=new node[m];//用一个长度为m的数组存放树中节点
		str="";           //保存编码
	};
};
//构造哈夫曼树
void Huffman(Haffuman_Tree& T)
{
	int i,j,p1,p2; //指针:p1指向最小值,p2指向次小值
	//small1等于最小值权重,small2等于次小值权重
	float small1,small2;

	for(i=n;i<m;i++)//0..n-1已经有权重了,现在的目标是将n..m-1的权重值全填满就可以
	{
		p1=p2=nil;
		small1=small2=FLT_MAX ;
		for(j=0;j<i;j++)//遍历所有已存在的权重值,选出最小值和次小值
		{
			if(T.Array[j].p==nil) //只考虑父节点为空的情况
			{
				if(T.Array[j].weight<small1)
				{
					small2=small1;
					small1=T.Array[j].weight;
					p2=p1;
					p1=j;
				}
				else if(T.Array[j].weight<small2)
				{
					small2=T.Array[j].weight;
					p2=j;
				}
			}
		}
		T.Array[p1].p=T.Array[p2].p=i;
		T.Array[i].p=nil;
		T.Array[i].left=p1;
		T.Array[i].right=p2;
		T.Array[i].weight=small1+small2;
	}
}
// 对哈夫曼树的叶节点进行编码
void huff_encode(Haffuman_Tree& T)
{
	int x,y;
    for (int i = 0; i < n; i++)  // 为每一个叶节点进行编码
	{
        x = i;
		y = T.Array[i].p;
        while (y != nil) //如果父节点不为空
		{  
            // 若当前节点是父节点的左孩子,则编码为0
			if (T.Array[y].left == x)
			{
				T.str.push_back('0');
            }
            // 若当前节点是父节点的右孩子,则编码为1
			else if (T.Array[y].right == x) 
			{
				T.str.push_back('1');
            }
            // 继续向上遍历
            x = y;
			y = T.Array[y].p;
        }
		T.str.push_back(' ');//分隔符
    }
}
void main()
{
	Haffuman_Tree T;
	//测试数据5 9 12 13 16 45
	for(int i=0;i<n;i++)
	{
		cin >> T.Array[i].weight;
	}
	Huffman(T);
	cout<<"根结点:"<<endl;
	for(int i=0;i<n;i++)
		cout<<T.Array[i].weight<<" ";
	cout<<endl<<"非根结点:"<<endl;
	for(int i=n;i<m;i++)
		cout<<T.Array[i].weight<<" ";
	cout<<endl;
	huff_encode(T);
	cout<<"输出赫夫曼编码:"<<endl;
	cout<<T.str<<endl;
}

测试数据基于算法导论p247页:

贪心算法-赫夫曼树_第3张图片

贪心算法-赫夫曼树_第4张图片

你可能感兴趣的:(类,编码,二叉树,算法导论,应用)