哈夫曼树从入门到进阶

目录

一、哈夫曼树的初始化

 二、哈夫曼编码

三、WPL计算

四、字符串的哈夫曼编码

五、利用哈夫曼编码实现字符串压缩


一、哈夫曼树的初始化

/**哈夫曼树结点结构*/
struct HTNode
{
    int weight;//结点权重
    int parent, left, right;//父结点、左孩子、右孩子在数组中的位置下标
};
void createHuffmanTree(HTNode * HTArray,int * weightArray,int leafNum)
{
    //哈夫曼数组的长度
    int length=leafNum*2-1;
    //叶子结点初始化
    for(int i=0;i<leafNum;i++)
    {
        HTArray[i].parent=-1;
        HTArray[i].left=-1;
        HTArray[i].right=-1;
        HTArray[i].weight=weightArray[i];
    }
    //非叶子结点初始化
    for(int i=leafNum;i<length;i++)
    {
        HTArray[i].parent=-1;
        HTArray[i].left=-1;
        HTArray[i].right=-1;
        HTArray[i].weight=0;//权值为0
    }

    //构建哈夫曼树
    //k为当前要写的下标,从第一个非叶子结点开始
    for(int k=leafNum;k<length;k++)
    {
        int minIndex1,minIndex2;//两个最小值的下标
        selectMin(HTArray,k,minIndex1,minIndex2);//求最小和次小权值的下标
      
        //minIndex1和minIndex2两个结点构建子树
        HTArray[k].weight=HTArray[minIndex1].weight+HTArray[minIndex2].weight;
        HTArray[k].left=minIndex1;
        HTArray[k].right=minIndex2;
        HTArray[minIndex1].parent=k;
        HTArray[minIndex2].parent=k;
        //cout<<"最小下标"<<minIndex1<<"次小下标"<<minIndex2<<endl;
        //printHufTree(HTArray,leafNum);//打印中间结果
    }
    return ;
}

这段代码就完成了一个哈夫曼数最基本的创建,采用类似于表格的方式存储各类节点的信息。对于每个叶子节点,初始时权重是已知的,其余都赋值为-1表示没有,而其他节点所有元素都是未知的。

接下来,我们展示如何每次选择两个权重最小的节点。

void selectMin(HTNode * HTArray,int k,int & minIndex1,int & minIndex2)
{
    //minIndex1表示权重最小节点索引,minIndex2表示次小节点
    //flag表示当前已经被赋值的最小索引个数
    int flag = 0;
    for(int i = 0; i < k; i ++)
    {
        if(HTArray[i].parent == -1)
        {
           //没有一个索引被赋值,则赋值给第一个索引
           if(!flag)
           {
               minIndex1 = i;
               flag ++;
           }
           else if(flag == 1)    //只有一个被赋值,则判断大小决定赋值
           {
               if(HTArray[i].weight < HTArray[minIndex1].weight)
               {
                   minIndex2 = minIndex1;
                   minIndex1 = i;
               }
               else minIndex2 = i;
               flag ++;
           }
           else
           {
               if(HTArray[i].weight < HTArray[minIndex1].weight)
               {
                   minIndex2 = minIndex1;
                   minIndex1 = i;
               }
               else if(HTArray[i].weight < HTArray[minIndex2].weight) minIndex2 = i;
           }
        }
    }
    
}

 二、哈夫曼编码

在成功实现哈夫曼树的创建之后,我们就可以对每个叶子节点进行哈夫曼编码了。

void huffmanCoding(HTNode* HTArray, char *code[], int leafNum)
{
    char* temp = new char[leafNum];
    temp[leafNum - 1] = '\0';
    for(int i = 0; i < leafNum; i ++)
    {
        int start = leafNum - 1;
        int pos = i;
        int parent = HTArray[i].parent;
        while(parent != -1)
        {
            if(HTArray[parent].left == pos)
                temp[--start] = '0';
            else temp[--start] = '1';
            pos = parent;
            parent = HTArray[parent].parent;
        }
        code[i] = new char[leafNum -start];
        strcpy(code[i], &temp[start]);
    }
    delete temp;
}

由于我们特殊的存储格式,很难实现从父节点一路遍历到孩子节点,所以我们采用逆向获得编码的方式,从孩子节点向上遍历。

三、WPL计算

根据WPL(带权路径长度)的计算公式,可以很容易求出对应哈夫曼树的WPL。

int getWPL(HTNode* HTArray, int leafNum)
{
    int sum = 0;
    for(int i = leafNum; i < 2 * leafNum - 1; i ++)
    {
        sum += HTArray[i].weight;
    }
    return sum;
}

四、字符串的哈夫曼编码

由于输入的是字符串,所以我们需要提取一下字符串里的数据

void getData(string s, int w[],char data[], int &leafNum)
{
    for(auto i : s)
    {
        w[i] ++;        //统计位置为i的字符个数
    }
    for(int i = 0; i < 128; i ++)
    {
        if(w[i])
            data[leafNum ++] = i;    //按顺序把出现过的字符放入data数组
    }
    int k = 0;
    for(int i = 0; i < 128; i ++)
    {
        if(w[i])
            w[k ++] = w[i];    
//把w数组中为零的部分删掉,不为零的下标前移,让data和w数组中相同下标对应的元素分别为该字符,和它的权重   
    }
}

其中s为读取的字符串,w为对应权值数组,data为字符串中的内容,leafNum为叶子节点个数(字符个数)。查ASCII码表得知常见字符最大的对应十进制数为127。

接下来利用函数打印就行

void printCode(char* code[], char* data, int leafNum)
{
    for(int i = 0; i < leafNum; i ++)
    {
        cout << data[i] << ':' << code[i] << endl;
    }
}

五、利用哈夫曼编码实现字符串压缩

对于一个较长的字符串,我们可以利用求出哈夫曼编码对其压缩。

string getCode(char* code[], string s)
{
	string a;
	for(auto i : s)
	{
		a += code[i];
	}
	int t = 0;
	int j = 1;
	string b;
	for(auto i : a)
	{
		if(t == 8)
		{
			t = 0;
			char c = char(j);
			b += c;
			j = 1;
		}
		j <<= 1;
		if(i == '1')
			j ++;
		t ++;
	}
	if(a.size() % 8 != 0)
		j <<= (8 - a.size() % 8);
	b += char(j);
	
	return b;
}

我们知道,ASCII码表中的字符对应的都是八位二进制数,所以,我们可以对得到的编码每八位进行一次转换,得到一个对应的字符。这样就可以把长串的字符串变成短串。对于最后如有剩余,我们采用补零的策略。

对于压缩后的短串,我们同样需要解压缩。

string Sentence(string Code, HTNode* HTArray, int leafNum, int size)
{
	int pos = 2 * leafNum - 2;
	string a;
	bool flag = false;
	for(auto i : Code)
	{
		string b;
		for(int j = 0; j < 8; j ++)
		{
			int x = int(i) >> j & 1;
			b = char(x + '0') + b;
		}
		for(auto i : b)
		{
			if(i == '0')
			{
				if(HTArray[pos].left != -1)
				{
					pos = HTArray[pos].left;
				}
				else
				{
					a += HTArray[pos].data;
					pos = 2 * leafNum - 2;
					pos = HTArray[pos].left;
				}
			}
			else
			{
				if(HTArray[pos].right != -1)
				{
					pos = HTArray[pos].right;
				}
				else
				{
					a += HTArray[pos].data;
					pos = 2 * leafNum - 2;
					pos = HTArray[pos].right;
				}
			}
			if(a.size() == size)
			{
				flag = true;
				break;
			}
		}
	}
	if(!flag) a += HTArray[pos].data;
	return a;
}

对于Code(压缩串)中的每一个字符,我们都需要把它分成八位的二进制串再进行运算。先将字符转成十进制整数,然后从低位向高位依次取出二进制对应数,由于是逆向的,所以要将每一次取出来的数放在前面。由于最后可能被补零了,所以我们要记录原长度,防止多读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值