简介
哈夫曼树是一种用来对字符进行编码的数据结构,可以根据字符的使用频率来决定字符的二进制表示,使得转化后的二进制序列尽可能短。
哈夫曼树的具体介绍见此博客用漫画介绍哈弗曼树
接下来介绍哈夫曼树的构造方法,假设给定n个字符及其对应的频率(出现次数),求出用哈夫曼编码后编码串的长度。
比如:
4 //字符个数
a 1
b 2
c 3
d 4
会返回19。(如果不知道怎么算请看上面链接的博客)
c++构造
方法一
首先可以想到用优先队列,将包含频率信息的节点加入队列,然后每次从队列中拿出两个最小的节点a和b,求出频率和,构造新的节点,其左右子节点分别指向a和b,将新节点加入队列。重复此过程直至队列中只剩一个节点。
在上面的步骤中我们模拟哈夫曼树的构造过程,确确实实地构造出一棵树来,接下来只需要由这棵树的根节点开始遍历,找到每个叶子节点,将它们的值(即频率)和深度(即编码长度)的乘积加起来,就是我们要的结果。
时间复杂度:O(nlogn)
ps:这个代码没有释放指针,但是这不是要点,所以忽略了。
#include<iostream>
#include<queue>
#include<unordered_map>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
struct Node {
long long val;
Node* left, *right;
Node(long long val) {
this->val = val;
left = NULL;
right = NULL;
}
};
struct cmp {
bool operator()(const Node* a, const Node* b) const{
return a->val > b->val;
}
};
long long search(Node* root, long long level) {
if (root == NULL) return 0;
if (root->left == NULL && root->right == NULL) return root->val * level;
return search(root->left, level + 1) + search(root->right, level + 1);
}
int main() {
long long n;
cin >> n;
if (n == 0) {
cout << 0;
return 0;
}
long long arr[n];
char c;
for (long long i = 0; i < n; ++i) {
cin >> c >> arr[i];
}
if (n == 1) {
cout << arr[0];
return 0;
}
priority_queue<Node*, vector<Node*>, cmp> pq;
for (long long i = 0; i < n; ++i) {
//if (arr[i] <= 0) continue;
Node* t = new Node(arr[i]);
pq.push(t);
}
if (pq.size() == 1) {
cout << pq.top()->val << endl;
return 0;
}
Node* root;
while(pq.size() > 1) {
Node* a = pq.top();
pq.pop();
Node* b = pq.top();
pq.pop();
Node* sum = new Node(a->val + b->val);
sum->left = a;
sum->right = b;
root = sum;
pq.push(sum);
}
cout << search(root, 0) << endl;
}
方法二
如果不想用优先队列,不想自己写比较函数,可以怎样写呢?
注意到一个规律:每次从队列中拿出最小的两个节点按值相加,得到的新节点的值是递增的,假设我们只用普通的数组来存储最初的节点,并且用另一个数组来存储新增的节点,再用两个指针分别指向两个数组的下标,通过一定的控制(从左边数组拿或从右边数组拿)可以实现。具体请看代码。
#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;
int n;
vector<long long> a[2];
int idx[2];
inline long long popleft(int i) {
return a[i][idx[i]++];
}
inline long long getmin() {
if (idx[0] >= (int)a[0].size()) {
return popleft(1);
}
if (idx[1] >= (int)a[1].size()) {
return popleft(0);
}
if (a[0][idx[0]] < a[1][idx[1]]) {
return popleft(0);
}
return popleft(1);
}
int main() {
cin >> n;
if (n == 0) {
cout << 0 << endl;
return 0;
}
long long t1, t2;
char c;
for (int i = 0; i < n; ++i) {
cin >> c >> t2;
a[0].push_back(t2);
}
if (n == 1) {
cout << t2 << endl;
return 0;
}
sort(a[0].begin(), a[0].end());
long long ans = 0;
for (int i = 1; i < n; ++i) {
t1 = getmin();
t2 = getmin();
ans += t1 + t2;
a[1].push_back(t1 + t2);
}
cout << ans << endl;
return 0;
}
时间复杂度依然是O(nlogn)。