关于哈夫曼树的理解

关于哈弗曼树的理解

今天我们就来梳理一下哈夫曼树。哈夫曼树的思想我觉得可以归结成,由小到大,逐步合并。为了更好地理解,我们来看一个实际问题吧:

我们知道,在我们使用26键拼音输入时,每一个字母都会有一个使用频率。现在假定 (A:3;B:0.2;C:0.1;D:4),其中这四个字母有各自的权值。当我们将这四个字母进行二进制编码时,我们会引发思考。假设我们固定编码长度,让计算机按固定的二进制位数(如本例子为两位)来进行识别,是一种可行的方法 (A:00 ,B:01 ,C:10 ,D:11)计算机每次取两位来进行识别。

但是这样运行效率就不高了,比如当字母数量增加到26个字母时候,计算机为了准确识别,只能增加识别二进制的位数。我们不禁思考,如果让频繁出现的 D字母能更快的识别,就能增加效率了。此时,哈弗曼树便因运而生。先看看上述例子的哈弗曼树构建过程。

关于哈夫曼树的理解_第1张图片

可以看到每个字母就是各个叶子,如果按照左0右1的规则,那么可以编写成:D :1 , A:01,C:001,B:000 。(这里是因为)我们可以发现,这样编写的好处是识别的唯一性。比如一段二进制信息(101000),我们发现只有唯一一个结果:DAB。不存在因为编码的重合导致识别错误。

我先给出哈夫曼树及其编码,然后再进行分析

#include
#include
using namespace std;

#define leaves 8              //叶子数目 
#define pointSum leaves*2-1    //节点总数
#define MaxValue 10000         //最大权值 

typedef struct{              //构造哈弗曼树的节点类型 
	char ch;
	double weight;
	int parent;
	int Lchild,Rchild;
}Htreetype;

typedef struct{              //构建哈弗曼树码的类型 
	int bit[leaves];
	int start;
	char ch;
}Hcodetype;

void select(Htreetype t[],int k,int *p1,int *p2)  //找最小节点的点 
{
	*p1 = *p2 = 0;
	int smallOne,smallTwo;
	smallOne = smallTwo = MaxValue;
	int i;
	for(i=0; i<k; i++)
	{
		if(t[i].parent == -1)
		{
			if(t[i].weight < smallOne)
			{
				smallTwo = smallOne;
				smallOne = t[i].weight;
				*p2 = *p1;
				*p1 = i;
			}
			else if(t[i].weight <smallTwo)
			{
				smallTwo = t[i].weight;
				*p2 = i;
			}
		}
	}
}

void HffmanTree(Htreetype t[])
{
	int i,p1,p2;
	double value;
	p1 = p2 = 0;
	for(i=0; i<pointSum; i++)       //初始化 
	{
	    t[i].weight = 0;
	    t[i].parent = -1;
	    t[i].Lchild = -1;
	    t[i].Rchild = -1;
    }
    cout<<"-----------请输入8字母的权值-----------"<<endl;
    
    for(i=0;i<leaves;i++)          //输入权值 
    {
		cin>>t[i].ch;
		cin>>value;
		t[i].weight = value;
	}
	
	for(i=leaves; i<pointSum;i++)     //建立哈弗曼树数组 
    {
		select(t,i,&p1,&p2);
		t[p1].parent = i;             //找出权重最小的两个并且合并,合并后共同parent为i 
        t[p2].parent = i;
        t[i].Lchild = p1;
        t[i].Rchild = p2;
        t[i].weight = t[p1].weight + t[p2].weight;  //合并后parent的权重为左右孩子的权值之和 
        
	}
}

void HffmanCode(Hcodetype code[],Htreetype t[])
{
	int i,c,p;
	Hcodetype cd;      //缓冲变量,暂时存储
	HffmanTree(t);
    for (i = 0; i < leaves; i++)
    {
        cd.start = leaves;  
        cd.ch = t[i].ch;
        c = i;                  //从叶子结点向上
        p = t[i].parent;       //t[p]是t[i]的parent
        while (p != -1)        //判断的条件是直到不为顶端 
        {  
            cd.start--;        //用来记录该叶子在第几层 
            if (t[p].Lchild == c)
                cd.bit[cd.start] = 0;           //左子树编为0
            else
                cd.bit[cd.start] = 1;          //右子树编为1
            c = p;                              //再向上移动,继续寻亲之旅 
            p = t[c].parent;
        }
        code[i] = cd;                           //第i+1个字符的编码存入code
    } 
}

void show(Htreetype t[], Hcodetype code[])
{
    int i, j;
    for (i = 0; i<leaves; i++)
    {
        cout<<code[i].ch<<" ";
        for (j = code[i].start; j<leaves; j++)    //编码的长度等于总层数减去所在层数,即从顶层到叶子 
            cout<<code[i].bit[j]<<" ";            // 所要经历的层数 
        cout<<endl;
    }
}

int main()
{
	Htreetype t[pointSum];
    Hcodetype code[leaves];
    HffmanCode(code, t);
    show(t,code);
    return 0;
}
typedef struct{                //构造哈弗曼树的节点类型 
	char ch;                   //存储数据类型
	double weight;             //该数据的权值
	int parent;                //父母
	int Lchild,Rchild;         //左右孩子
}Htreetype;

先建立一个结构体,结构体中有存储数据类型,权值,父母和左右孩子。

void HffmanTree(Htreetype t[])
{
	int i,p1,p2;
	double value;
	p1 = p2 = 0;
	for(i=0; i<pointSum; i++)       //初始化 
	{
	    t[i].weight = 0;
	    t[i].parent = -1;
	    t[i].Lchild = -1;
	    t[i].Rchild = -1;
    }
    cout<<"-----------请输入8字母的权值-----------"<<endl;
    
    for(i=0;i<leaves;i++)          //输入权值 
    {
		cin>>t[i].ch;
		cin>>value;
		t[i].weight = value;
	}
	
	for(i=leaves; i<pointSum;i++)     //建立哈弗曼树数组,从最后的元素开始
    {
		select(t,i,&p1,&p2);          //注意函数的参数是传地址,这样就可以改变形参p1和p2
		t[p1].parent = i;             //找出权重最小的两个并且合并,合并后共同parent为i 
        t[p2].parent = i;
        t[i].Lchild = p1;             //将最小权值的元素所在列表的所在位置作为左孩子
        t[i].Rchild = p2;             //将第二小权值的元素所在列表的所在位置作为右孩子
        t[i].weight = t[p1].weight + t[p2].weight;  //合并后parent的权重为左右孩子的权值之和 
        
	}
}

void select(Htreetype t[],int k,int *p1,int *p2)  //找最小节点的点 
{
	*p1 = *p2 = 0;
	int smallOne,smallTwo;
	smallOne = smallTwo = MaxValue;               //先将值初始化,其中smallone为最小,smallTwo为
	int i;                                        //第二小
	for(i=0; i<k; i++)
	{
		if(t[i].parent == -1)                     //找出并未读取过的点
		{
			if(t[i].weight < smallOne)
			{
				smallTwo = smallOne;
				smallOne = t[i].weight;
				*p2 = *p1;                      //p2得到第二小权值的元素所在列表的所在位置
				*p1 = i;                        //p1得到最小权值的元素所在列表的所在位置i
			}
			else if(t[i].weight <smallTwo)      //这里考虑当前元素大于第一小的p1,但小于第二小p2
			{
				smallTwo = t[i].weight;
				*p2 = i;
			}
		}
	}
}



然后是建立哈弗曼树的过程,首先先初始化哈夫曼树,然后找出哈弗曼树中最小的两个元素进行合并,并把刚刚已经使用的那两个元素进行标记,最后可以得到如下哈夫曼树的列表形式。

关于哈夫曼树的理解_第2张图片

void HffmanCode(Hcodetype code[],Htreetype t[])
{
	int i,c,p;
	Hcodetype cd;      //缓冲变量,暂时存储
	HffmanTree(t);     //调用函数,先建立哈夫曼树
    for (i = 0; i < leaves; i++)
    {
        cd.start = leaves;      //叶子数量就是层数,可以参考我发的第一张图,而第几层也决定哈夫曼编
        cd.ch = t[i].ch;        //码的长度
        c = i;                  //从叶子结点向上
        p = t[i].parent;       //t[p]是t[i]的parent
        while (p != -1)        //判断的条件是直到不为顶端,顶端并没有父母,所以parent值为-1
        {                    
            cd.start--;        //用来记录该叶子在第几层 
            if (t[p].Lchild == c)
                cd.bit[cd.start] = 0;           //左子树编为0
            else
                cd.bit[cd.start] = 1;          //右子树编为1
            c = p;                              //再向上移动,继续寻亲之旅 
            p = t[c].parent;
        }
        code[i] = cd;                           //第i+1个字符的编码存入code
    } 
}

然后是哈夫曼编码的过程,先取出叶子,然后由叶子开始向上寻亲,然后逐步判断自己是左孩子还是右孩子,根据左0右1的规则进行哈夫曼编码。

void show(Htreetype t[], Hcodetype code[])
{
    int i, j;
    for (i = 0; i<leaves; i++)
    {
        cout<<code[i].ch<<" ";
        for (j = code[i].start; j<leaves; j++)    //编码的长度等于总层数减去所在层数,即从顶层到叶子 
            cout<<code[i].bit[j]<<" ";            // 所要经历的层数 
        cout<<endl;
    }
}

最后是输出哈夫曼代码的过程。看一下运行结果

关于哈夫曼树的理解_第3张图片

你可能感兴趣的:(算法)