05-树9 Huffman Codes

Huffman Codes

debug亿年...折腾大半天终于通过所有测试

总结几点重要的

  1. 答案若是输出的形式(cout),提交时一定要注意删除多余的输出语句(自己debug时可能用了多条输出语句来检查过程)
  2. 最小堆data[]存放的是树指针,建堆、插入、删除等操作是交换数组元素(即指针本身),而非交换指针所指向的结点的freq值;最小堆本身是个完全二叉树,数组下标之间的关系就是“父子关系”
  3. 在自己定义得结构体最小堆里嵌套另一结构体指针数组,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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

低冷dl

喜欢您来~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值