问题描述:
给定一段文字,求出占用位数最小的前缀码。
名词解释:
前缀码:任何代码都不是其他代码的前缀,比如:
l ----> 0
o ----> 10
v ----> 110
e ----> 1110
那么单词“love”的前缀码就是: 0101101110
问题解释:
求给定文字的最小前缀码,即根据频率来确定每一个字母的变长码长度,我们即可用到哈夫曼编码:
哈夫曼编码:
像上图演示的那样,哈夫曼编码我们需要用到二叉树来进行编码,再观察可知,我们是对频率进行了排序,选定最小两个字母来进行组合
(1).因此,我们需要定义一个数据结构,这个数据结构可以根据频率大小(权值)排序,这个数据结构还可以保存左、右叶子节点,可以保存字母的索引编号
struct node{
int weight;//权值
int depth;//后面遍历时需要的深度
int l,r,num;//左右叶子节点、索引编号
char t;//字母
node(){};
node(int w, int n)
{
this->weight = w;
this->num = n;
}
bool operator<(const node& a)
const{
return this->weight > a.weight;
}
}Leaf[1001];
(2).接下来我们进行输入操作,输入的时候,我们将输入的N个字母及其频率存储在Leaf【1:N】中,并建立优先队列(注意,优先队列与前面的Leaf【1:N】是分别独立的两组数据)
int main()
{
int N,i;
scanf("%d",&N);
for(i = 1; i <= 2 * N - 1; i++)
Leaf[i].l = Leaf[i].r = 0;//先初始化l和r
std::priority_queue <node> Q;
for(i = 1; i <= N; i++)
{
scanf("\n%c %d",&Leaf[i].t, &Leaf[i].weight);
Leaf[i].num = i;
Q.push(node(Leaf[i].weight, i));//将该数据的权值和索引编号压入优先队列
}
(3).再接下来就是核心的建树操作了,前面已经确定了Leaf【1:N】的数据值,下面我们就是根据压入优先队列的数据继续填充Leaf,并建立二叉树:
for(i = 1; i <= N - 1; i++)//最多继续加N - 1个数据
{
node L = Q.top();
Q.pop();
if(Q.empty())
break;//若取一下之后队列空了,证明已经加到头了;
node R = Q.top();
Q.pop();//取出最小的两个数据分别作为左节点和右节点
int ws = L.weight + R.weight;//权值之和
Q.push(node(ws, i + N));//将求得的数据之和压入优先队列
//***********建树***********:
Leaf[i + N].l = L.num;
Leaf[i + N].r = R.num;
Leaf[i + N].num = i + N;
}
(4).建树之后就是遍历操作了,我们用先序遍历,在遍历的时候,将哈夫曼编码写入提前定义的二维数组中:
memset(hfm, 0, sizeof(hfm));
BL(i + N - 1, 0);//从最后存入的数据即根节点开始遍历;
for(i = 1; i <= N; i++)
{
printf("%c: ",Leaf[i].t);
for(int j = 0; j < Leaf[i].depth; j++)
{
printf("%d", hfm[i][j]);
}
printf("\n");
}
return 0;
}
int函数到此就结束了,接下来给出所用到的遍历函数:
int hfm[1001][1001], sum = -1;//sum用来记录遍历的层数;
void BL(int n, int f)//n代表节点序号,f代表是左叶子还是右叶子
{
if(sum >= 0)//第二层才开始给出哈夫曼编码;
{
Leaf[n].depth = sum;
if(f == 0)
hfm[n][sum] = 0;
else if(f == 1)
hfm[n][sum] = 1;
if(Leaf[n].l != 0 && Leaf[n].r != 0)//如果有左右节点,则传值
{
for(int i = 0; i <= sum; i++)
{
hfm[Leaf[n].l][i] = hfm[n][i];
hfm[Leaf[n].r][i] = hfm[n][i];
}
}
else // 如果没有,则返回;
return;
}
sum++;
BL(Leaf[n].l,0);
BL(Leaf[n].r,1);
sum--;
return;
}
到此函数就已经全部给出了(亲测有效吼吼吼)
贴一下完整代码:
#include<iostream>
#include<stdlib.h>
#include<string.h>
#include<queue>
struct node{
int weight;//权值
int depth;//后面遍历时需要的深度
int l,r,num;//左右叶子节点、索引编号
char t;//字母
node(){};
node(int w, int n)
{
this->weight = w;
this->num = n;
}
bool operator<(const node& a)
const{
return this->weight > a.weight;
}
}Leaf[1001];
int hfm[1001][1001], sum = -1;//sum用来记录遍历的层数;
void BL(int n, int f)//n代表节点序号,f代表是左叶子还是右叶子
{
if(sum >= 0)//第二层才开始给出哈夫曼编码;
{
Leaf[n].depth = sum;
if(f == 0)
hfm[n][sum] = 0;
else if(f == 1)
hfm[n][sum] = 1;
if(Leaf[n].l != 0 && Leaf[n].r != 0)//如果有左右节点,则传值
{
for(int i = 0; i <= sum; i++)
{
hfm[Leaf[n].l][i] = hfm[n][i];
hfm[Leaf[n].r][i] = hfm[n][i];
}
}
else // 如果没有,则返回;
return;
}
sum++;
BL(Leaf[n].l,0);
BL(Leaf[n].r,1);
sum--;
return;
}
int main()
{
int N,i;
scanf("%d",&N);
for(i = 1; i <= 2 * N - 1; i++)
Leaf[i].l = Leaf[i].r = 0;//先初始化l和r
std::priority_queue <node> Q;
for(i = 1; i <= N; i++)
{
scanf("\n%c %d",&Leaf[i].t, &Leaf[i].weight);
Leaf[i].num = i;
Q.push(node(Leaf[i].weight, i));//将该数据的权值和索引编号压入优先队列
}
for(i = 1; i <= N - 1; i++)//最多继续加N - 1个数据
{
node L = Q.top();
Q.pop();
if(Q.empty())
break;//若取一下之后队列空了,证明已经加到头了;
node R = Q.top();
Q.pop();//取出最小的两个数据分别作为左节点和右节点
int ws = L.weight + R.weight;//权值之和
Q.push(node(ws, i + N));//将求得的数据之和压入优先队列
//***********建树***********:
Leaf[i + N].l = L.num;
Leaf[i + N].r = R.num;
Leaf[i + N].num = i + N;
}
memset(hfm, 0, sizeof(hfm));
BL(i + N - 1, 0);//从最后存入的数据即根节点开始遍历;
for(i = 1; i <= N; i++)
{
printf("%c: ",Leaf[i].t);
for(int j = 0; j <= Leaf[i].depth; j++)
{
printf("%d", hfm[i][j]);
}
printf("\n");
}
system("pause");
return 0;
}
运行结果如下:
以上!