构建哈夫曼树

哈夫曼树是一种非常有用的数据结构,通常用于数据压缩。以下是使用C++构造哈夫曼树的步骤:

  1. 定义节点:哈夫曼树的节点包含三个属性,leftright 和 weightleft 和 right 指向左孩子和右孩子,weight 存储节点的权重。

struct Node { 
int weight; 
Node* left; 
Node* right; 
};
  1. 创建节点:为每个输入的权重创建一个新的节点。


	Node* createNode(int weight) { 

	Node* newNode = new Node(); 

	newNode->weight = weight; 

	newNode->left = NULL; 

	newNode->right = NULL; 

	return newNode; 

	}
  1. 构建优先队列:使用weight作为优先队列的优先级。这样,权重最小的节点将总是在队列的前面。

struct Compare { 
bool operator()(Node* l, Node* r) { 
return l->weight > r->weight; 
} 
}; 
std::priority_queue<Node*, std::vector<Node*>, Compare> minHeap;
  1. 构建哈夫曼树:将所有节点添加到优先队列中。然后,从队列中取出两个权重最小的节点(需要进行排序),创建一个新的节点作为这两个节点的父节点,这个新节点的权重是这两个节点的权重之和。然后将这两个节点从队列中删除,将新的父节点添加到队列中。重复这个过程,直到队列中只剩下一个节点,这个节点就是最终的哈夫曼树的根节点。


	while (minHeap.size() > 1) { 

	Node* left = minHeap.top(); 

	minHeap.pop(); 

	Node* right = minHeap.top(); 

	minHeap.pop(); 

	Node* parent = createNode(left->weight + right->weight); 

	parent->left = left; 

	parent->right = right; 

	minHeap.push(parent); 

	}
  1. 从哈夫曼树中构造编码表:从根节点开始,向左孩子走就向编码表中添加0,向右孩子就向编码表中添加1这样,每个节点都会得到一个唯一的二进制编码。同时,记录每个节点到其父节点的距离。
  2. 使用编码表对数据进行编码:对于数据中的每一个字符,从哈夫曼树中找到相应的节点,然后使用从根节点到该节点的路径作为该字符的编码。

例题:基于哈夫曼树的数据压缩算法 

任务描述

输入一串字符串,根据给定的字符串中字符出现的频率建立相应哈夫曼树,构造哈夫曼编码表,在此基础上可以对待压缩文件进行压缩(即编码),同时可以对压缩后的二进制编码文件进行解压(即译码)。

编程要求

输入

多组数据,每组数据一行,为一个字符串(只考虑26个小写字母即可)。当输入字符串为“0”时,输入结束。

输出

每组数据输出2n+4行(n为输入串中字符类别的个数)。第一行为统计出来的字符出现频率(只输出存在的字符,格式为:字符:频度),每两组字符之间用一个空格分隔,字符按照ASCII码从小到大的顺序排列。第二行至第2n行为哈夫曼树的存储结构的终态(形如教材139页表5.2(b),一行当中的数据用空格分隔)。第2n+2行为每个字符的哈夫曼编码(只输出存在的字符,格式为:字符:编码),每两组字符之间用一个空格分隔,字符按照ASCII码从小到大的顺序排列。第2n+3行为编码后的字符串,第2n+4行为解码后的字符串(与输入的字符串相同)。

#include<iostream>
#include<string.h>
#define MAXSIZE 100
using namespace std;
typedef struct
{//哈夫曼树结点的形式
	int weight;               //结点的权值
	int parent,lchild,rchild;  //结点的双亲、左孩子、右孩子的下标
}HTNode,*HuffmanTree;       //动态分配数组存储哈夫曼树
typedef char **HuffmanCode;   //定义编码表类型
int Search(char a[],char ch)
{//查找数组中字符ch所在的位置,返回数组下标,否则返回-1
	for(int i=0;a[i]!='\0';i++)
	{
		if(a[i]==ch) return i;
	}
	return -1;
}
void Sort(char a[],int b[],int len)
{//按ASCII码冒泡排序
/**************begin************/
	int i ,j,t2;
    char t1;
    for(i = 0; i < len - 1; i++){
        for(j = 0; j < len - 1 - i; j++){
            if(a[j] > a[j+1]){
                t1=a[j];
                a[j]=a[j+1];
                a[j+1]=t1;
                t2=b[j];
                b[j]=b[j+1];
                b[j+1]=t2;
            }
        }
    }


    
    /**************end************/
}
void Select_min(HuffmanTree HT,int n,int &s1,int &s2)
{// 在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2
/**************begin************/
	 int min1=MAXSIZE,min2=MAXSIZE,i,min1_j,min2_j;
    for(i=1;i<=n;i++)
    {
        if(HT[i].parent==0)    //选择双亲域为0的结点
        {
            if(HT[i].weight<min1)
            {
                min1=HT[i].weight;   //更新min1为当前最小值
                min1_j=i;            //记录相应下标i
            }
        }
    }
    s1=min1_j;    //记录它在HT中的序号s1
    for(i=1;i<=n;i++)
    {
        if(HT[i].parent==0&&i!=min1_j)   //选择双亲域为0且不是刚才已选择的结点
        {
            if(HT[i].weight<min2)
            {
                min2=HT[i].weight;
                min2_j=i;
            }
        }
    }
    s2=min2_j;    //记录它在HT中的序号s2
    /**************end************/
}
int m;
void CreateHuffmanTree(HuffmanTree &HT,int n,int b[])
{//构造哈夫曼树HT
/**************begin************/
	if(n<=1) return;
    int s1,s2;
    m=2*n-1;            //共有2n-1个结点
    HT=new HTNode[m+1];   //0号单元未用,所以需要动态分配m+1单元,HT[m]表示根结点
    int i;
    for(i=1;i<=m;i++)   //将1~m号单元中的双亲、左孩子、右孩子的下标都初始化为0
    {
        HT[i].parent=0;
        HT[i].lchild=0;
        HT[i].rchild=0;
    }
    for(i=1;i<=n;i++)    //读入前n个单元中叶子结点的权值
        HT[i].weight=b[i-1];
    /*-------------------初始化工作结束,下面开始创建哈夫曼树------------------*/
    for(i=n+1;i<=m;i++)
    {//通过n-1次的选择、删除和合并来创建哈夫曼树
        Select_min(HT,i-1,s1,s2);
        // 在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2
        HT[s1].parent=i;
        HT[s2].parent=i;
        //得到新结点i,从森林中删除s1,s2,将s1和s2的双亲域有0改为1
        HT[i].lchild=s1;   //s1,s2分别作为i的左右孩子
        HT[i].rchild=s2;
        HT[i].weight=HT[s1].weight+HT[s2].weight;   //i的权值为左右孩子权值之和
    }//for



    /**************end************/
}
void CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)
{//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
/**************begin************/
	 HC=new char*[n+1];      //分配存储n个字符编码的编码表空间
    char* cd;
    int i,c,f,start;
    cd=new char[n];         //分配临时存放每个字符编码的动态分配空间
    cd[n-1]='\0';            //编码结束符
    for(i=1;i<=n;i++)        //逐个字符求哈夫曼编码
    {
        start=n-1;           //start开始时指向最后,即编码结束符位置
        c=i;
        f=HT[i].parent;       //f指向结点c的双亲结点
        while(f!=0)          //从叶子结点开始向上回溯,直到根结点
        {
            --start;
            if(HT[f].lchild==i)   //结点c是f的左孩子,则生成代码0
                cd[start]='0';
            else                  //结点c是f的右孩子,则生成代码1
                cd[start]='1';
            c=f;
            f=HT[c].parent;       //继续向上回溯
        }                         //求出第i个字符的编码
        HC[i]=new char[n-start];  //为第i个字符编码分配空间
        strcpy(HC[i],&cd[start]); //将求得的编码从临时空间cd复制到HC的当前行中
    }//for
    delete cd;                   //释放临时空间
    /**************end************/
}
void CharFrequency(char ch[],char a[],int b[],int &j)
{//统计词频
/**************begin************/
    int i,pos;
    for(i=0;ch[i]!='\0';i++)
    {
        pos=Search(a,ch[i]);     //在数组a中查找字符ch[i]的位置
        if(pos==-1)             //如果在a中没有找到
        {
            a[j]=ch[i];        //将字符ch[i]加入数组a中
            a[j+1]='\0';       //数组a的元素末位置为'\0'
            b[j]++;            //词频数组元素b[j]加1
            j++;               //后移指针j
        }
        else
        {
            b[pos]++;          //如果在a中找到了字符ch[i]的位置,对应词频元素数值加1
        }
    }




    /**************end************/
}
void PrintHT(HuffmanTree HT)
{//输出哈夫曼树的存储结构的终态
/**************begin************/
    for(int i=1;i<=m;i++)
    {
        cout<<i<<" "<<HT[i].weight<<" "<<HT[i].parent<<" "<<HT[i].lchild<<" "<<HT[i].rchild<<endl;
    }
    /**************end************/
}
void PrintHC(HuffmanCode HC,char a[],int j)
{//输出每个字符的哈夫曼编码
/**************begin************/
    for(int i=1;i<=j;i++)
    {
        if(i!=j)
            cout<<a[i-1]<<":"<<HC[i]<<" ";
        else
            cout<<a[i-1]<<":"<<HC[i]<<endl;
    }
    /**************end************/
}
int main()
{
	char ch[MAXSIZE];
	int i,j;
	while(cin>>ch)
	{
		if(ch[0]=='0') break;
		HuffmanTree HT;
		char a[MAXSIZE]={'\0'};
		int b[MAXSIZE]={0};
		j=0;      //j统计不同字符的数量
		CharFrequency(ch,a,b,j);   //统计词频
		Sort(a,b,j);     //按ASCII码冒泡排序
		for(i=0;a[i]!='\0';i++)   //输出统计出来的字符和出现频率
		{
			if(a[i+1]!='\0')
				cout<<a[i]<<":"<<b[i]<<" ";
			else
				cout<<a[i]<<":"<<b[i]<<endl;
		}
		//构造哈夫曼树
		CreateHuffmanTree(HT,i,b);    //构造哈夫曼树HT
		PrintHT(HT);      //输出哈夫曼树的存储结构的终态
		//哈夫曼编码
		HuffmanCode HC;    //编码表HC
		CreateHuffmanCode(HT,HC,j);
		PrintHC(HC,a,j);    //输出每个字符的哈夫曼编码
		int k;
		for(i=0;ch[i]!='\0';i++)    //输出编码后的字符串
		{
			for(k=0;k<j;k++)
			{
				if(ch[i]==a[k])
					cout<<HC[k+1];
			}
		}
		cout<<endl;
		cout<<ch<<endl;//输出解码后的字符串(与输入的字符串相同)
	}
	return 0;
}

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值