哈夫曼编码 C++实现
这是以前写的一道题,题目记不清了,是PTA上的,所以题干应该都差不多,思路都在注释里了
#include <string>
#include <iostream>
using namespace std;
typedef struct {
int weight;
int parent, lchild, rchild;
}HTNode, *HuffmanTree; //赫夫曼树的节点结构
typedef char * * HuffmanCode;
void Select(HuffmanTree HT, int n, int &s1, int &s2)//在HT[1..n]选择parent为0且weight最小的两个结点,其序号分别为s1和s2
{
HuffmanTree p;
int i, j, min;
for (i = 1;; i++)//先找一个没有双亲节点的结点,这个结点的位置给s1和p
{
if (HT[i].parent == 0)
{
s1 = i;
p = HT + i;
break;
}
}
min = p->weight;//先将这个结点的权值记为最小值
p++;
for (i = s1 + 1; i <= n; i++, p++)//从以找到的这个结点的下一个结点开始寻找双亲节点也为零且权值小于它的结点,从而找到目前序列中权值最小的结点
{
if (p->weight < min&&p->parent == 0)
{
s1 = i;
min = p->weight;
}
}
//寻找s2位置的操作同上
for (j = 1;; j++)
{
if (HT[j].parent == 0 && j != s1)
{
s2 = j;
p = HT + j;
break;
}
}
min = p->weight;
p++;
for (j = s2 + 1; j <= n; j++, p++)
{
if (p->weight < min&&p->parent == 0 && j != s1)
{
s2 = j;
min = p->weight;
}
}
}
void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n)
//w存放n个字符的权值(均>0),构造哈夫曼树HT,并求出n个字符的哈夫曼编码HC
{
HuffmanTree p;
int m, i, s1, s2;
char * cd;
if (n <= 1) return;
m = 2 * n - 1;
HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));//创建静态链表,0号单元未用
for (p = HT + 1, i = 1; i <= n; i++, p++, w++) *p = { *w,0,0,0 };//给每个结点的权值赋值,同时令双亲、左孩子、右孩子为空
for (; i <= m; i++, p++) *p = { 0,0,0,0 };//前n个结点肯定为叶节点,从第n+1个结点开始,每个结点都是一个中间结点,权值都不确定,所以先赋为0
for (i = n + 1; i <= m; i++)//建哈夫曼树。在哈夫曼树中,每个结点的都是以在数组HT中的位置来表示的,权值记录在weight中。比如一串字符为5,11,7。1代表权值为5的结点,2代表权值为11的结点,3代表权值为7的结点
{
Select(HT, i - 1, s1, s2);//s1,s2应为地址传递,因为下边的代码需要用到s1,s2的值
HT[s1].parent = i; HT[s2].parent = i;//表示HT中第i个数所在的结点是HT中第s1个数所在节点的双亲结点
HT[i].lchild = s1; HT[i].rchild = s2;//表示HT中第i个数所在的结点的左孩子是第s1个数所在的结点
HT[i].weight = HT[s1].weight + HT[s2].weight; //表示HT中第i个数所在节点的权值是HT中第s1个数所在节点的权值加HT中第s2个数所在节点的权值
}
//---从叶子到根逆向求每个字符的哈夫曼编码---
HC = (HuffmanCode)malloc((n + 1) * sizeof(char *));//分配n个字符编码的头指针向量
cd = (char *)malloc(n * sizeof(char));//分配求编码的工作空间
cd[n - 1] = '\0';//编码结束符
for (i = 1; i <= n; i++)//逐个字符求哈夫曼编码
{
int start, c, f;
start = n - 1;//编码结束符位置
for (c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)//从叶子到根逆向求编码,HT[1..n]就是叶子
{
if (HT[f].lchild == c) cd[--start] = '0';
else cd[--start] = '1';
}
HC[i] = (char *)malloc((n - start) * sizeof(char));//为第i个字符编码分配空间
strcpy(HC[i], &cd[start]);//从cd复制编码到HC
}
free(cd);//释放工作空间
}
int main()
{
int N;
int sum = 0;
char ch[10001];//字符
int w[10001];//权值
cin >> N;
for (int i = 0; i < N; i++)
cin >> ch[i] >> w[i];
HuffmanTree HT;
HuffmanCode HC;
HuffmanCoding(HT, HC, w, N);
for (int i = 1; i <= N; i++)
sum += w[i - 1] * strlen(HC[i]); //统计最优长度
int M, flag[10001];
cin >> M;
string str[10001];
char c;
for (int i = 0; i < M; i++)
{
int n = 0;
for (int j = 0; j < N; j++)
{
cin >> c >> str[j]; //输入任意N个编码
n += str[j].length() * w[j]; //统计在该种编码的情况下所用的字节总数
}
if (n != sum) //如果不等于最优长度
flag[i] = 0;
else
{
int flag2 = 1;
for (int k = 0; k < N - 1; k++) //从第一行编码开始判断其下面的每一行编码是否为该行编码的前缀
{
for (int l = k + 1; l < N; l++)
{
flag2 = str[k].find(str[l]);//find函数,若str[l]是str[k]的子串,则返回str[l]在str[k]中的下标位置,若位置是0,则说明该编码不是前缀编码
if (flag2 == 0) //flag2=0说明str[l]是str[k]的子串,且str[l]在str[k]中的下标位置是0,则该编码不是前缀码
{
flag[i] = 0;
break;
}
}
if (flag2 == 0)
break;
}
if (flag2 != 0)
flag[i] = 1;
}
}
for (int i = 0; i < M - 1; i++)
{
if (flag[i] == 1)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
if (flag[M - 1] == 1)//控制输出格式,最后一次输出不需要换行
cout << "Yes";
else
cout << "No";
getchar();
getchar();
return 0;
}