算法要求和答案并不复杂,不复制,不直接贴结果,而是加一些个人分析,面向非算法专业的人员。
背景:
哈夫曼编码,构造一个二叉树,左支代表编码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;
}
算法:个人认为,前期靠智商,聪明就行。中后期要靠经验了,不然智商再好,也是在重走科研路线,耗不起,要基于别人的成果向前。