debug亿年...折腾大半天终于通过所有测试
总结几点重要的
- 答案若是输出的形式(cout),提交时一定要注意删除多余的输出语句(自己debug时可能用了多条输出语句来检查过程)
- 最小堆data[]存放的是树指针,建堆、插入、删除等操作是交换数组元素(即指针本身),而非交换指针所指向的结点的freq值;最小堆本身是个完全二叉树,数组下标之间的关系就是“父子关系”
- 在自己定义得结构体最小堆里嵌套另一结构体指针数组,new 一个最小堆之后还要对其data[]的每一个元素再 new 一个树结点(data[]存放的树指针再指向这些结点)
#include <iostream>
#define MAXNUM 63 // 最大字符数
using namespace std;
// 利用最小堆建立哈夫曼树, 最小堆data[]存放树指针, 通过最小堆排序再提取结点(通过指针)建树
// 核心算法: ①计算最优编码长度 ②对每位学生的提交进行检查:长度&建树时是否满足前缀码要求
typedef struct HTNode *HuffmanTree;
struct HTNode {
int freq; // 频率
HuffmanTree left, right;
};
typedef struct MinHeap *minHeap;
struct MinHeap { // 注意data[]存的是树结点指针, 因为建哈夫曼树要保存结点
HuffmanTree data[MAXNUM+1]; // +1: 哨兵
int size; // 堆中当前元素个数(不含哨兵)
int capacity; // 最小堆容量
};
/*----------------------------最小堆----------------------------*/
/*----------------建造最小堆----------------*/
// 注意区分 堆的结点 与 存放的哈夫曼树结点
// 下滤: 将H中以H->data[p]为根的子堆调整为最小堆
void perceDown( minHeap H, int p ) {
int parent, child;
HuffmanTree X = H->data[p]; // 取出根结点存放的哈夫曼树结点指针
for( parent = p; parent*2 <= H->size; parent = child ) { // 若有子结点
child = parent * 2; // 左子结点(数组下标)
if( (child != H->size) && (H->data[child+1]->freq < H->data[child]->freq) )
child++; // 若有右子结点, 指向更小的结点
if( X->freq < H->data[child]->freq )
break; // 找到了合适的位置
else // 下滤X
H->data[parent] = H->data[child]; // 子结点上移
}
H->data[parent] = X;
}
// 先按顺序输入元素, 满足完全二叉树的结构特性
// 再调整H->data[]中的元素, 使满足最小堆的有序性
minHeap buildHeap( int freq[], int N ) {
minHeap H = new MinHeap; // 创建一个空的最小堆
/* 非常重要非常重要非常重要!!! 一定要逐个new HTNode, 否则段错误, 血泪 */
for( int i = 0; i < MAXNUM+1; i++ )
H->data[i] = new HTNode;
H->data[0]->freq = 0; // 哨兵: 0比任何频率都小
H->size = 0;
H->capacity = N;
for( int i = 0, j = 1; i < MAXNUM; i++ ) // 将freq[]输入到H->data[]->freq
if( freq[i] ) { // 若不为0即有频率
H->data[j++]->freq = freq[i];
H->size++; // 别忘了!
}
// 开始调整, 从最后一个结点的父节点开始, 到根结点1
for( int i = H->size / 2; i > 0; i-- )
perceDown( H, i );
return H;
} // 建毕
bool insert( minHeap H, HuffmanTree X ) { // 插入
int i;
if( H->size == H->capacity ) return false; // 堆满
for( i = ++H->size; H->data[i/2]->freq > X->freq; i /= 2 ) // i指向插入后堆(size++)中的最后一个元素的位置
H->data[i] = H->data[i/2]; // 上滤X
H->data[i] = X; // 将X插入
return true;
}
HuffmanTree deleteMin( minHeap H ) { // 返回并删除最小结点
if( !H->size ) return nullptr; // 堆空
HuffmanTree min = H->data[1]; // 取出根节点(最小结点)
// 用最后一个元素从根节点开始向上过滤下层结点, 删后size--
H->data[1] = H->data[H->size--];
perceDown(H, 1); // 从根结点开始将堆调整为最小堆
return min;
}
/*----------------------------哈夫曼树----------------------------*/
// 借助最小堆创建哈夫曼树, 提高每次取最小的两个值的效率
HuffmanTree Huffman( minHeap H ) {
HuffmanTree T;
int num = H->size; // 重要重要重要!!! 一定要先把H->size存起来, 因为这是要控制循环次数的, 而H->size在改变!!
for( int i = 1; i < num; i++ ) { // 做H->size-1次合并操作
T = new HTNode;
// 每次找两个最小结点作为新T的左右子结点
T->left = deleteMin(H);
T->right = deleteMin(H);
T->freq = T->left->freq + T->right->freq; // 计算新权值
insert(H, T); // 将新T插入最小堆
}
return deleteMin(H);
}
// weighted path length of tree 此处为所有编码长度之和
int WPL( HuffmanTree T, int depth ) { // depth: 当前根结点深度 (深度从0开始)
if( !T->left && !T->right ) return depth * T->freq; // 叶子结点
// 否则T一定有2个孩子(哈夫曼树特性)
else return WPL(T->left, depth+1) + WPL(T->right, depth+1);
}
/*----------------------------判断----------------------------*/
int getIndex( char C ) {
if( C - '0' >= 0 && C - '0' <= 9 ) return C-'0';
else if( C - 'a' >= 0 && C - 'a' <= 25 ) return C-'a'+10;
else if( C - 'A' >= 0 && C - 'A' <= 25 ) return C-'A'+36;
else if( C == '_' ) return MAXNUM-1;
//return -1;
}
bool buildJudgeTree( HuffmanTree tmp, int freq[], char C, string code ) { // 每次tmp从根节点开始
for( int k = 0; k < code.length(); k++ ) {
if( code[k] == '1' ) { // 往右走
if( !tmp->right ) { // 若无右孩子
tmp->right = new HTNode;
tmp->right->left = tmp->right->right = nullptr;
tmp->right->freq = 0;
} // 若有右孩子, 且此点有频率, 则错(不符合前缀码规则)
else if( tmp->right->freq ) return false;
tmp = tmp->right;
}
else { // code[k] == '0' 往左走
if( !tmp->left ) { // 若无左孩子
tmp->left = new HTNode;
tmp->left->left = tmp->left->right = nullptr;
tmp->left->freq = 0;
} // 若有左孩子, 且此点有频率, 则错(不符合前缀码规则)
else if( tmp->left->freq ) return false;
tmp = tmp->left;
}
if( k == code.length()-1 ) { // 编码结尾
if( !tmp->left && !tmp->right ) // 若是叶结点
tmp->freq = freq[ getIndex(C) ]; // 填入频率
else return false; // 否则错
}
}
return true; // 这里true只代表这部分检查是true
}
bool judge( HuffmanTree T, int freq[], int N ) { // 每次judge一个学生
char C;
string code;
int len = 0, codeLen = WPL(T, 0); // len:测试编码总长度 codeLen:最优编码总长度
HuffmanTree judgeT = new HTNode; // 每次judge对应不同的judgeT
judgeT->left = judgeT->right = nullptr;
judgeT->freq = 0;
bool result = true; // 初始化为true
for( int j = 0; j < N; j++ ) { // 对N行字符与编码
cin >> C >> code; // 边输入边检查(计算长度和建树)
if( !result ) continue; // 任一环节判错则错, 但必须继续读取完所有输入, 否则影响下一轮
/* 字符判断 */
if( !freq[ getIndex(C) ] ) {
result = false;
continue;
}
/* 长度判断 */
if( code.length() > N ) { // ????? 为什么视频说单个编码最长为N-1(即62), 但是测试点3却认为长63的code是对的?
result = false; // 单个编码最长为N-1(?)
continue;
}
len += code.length() * freq[ getIndex(C) ];
if( len > codeLen ) { // 超过最优编码总长度
result = false;
continue;
}
/* 建树, 若返回错则错, 若对继续检查 */
if( !buildJudgeTree( judgeT, freq, C, code ) ) result = false;
}
return result;
}
int main() {
int N, M; // N:字符数 M:学生人数
char C; // 输入的字符
int freq[MAXNUM] = {0}; // 频率数组初始化, 其他默认为0
// 0-9:0-9, 10-35:a-z, 36-61:A-Z, 62:_
cin >> N;
for( int i = 0; i < N; i++ )
cin >> C >> freq[ getIndex(C) ];
minHeap H = buildHeap(freq, N); // 创建容量为N的最小堆
HuffmanTree T = Huffman(H); // 建立Huffman树
cin >> M;
for( int i = 0; i < M; i++ ) { // 对M个学生
if( judge(T, freq, N) ) cout << "Yes" << endl;
else cout << "No" << endl;
}
}