数据结构-哈夫曼树(Huffman)

  • 哈夫曼树


  • 哈夫曼树概述

(1)总括:哈夫曼(Huffman)树又称为最优二叉树,是一类带权路径长度最短的树

(2)树的路径长度:树根到每个结点的路径长度之和(对于节点个数相同的二叉树来说,路径长度最短树是完全二叉树

(3)权值(Weight):每个叶子结点所赋予的特殊值

(4)叶子带权路径长度:一个叶子结点的权值该结点到根的路径长度乘积

(5)二叉树的带权路径长度(Weighted Path Length):简记WPL所有叶子带权路径长度之和

                          WPL=\sum wl(w是第k个叶子结点的权值,l为第k个叶子结点的路径长度)

给定n个叶子结点,在这些结点构造的二叉树中,WPL最小的二叉树称为哈夫曼树

  • 哈夫曼树的构造

(1)思路:

                  要想让一颗二叉树的带权路径长度最小,必须使权值相对大的叶子结点更靠近根结点

(2)哈夫曼算法:

                 a.给定含有n棵二叉树的集合F,每二棵叉树左右孩子都为空(说白了就是哈夫曼树的原始叶子节点)

                 b.选取两棵根节点最小的树作为左右子树构造一棵新的二叉树,并且该二叉树的根节点权值为左右子树根结点权值之和

                 c.删除这两棵被结合的二叉树,同时新的二叉树加入F

                 d.重复b c,直到F中只剩下一棵二叉树,这棵二叉树就是哈夫曼树

(3)总结下规律

                 ①最先给定的n棵左右子树为空的二叉树,最终成为哈夫曼树的叶子结点,所以:原始二叉树数n=哈夫曼树叶子结点数

                 ②在构造过程中每两棵树结合成一棵直到剩下一棵树,共结合n-1次,每次结合新形成一个结点

                 ③由①②可得一颗具有n个叶子结点的哈夫曼树具有n+n-1=2n-1个结点

                 ④每棵二叉树只结合一次

(4)哈夫曼优化算法

                利用sort,并且只需在没有结合过的二叉树中寻找两棵最小权重的二叉树

                对保存二叉树的数组进行分区,前一部分是已经结合过的,后一部分是还未结合或者新的二叉树

                用sort可以将后一部分最小权值的两棵二叉树置于后一部分的最前两个位置

               结合生成新二叉树并把该树置于数组尾部,同时定义的排序开始位置(Begin)后移两个位置,因为这个时候又有两棵二叉树已经结合过,下次排序从Begin开始

                 注意!为了保证一致性,结合新的二叉树的左孩子树根结点权重默认小于右孩子树根结点权重

                 而且为了不因多次排序乱了结点产生的顺序,我在结点中定义INDEX用于保存一开始结点的下标。

                 在完成构建树后依照INDEX进行排序一次

#define Length 10
struct Huffman_Node		//哈夫曼树节点
{
	int Index;			//刚输入权重的下标
	int Weight;			//权重
	Huffman_Node* Parent;		//父母节点
	Huffman_Node* Left_Child;		//左孩子
	Huffman_Node* Right_Child;		//右孩子
};

bool MyCompare(Huffman_Node* A, Huffman_Node* B)	//自定义排序,将Parent=NULL(未结合的节点)与已经结合的节点分开,未结合的在数组下部分
{													//两部分都按照权重升序排序
	if (A->Parent == NULL && !B->Parent == NULL)
		return A->Weight < B->Weight;
	else if (A->Parent == NULL && B->Parent != NULL)
		return false;
	else if (A->Parent != NULL && B->Parent == NULL)
		return true;
	else
		return A->Weight < B->Weight;
}

bool _MyCompare(Huffman_Node* A, Huffman_Node* B)  //自定义排序,按节点一开始的输入顺序排序
{
	return A->Index < B->Index;
}

//Initial_NodeNum 是初始节点个数,也就是所构建的哈夫曼树的树叶数  
//*Weight_List 权重值
Huffman_Tree::Huffman_Tree(int Initial_NodeNum, int *Weight_List):Leaf_Node_Num(Initial_NodeNum)
{
	
	Huffman_List = new Huffman_Node*[2 * Initial_NodeNum - 1];		//分配哈夫曼树节点数组,个数为 2*Leaf_Num-1
	for (int i = 0; i < 2 * Initial_NodeNum - 1; i++)		//节点数组下标从0开始
	{
		Huffman_List[i] = new Huffman_Node;		//分配节点
		if (i < Initial_NodeNum)				//初始化原始叶子节点
		{		
			Huffman_List[i]->Index = i;				//记录叶子权重输入顺序(下标)
			Huffman_List[i]->Weight = Weight_List[i];
		}

		//孩子和父母节点全部初始为空(NULL)
		Huffman_List[i]->Left_Child = NULL;
		Huffman_List[i]->Right_Child = NULL;
		Huffman_List[i]->Parent = NULL;
	}
	
	int Begin = 0;			//Begin是未结合节点(包括新结合成的节点)在数组中开始的位置---因为数组的前一部分是已经组合的节点
	int Temp_Num = Initial_NodeNum;
	
	while (Initial_NodeNum != 2 * Temp_Num - 1)		//当树节点达到2*Leaf_Num-1时说明哈夫曼树构建完成
	{
		sort(&Huffman_List[Begin], Huffman_List + Initial_NodeNum, MyCompare);		//进行排序,下标为Begin,Begin+1的节点为当前权重最小的两个节点
		Huffman_List[Initial_NodeNum]->Index = Initial_NodeNum;		//保存下标
		Huffman_List[Initial_NodeNum]->Weight = Huffman_List[Begin]->Weight + Huffman_List[Begin + 1]->Weight;		//新结合节点的权重为两个节点权重的和
		Huffman_List[Initial_NodeNum]->Left_Child = Huffman_List[Begin];		//为了统一,值小的为左孩子
		Huffman_List[Initial_NodeNum]->Right_Child = Huffman_List[Begin + 1];



		//被结合的两个节点的父母亲为新结合的节点
		Huffman_List[Begin]->Parent = Huffman_List[Initial_NodeNum];
		Huffman_List[Begin + 1]->Parent = Huffman_List[Initial_NodeNum];

		Begin += 2;			//两个节点没了,数组下标后移
		Initial_NodeNum += 1;	//节点数加1
	}

	//哈夫曼树构建完成

	//为了保持与输入一致,按输入权重及新节点产生顺序进行排序
	sort(Huffman_List, Huffman_List + Initial_NodeNum, _MyCompare);		
	
	
	Root_Node = Huffman_List[Initial_NodeNum-1];   //保存树根,树根为最后生成的节点
}

 

  • 哈夫曼编码

(1)规则:在哈夫曼树中,规定所有结点的左分支代表0右分支代表1,从根结点到某一叶子结点所经过的路径组成的0 1序列便为该叶子节点字符的哈夫曼编码。(哈夫曼编码针对的是哈夫曼树的叶子节点

(2)编码实现思路:在编程中一个叶子结点对于内容编码的确定,是需要以叶子为起点爬到根节点进行确定,而怎么知道当前结点是左是右?我们知道当前结点保存了其父母结点的地址,所以只需要当前结点是其父母结点的左孩子还是右孩子就可以确定该段的编码,然后一直找到根结点编码完成。

实现啦实现啦

struct Huffman_Code			//储存编码的结构
{
	char Code[Length];			//储存编码
	int Depth;		//编码长度+1(对应为该叶子节点的度+1)
};

void Huffman_Tree::_ExcuteCode()			//进行编码
{
	for (int i = 0; i < Leaf_Node_Num; i++)			
	{
		Huffman_Node* _Parent = Huffman_List[i]->Parent;			//_Parent 当前节点的父母节点
		Huffman_Node* _Child = Huffman_List[i];			//_child 当前节点
		Huffman_Code_List[i].Depth = -1;		
		while (_Parent != NULL)
		{
			if (_Child == _Parent->Left_Child)
				Huffman_Code_List[i].Code[++Huffman_Code_List[i].Depth] = '0';		//是左孩子,该段编码0
			else
				Huffman_Code_List[i].Code[++Huffman_Code_List[i].Depth] = '1';		//右孩子,编码1


			//往树根走
			_Child = _Parent;
			_Parent = _Parent->Parent;

		}
	}
}

                      

  • 哈夫曼树类实现(全部代码)

#include	
#include	
using namespace std;
#define Length 10
struct Huffman_Node		//哈夫曼树节点
{
	int Index;			//刚输入权重的下标
	int Weight;			//权重
	Huffman_Node* Parent;		//父母节点
	Huffman_Node* Left_Child;		//左孩子
	Huffman_Node* Right_Child;		//右孩子
};

struct Huffman_Code			//储存编码的结构
{
	char Code[Length];			//储存编码
	int Depth;		//编码长度+1(对应为该叶子节点的度+1)
};

bool MyCompare(Huffman_Node* A, Huffman_Node* B)	//自定义排序,将Parent=NULL(未结合的节点)与已经结合的节点分开,未结合的在数组下部分
{													//两部分都按照权重升序排序
	if (A->Parent == NULL && !B->Parent == NULL)
		return A->Weight < B->Weight;
	else if (A->Parent == NULL && B->Parent != NULL)
		return false;
	else if (A->Parent != NULL && B->Parent == NULL)
		return true;
	else
		return A->Weight < B->Weight;
}

bool _MyCompare(Huffman_Node* A, Huffman_Node* B)  //自定义排序,按节点一开始的输入顺序排序
{
	return A->Index < B->Index;
}

class Huffman_Tree
{
private:
	Huffman_Node **Huffman_List;		//地址数组,数组的每一个元素指向一个节点
	Huffman_Code *Huffman_Code_List;		//叶子节点编码组
	Huffman_Node *Root_Node;			//哈夫曼树根节点
	int Leaf_Node_Num;					//叶子数量


	void PreOrder_Op(Huffman_Node* &Root);		//先序遍历哈夫曼树的内部实现函数
	void _ExcuteCode();					//对叶子节点进行编码的内部实现函数


public:
	Huffman_Tree(int Initial_NodeNum, int *Weight_List);		//构造哈夫曼树
	void PreOrder();			//先序遍历接口函数
	void Create_Code();			//叶子节点编码外部接口
	void Print_Huffman_Code();		//输出所有叶子编码
};


//Initial_NodeNum 是初始节点个数,也就是所构建的哈夫曼树的树叶数  
//*Weight_List 权重值
Huffman_Tree::Huffman_Tree(int Initial_NodeNum, int *Weight_List):Leaf_Node_Num(Initial_NodeNum)
{
	Huffman_Code_List = new Huffman_Code[Initial_NodeNum];				//分配编码节点组,多少叶子就有多少编码	
	Huffman_List = new Huffman_Node*[2 * Initial_NodeNum - 1];		//分配哈夫曼树节点数组,个数为 2*Leaf_Num-1
	for (int i = 0; i < 2 * Initial_NodeNum - 1; i++)		//节点数组下标从0开始
	{
		Huffman_List[i] = new Huffman_Node;		//分配节点
		if (i < Initial_NodeNum)				//初始化原始叶子节点
		{		
			Huffman_List[i]->Index = i;				//记录叶子权重输入顺序(下标)
			Huffman_List[i]->Weight = Weight_List[i];
		}

		//孩子和父母节点全部初始为空(NULL)
		Huffman_List[i]->Left_Child = NULL;
		Huffman_List[i]->Right_Child = NULL;
		Huffman_List[i]->Parent = NULL;
	}
	
	int Begin = 0;			//Begin是未结合节点(包括新结合成的节点)在数组中开始的位置---因为数组的前一部分是已经组合的节点
	int Temp_Num = Initial_NodeNum;
	
	while (Initial_NodeNum != 2 * Temp_Num - 1)		//当树节点达到2*Leaf_Num-1时说明哈夫曼树构建完成
	{
		sort(&Huffman_List[Begin], Huffman_List + Initial_NodeNum, MyCompare);		//进行排序,下标为Begin,Begin+1的节点为当前权重最小的两个节点
		Huffman_List[Initial_NodeNum]->Index = Initial_NodeNum;		//保存下标
		Huffman_List[Initial_NodeNum]->Weight = Huffman_List[Begin]->Weight + Huffman_List[Begin + 1]->Weight;		//新结合节点的权重为两个节点权重的和
		Huffman_List[Initial_NodeNum]->Left_Child = Huffman_List[Begin];		//为了统一,值小的为左孩子
		Huffman_List[Initial_NodeNum]->Right_Child = Huffman_List[Begin + 1];



		//被结合的两个节点的父母亲为新结合的节点
		Huffman_List[Begin]->Parent = Huffman_List[Initial_NodeNum];
		Huffman_List[Begin + 1]->Parent = Huffman_List[Initial_NodeNum];

		Begin += 2;			//两个节点没了,数组下标后移
		Initial_NodeNum += 1;	//节点数加1
	}

	//哈夫曼树构建完成

	//为了保持与输入一致,按输入权重及新节点产生顺序进行排序
	sort(Huffman_List, Huffman_List + Initial_NodeNum, _MyCompare);		
	
	
	Root_Node = Huffman_List[Initial_NodeNum-1];   //保存树根,树根为最后生成的节点
}

void Huffman_Tree::PreOrder_Op(Huffman_Node* &Root)		//先序,不多说
{
	if (Root == NULL)
		return;
	cout << "Weight: " << Root->Weight << endl;
	

	if(Root->Left_Child)
		PreOrder_Op(Root->Left_Child);
	if(Root->Right_Child)
		PreOrder_Op(Root->Right_Child);
}

void Huffman_Tree::_ExcuteCode()			//进行编码
{
	for (int i = 0; i < Leaf_Node_Num; i++)			
	{
		Huffman_Node* _Parent = Huffman_List[i]->Parent;			//_Parent 当前节点的父母节点
		Huffman_Node* _Child = Huffman_List[i];			//_child 当前节点
		Huffman_Code_List[i].Depth = -1;		
		while (_Parent != NULL)
		{
			if (_Child == _Parent->Left_Child)
				Huffman_Code_List[i].Code[++Huffman_Code_List[i].Depth] = '0';		//是左孩子,该段编码0
			else
				Huffman_Code_List[i].Code[++Huffman_Code_List[i].Depth] = '1';		//右孩子,编码1


			//往树根走
			_Child = _Parent;
			_Parent = _Parent->Parent;

		}
	}
}
void Huffman_Tree::PreOrder()
{
	PreOrder_Op(Root_Node);
}

void Huffman_Tree::Create_Code()
{
	_ExcuteCode();
}

void Huffman_Tree::Print_Huffman_Code()
{
	for (int i = 0; i < Leaf_Node_Num; i++)
	{
		int Len = Huffman_Code_List[i].Depth;
		cout << "Weight :" << Huffman_List[i]->Weight << "  Code :";
		for (int j = Len; j >= 0; j--)				//注意要逆序输出就好
			cout << Huffman_Code_List[i].Code[j];
		cout << endl;
	}
}


//test
int main()
{
	int *Weight;
	int Root_Num;
	cin >> Root_Num;
	Weight = new int[Root_Num];
	for (int i = 0; i < Root_Num; i++)
		cin >> Weight[i];

	Huffman_Tree Test_Huffman(Root_Num, Weight);

	Test_Huffman.PreOrder();
	Test_Huffman.Create_Code();
	Test_Huffman.Print_Huffman_Code();
	delete Weight;
	return 0;
}

 

你可能感兴趣的:(数据结构)