题目地址:05-树9 Huffman Codes
求解过程:
判断组编码是否是Huffman编码需要满足两个条件:
1. 没有共同前缀(这里需要说明一下,在信息论中前缀码表示的没有共同前缀的编码,因此这里我用没有共同前缀来表述)。
2. 长度与Huffman编码长度相同
我在这里用了一个最小堆来存放编码的长度,然后通过循环依次取出最小堆顶部的元素来构建Huffman树,通过Huffman树来计算WPL(最短带权路径长度)。然后通过对编码建树来判断是否有共同前缀和计算输入编码的WPL共同判定是否为Huffman编码。
奇怪的地方:
不知道为什么我用(TNode*)malloc(sizeof(TNode))和new(TNode)会得到完全不同的结果,前者无法通过,会出现段错误,希望有大佬指教。
程序:
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
#define MaxNum 64
typedef struct TNode *Huffman;
struct TNode
{
int weight;
TNode *left = NULL, *right = NULL;
int flag = 0; // 用于判定是否有共同前缀
};
typedef struct HeapNode *MinHeap; // 最小堆
struct HeapNode
{
TNode Data[MaxNum];
int size;
};
MinHeap CreateHeap(int MaxCapacity) // 初始化最小堆
{
MinHeap H = (MinHeap)malloc(sizeof(HeapNode));
H->size = 0;
H->Data[0].weight = -1; // 第0个元素做哨兵,存最小元素
return H;
}
void Insert(MinHeap H, TNode *item) // 插入元素
{
int i = ++(H->size);
for (; H->Data[i/2].weight > item->weight; i /= 2)
H->Data[i]= H->Data[i/2];
H->Data[i] = *item;
}
TNode* DeleteMin(MinHeap H)
{
TNode *item = (TNode*)malloc(sizeof(TNode));
*item = H->Data[1]; // 取出第一个结点(0号元素是哨兵)
TNode temp = H->Data[(H->size)--]; // 用最后一个元素来填充空白
int child, parent;
for (parent = 1; parent*2 <= H->size; parent = child)
{
child = parent * 2;
if ((child != H->size) && (H->Data[child].weight > H->Data[child+1].weight))
child++;
if (temp.weight <= H->Data[child].weight) // 如果找到合适的位置插入
break;
else
H->Data[parent] = H->Data[child];
}
H->Data[parent] = temp;
return item;
}
MinHeap ReadNode(int N, MinHeap H, int NodeWeight[]) // 读取各个结点的权重并插入
{ /* NodeWeight 用于暂时存放结果*/
char ch;
int weight;
for (int i = 0; i < N; i++)
{
cin >> ch >> weight;
NodeWeight[i] = weight;
// TNode* T = (Huffman)malloc(sizeof(TNode)); // 这两个有差别?
TNode *T = new(TNode);
T->weight = weight;
Insert(H, T);
}
return H;
}
TNode* BuildHuffman(MinHeap H) // 构造Huffman树
{ /* 注意,这里一定不能在for循环里面写H->size-1,因为
这样写的话,每次DeleteMin操作都会修改H->size的值*/
int num = H->size-1;
TNode* T;
for (int i = 0; i < num; i++) // 只要合并size-1次即可
{
T = (TNode*)malloc(sizeof(TNode));
T->left = DeleteMin(H);
T->right = DeleteMin(H);
T->weight = T->left->weight + T->right->weight;
Insert(H, T);
}
T = DeleteMin(H);
return T;
}
int WPL(Huffman T, int depth) // 计算带权路径长度
{
if ((!T->left) && (!T->right)) // 如果是根结点
{
return (T->weight) * depth;
}
else // 如果不是根结点
{
return (WPL(T->left, depth+1) + WPL(T->right, depth+1));
}
}
bool Judge(string code, Huffman H) // 判断是否有共同前缀
{
int len = code.length(), i;
for (i = 0; i < len; i++)
{
if (code[i] == '0') // 如果为0就往左子树生长
{
if (!H->left) // 如果左子树为空
{
TNode *Node = new(TNode);
H->left = Node;
}
else // 如果左子树存在且已有结点,沿着该路径下去一定会变成前缀码
if (H->left->flag == 1)
return false;
H = H->left;
}
else
{
if (!H->right) // 如果右子树为空
{
TNode *Node = new(TNode);
H->right = Node;
}
else // 如果右子树存在且已有结点,沿着该路径下去一定会变成前缀码
if (H->right->flag == 1)
return false;
H = H->right;
}
}
H->flag = 1; // 结束循环后,将该点flag置为1表示有结点
if (!H->left && !H->right) // 如果这个是根结点,说明是正确的,否则是前缀码
return true;
else
return false;
}
int main()
{
int N, M, HuffLen, testWPL;
cin >> N;
int NodeWeight[N]; // 保存结点权重
MinHeap H = CreateHeap(N); // 初始化最小堆
H = ReadNode(N, H, NodeWeight); // 建立最小堆
Huffman T = BuildHuffman(H); // 建立Huffman树
HuffLen = WPL(T, 0); // 计算带权路径
cin >> M;
char letter; // 保存字母
string code; // 保存编码
for (int i = 0; i < M; i++)
{ /* 通过构造Huffman树来确定是否有共同前缀 */
int Flag = 0; // 判断是否合法,0表示合法
/* 想不明白下面两种写法有什么区别, 用上面的通不过 */
// TNode *judge = (TNode*)malloc(sizeof(TNode));
TNode *judge = new(TNode);
testWPL = 0; // 输入编码的WPL
for (int j = 0; j < N; j++)
{
cin >> letter >> code;
testWPL += code.length() * NodeWeight[j];
if (!Flag)
{
if (!Judge(code, judge)) // 如果是有共同前缀
Flag = 1;
}
}
/* 如果既没有共同前缀并且编码长度和Huffman编码长度相同 */
if (!Flag && (testWPL == HuffLen))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0;
}