【初级算法剖析】计算字符串需要的哈夫曼长度

算法要求和答案并不复杂,不复制,不直接贴结果,而是加一些个人分析,面向非算法专业的人员。

背景:

         哈夫曼编码,构造一个二叉树,左支代表编码0,右支1,从根节点到叶子节点就是编码,这样每个树叶节点都是具有唯一不等长编码的,通用用于无损压缩。为了合理利用空间,频率赵越高的,越要短,靠近根。每个元素有一个相应的权重,将子节点的权重加和,就表示这一支的总权重。自然权重越小,越要靠底层。所以,构建过程是:取权重最小的2个元素,形成树,根是权重和。将根放回剩余元素,迭代这个过程。细节不再解释。由于解析编码是从根开始的,这使的一个连续的编码,有固定的解析路径,也是就唯一的结果。

要求:

          计算一个字符串需要的编码长度,编码权重是单词各字母的单词内出现频率。

技巧:

          初步想法自然是先构建哈夫曼树,然后计算出每个字母的长度,相加即可。但是,由于频率并不是全局统计出来,而是相对单词,所有节点路径都走且只走一遍,长度和刚好等价于权值和,结果实际是所有叶子节点的路径,不含根,也可以理解为含根不含 叶子,叶子和根分别是路径的两端,加和等值,计算编码时, 高度比层次少1,所以要减去一边。用队列处理的话,不计根更方便。举例如下:

字符串:1123,1频率是2,2、3是1,所以,哈夫曼树如下,1的编码是:0,频率最高也是短,2、3是10,11,总编码是001011

不含叶节点的话,1高度1,占2位,可以理解为加了一次1的权重。2、3各占1位,高度2 ,可以理解为加1次。由于根和叶总和等值,所以思路设计为:

优先队列取小值,放入所有叶节点,即单词字母统计,每出2个,加和放回去,直到队列不足2个,丢弃根节点。这个思路设计,使长度是1时,要单独返回根节点。

下图中,圆圈上的都是权重,外面的红色是数字字符:


代码如下:

    int GetHFLen(string s) override
    {
        vector<int> c_size(128, 0);     //模型是hash,字母有限,用数组更快速
        for(size_t i = 0; i < s.length(); i++)
        {
            c_size[s[i]-0]++;           //通过-0变char为整数
        }
        cout << VecToStr(c_size, ' ') << endl;
        priority_queue<int, vector<int>, greater<int>> q;
        for(size_t i = 0; i < c_size.size(); i++)
        {
            int val = c_size[i+'A'];
            if(val > 0)         //因为用了数组做hash所以要排除空值以降低后续计算量
                q.push(val);
        }
        if(q.size() == 1)       //只有1个节点情况
            return q.top();
        int sum = 0;            //初值0,涵盖了空串情况
        while(q.size()>1)
        {
            int v1 = q.top();
            q.pop();
            v1 += q.top();
            q.pop();
            sum += v1;
            q.push(v1);
        }
        return sum;
    }



算法:个人认为,前期靠智商,聪明就行。中后期要靠经验了,不然智商再好,也是在重走科研路线,耗不起,要基于别人的成果向前。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值