PTA 05-哈夫曼编码 哈夫曼编码

题目描述:

给定一段文字,如果我们统计出字母出现的频率,是可以根据哈夫曼算法给出一套编码,使得用此编码压缩原文可以得到最短的编码总长。然而哈夫曼编码并不是唯一的。例如对字符串"aaaxuaxz",容易得到字母 'a'、'x'、'u'、'z' 的出现频率对应为 4、2、1、1。我们可以设计编码 {'a'=0, 'x'=10, 'u'=110, 'z'=111},也可以用另一套 {'a'=1, 'x'=01, 'u'=001, 'z'=000},还可以用 {'a'=0, 'x'=11, 'u'=100, 'z'=101},三套编码都可以把原文压缩到 14 个字节。但是 {'a'=0, 'x'=01, 'u'=011, 'z'=001} 就不是哈夫曼编码,因为用这套编码压缩得到 00001011001001 后,解码的结果不唯一,"aaaxuaxz" 和 "aazuaxax" 都可以对应解码的结果。本题就请你判断任一套编码是否哈夫曼编码。

输入格式:

首先第一行给出一个正整数 N(2≤N≤63),随后第二行给出 N 个不重复的字符及其出现频率,格式如下:

c[1] f[1] c[2] f[2] ... c[N] f[N]

其中c[i]是集合{'0' - '9', 'a' - 'z', 'A' - 'Z', '_'}中的字符;f[i]c[i]的出现频率,为不超过 1000 的整数。再下一行给出一个正整数 M(≤1000),随后是 M 套待检的编码。每套编码占 N 行,格式为:

c[i] code[i]

其中c[i]是第i个字符;code[i]是不超过63个'0'和'1'的非空字符串。

输出格式:

对每套待检编码,如果是正确的哈夫曼编码,就在一行中输出"Yes",否则输出"No"。

注意:最优编码并不一定通过哈夫曼算法得到。任何能压缩到最优长度的前缀编码都应被判为正确。

输入样例:

7
A 1 B 1 C 1 D 3 E 3 F 6 G 6
4
A 00000
B 00001
C 0001
D 001
E 01
F 10
G 11
A 01010
B 01011
C 0100
D 011
E 10
F 11
G 00
A 000
B 001
C 010
D 011
E 100
F 101
G 110
A 00000
B 00001
C 0001
D 001
E 00
F 10
G 11

输出样例:

Yes
Yes
No
No

大致意思就是:给你一些字符,以及字符出现次数,输入字符编码要你判断是不是哈夫曼编码。

解题思路:解读题意,即要你比较是不是案例编码是不是最优长度的前缀编码。那么就要判断两方面:

        1、是最优长度编码(也就是WPL)。

        2、符合前缀编码性质(即任何一个编码都不是其他某一个编码的前缀,比如案例四中,E的编码00正是A、B、C、D的前缀编码,这样在解码时会产生歧义,也就是结果不一)。

首先我们要求的是最优长度编码。

举个例子:构造A1 B2 C2 D5 E9的哈夫曼树以及求其WPL。

        这便是构造出的哈夫曼树。WPL = (|A|+|B|)* 4  +  |C|  *  3  +  |D|  *  2  +  |E|  *  1  =  37。这个是使用权值乘以路径长度,但是在计算其中非叶子节点的权值时,3 包含了1个(1+2),5是用3得来的,所以也包含了一个(1+2),同理10和19也是一样,所以如果直接将所有非叶子节点相加
WPL = 3 + 5 + 10 + 19 = 37。

所以我们可以用优先队列处理,代码如下:

priority_queue<int , vector<int>,greater<int>> que;
int main()
{
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++)
    {
        char x;
        int num;
        cin >> x >> num;
        q[x] = num;
        que.push(num);
    }
    int ans = 0;
    while(que.size() >= 2)
    {
        int a = que.top();    que.pop();
        int b = que.top();    que.pop();
        que.push(a+b);
        ans += a+b;
    }
}

这样求出的ans就是最优长度编码了。

接下来就是判断前缀编码了:

bool check(string s1,string s2)
{
    int a = min(s1.size(),s2.size());
    for(int i = 0;i < a;i++)
    {
        if(s1[i] != s2[i])
        {
            return true;
        }
    }
    return false;
}

方法也很简单,就是逐个比较如果有不同的就满足了。

AC代码如下:

#include<bits/stdc++.h>
using namespace std;
map<char,int> q;
int data[1010];
vector<int> arr;
priority_queue<int , vector<int>,greater<int>> que;
bool check(string s1,string s2)
{
    int a = min(s1.size(),s2.size());
    for(int i = 0;i < a;i++)
    {
        if(s1[i] != s2[i])
        {
            return true;
        }
    }
    return false;
}
int main()
{
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++)
    {
        char x;
        int num;
        cin >> x >> num;
        q[x] = num;
        que.push(num);
    }
    int ans = 0;
    while(que.size() >= 2)
    {
        int a = que.top();    que.pop();
        int b = que.top();    que.pop();
        que.push(a+b);
        ans += a+b;
    }
    //cout << ans << endl;
    int tt;
    cin >> tt;
    while(tt--)
    {
        int cnt = 0;
        string s[1010];
        for(int i = 1;i <= n;i++)
        {
            char x;
            cin >> x >> s[i];
            cnt += q[x]*s[i].size();//求案例的编码长度
        }
        //check最优
        if(cnt != ans)
        {
            arr.push_back(0);
            continue;
        }
        //check是否满足不是前缀编码
        int flag = 0;
        for(int i = 1;i <= n;i++)
        {
            for(int j = i+1;j <= n;j++)
            {
                if(!check(s[i],s[j]))
                {
                    arr.push_back(0);
                    flag = 1;
                    break;
                }
            }
            if(flag)
            {
                break;
            }
        }
        if(!flag)
        {
            arr.push_back(1);
        }
    }
    for(int i = 0;i < arr.size();i++)
    {
        if(arr[i])
        {
            cout << "Yes" << endl;
        }
        else{
            cout << "No" << endl;
        }
    }
    return 0;
}

同样将判断结果先放入vector容器,最后统一输出。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值