zoukankan      html  css  js  c++  java
  • 算法作业三-哈夫曼编码

    实验三 哈夫曼编码

    问题描述与实验目的:

    给定n个字母(或字)在文档中出现的频率序列X=<x1,x2,…,xn>,求出这n个字母的Huffman编码。为方便起见,以下将频率用字母出现的次数(或称权值)w1,w2,…,wn代替。

    输入

    输入文件中的开始行上有一个整数T,(0<T<=20),表示有T组测试数据。

    接下来是T行测试数据的描述,每组测试数据有2行。测试数据的第1行上是一个正整数n,(n<50),表示序列的长度。第2行是n个字母出现的权值序列w1,w2,…,wn,它们均为正整数,相邻的两个整数之间用空格隔开。

    输入直到文件结束。

    输出

    对输入中的每组有n个权值的数据,应输出n+1行:先在一行上输出“Case #”,其中“#”是测试数据的组号(从1开始);接下来输出n行,其第1行到第n行上依次输出第i个字母出现的次数和相应的Huffman编码,格式如下:

    wi Huffman编码。

    每组测试数据对应的输出最后结束时加一个空行,以便区分。

    为保证Huffman编码的唯一性,在构造Huffman树的过程中,我们约定:

    1.左儿子标记为0,右儿子标记为1;

    2.左儿子的权值>=右儿子的权值;

    3.相同权值w的两个字母x、y,先输入权值的字母x的Huffman编码长度不超过后输入权值的字母y的Huffman编码长度。

    4.合并两个节点后新的权值应从右到左搜索、插入到相应的位置。

    例如:输入权值序列8 9 3 4 1 2,其Huffman编码求解过程如下,参考图A-J:

    注意,如图C中权值1、2对应的节点合并后得权值为3的新节点,它插入到权值为3的原有节点的右边。

    输入样例

    2
    6
    9 8 3 4 1 2
    8
    60 20 5 5 3 3 3 1

    输出

    Case 1
    9 00
    8 01
    3 100
    4 11
    1 1011
    2 1010

    Case 2
    60 0
    20 10
    5 1101
    5 1110
    3 11000
    3 11001
    3 11110
    1 11111

    分析:

    哈夫曼编码本质是个贪心的算法。将每个字符视作一个带权结点(子树)。
    每次优先选择权值最小的两个子树,将二者合并,权值相加,权值小的作为右子结点(1),权值大的作左子结点(0),形成一个新的子树。再将这棵子树放回优先队列中,重复以上的操作。

    本题要输出每个字符对应的哈夫曼编码,数据n<50较小,所以不考虑用数据结构建树,用直接保存路径的方法实现。自定义一个结构体,内部记录子树的权值和所有叶子结点。
    用优先队列存储所有子树,因为堆排序是一种不稳定排序,可能导致相同权值的子树顺序颠倒,所以重载比较运算符,使用两个关键字排序。
    两个子树合并的时候,将左子树记录的叶子结点编码全部加上''0',右子树记录的结点编码全部加'1'。当队列中结点数为1时,算法结束。
    最后根据读入的顺序输出0-1字符串即可。

    运行结果:

    Alt text

    OJ测评结果:

    Alt text

    源代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int maxn = 105;
    
    string res[55];
    
    struct node{
        int val,id;
        vector<int> dp;
        bool operator < (const node & rhs) const{       //双关键字排序
            if(val==rhs.val) return id < rhs.id;
            return val > rhs.val;
        }
    }vz[maxn];
    
    int main()
    {
        #ifndef ONLINE_JUDGE
            freopen("in.txt","r",stdin);
            freopen("out.txt","w",stdout);
        #endif
        int T,cas=1; scanf("%d",&T);
        while(T--){
            int n;
            scanf("%d",&n);
            for(int i=0;i<=n;++i) res[i].clear();
    
            priority_queue<node> Q;
            for(int i=1,w;i<=n;++i){
                node tmp;
                scanf("%d",&w);
                vz[i].val = w;
                tmp.val = w;
                tmp.id = i;
                tmp.dp.push_back(i);
                Q.push(tmp);             //初始将每个结点视作子树
            }
            int cnt = n+1;
            while(!Q.empty()){
                node x = Q.top(); Q.pop();
                if(Q.empty()) break;
                node y = Q.top(); Q.pop();      //取出权值最小的两个子树
                node t;
                t.val = x.val + y.val;          //合并权值
                t.id = cnt++;
                for(int i=0,sz = x.dp.size() ;i < sz ;++i){          //左子树
                    int id = x.dp[i];
                    res[id].push_back('1');
                    t.dp.push_back(id);         //合并叶子结点
                }
                for(int i=0,sz = y.dp.size();i < sz; ++i){          //右子树
                    int id = y.dp[i];
                    res[id].push_back('0');
                    t.dp.push_back(id);
                }
                Q.push(t);
            }
            printf("Case %d
    ",cas++);
            for(int i=1;i<=n;++i){
                reverse(res[i].begin(),res[i].end());
                cout<<vz[i].val<<" "<<res[i]<<endl;
            }
            puts("");
        }
        return 0;
    }
    
    
  • 相关阅读:
    个人博客
    个人博客
    个人博客
    个人博客
    个人博客
    团队作业—个人记录
    4.21
    4.20
    4.19
    4.18
  • 原文地址:https://www.cnblogs.com/xiuwenli/p/9806340.html
Copyright © 2011-2022 走看看