二元前缀码:任何字符的代码不能作为其它字符代码的前缀.eg.Q={001,00,010,01}不是二元前缀代码,如序列0100001会产生歧义
设C={x1,x2,…,xn}是n个字符的集合,f(xi)为xi出现的频率,d(xi)为xi的码长,i=1,2,…,n.
存储一个字符的平均二进制位数(码数):
$B = \sum\limits_{i=1}^{n}f(x_i)d(x_i)$
每个二元前缀码对应一棵二叉树,树叶代表码字,树叶的深度表示码长,平均二进制位数相当于这棵树在给定频率下的平均深度,也称为这棵树的权
对同一组频率可以构造出不同的二叉树,对应的平均二进制位数也不同。占用位数越少的压缩效率越高,即每个码字平均使用二进制位数最少的前缀码,称为最优二元前缀码
如果叶片数n=2k,且每个码字的频率是1/n,那么这棵树应是一颗均衡的二叉树
问题:对于任意给定的n个频率f(x1),f(x2),…,f(xn),如何构造一棵对应于最优二元前缀码的二叉树?
Huffman算法:
输入:C={x1,x2,…,xn}字符集,每个字符的频率f(xi),i=1,2,…,n.
输出:Q
1.n<-|C|
2.Q<-C //按频率递增构成队列 Q
3.for i<-1 to n-1 do
4. z<-Allocate-Node()
5. z.left<-Q中最小元 //取出Q中最小元作为z的左儿子
6. z.right<-Q中最小元 //取出Q中最小元作为z的右儿子
7. f(z)<-f(x)+f(y)
8. Insert(Q,z)
9.return Q
版本一:(仅求带权路径长)
#include <iostream> #include <queue> using namespace std; int main(){ int n, c[100]; int x, y, ans; priority_queue<int, vector<int>, greater<int> > Q; // "> >"必须分开写,否则会被误认为右移运算符 while(cin >> n){ while (!Q.empty()){ Q.pop(); } for (int i = 0; i < n; ++i){ cin >> c[i]; Q.push(c[i]); } ans = 0; for (int i = 1; i < n; ++i){ //进行n-1轮循环 x = Q.top(); Q.pop(); y = Q.top(); Q.pop(); Q.push(x + y); ans += (x + y); } cout << ans << endl; //带权路径长度(WPL) } return 0; }
版本二:(构造前缀树)
#include <iostream> #include <queue> #include <deque> using namespace std; struct Node{ //typedef struct{}Node;和struct Node{};的区别在于,后者内部可定义Node变量 int freq; Node *left; Node *right; Node():freq(0), left(NULL), right(NULL){} /*friend bool operator < (Node a, Node b){ return a.freq > b.freq; 这段有问题,暂时未改 } */ }; class Cmp{ public: bool operator() (const Node *a, const Node *b) const{ return a->freq > b->freq; } }; priority_queue<Node *, vector<Node *>, Cmp> Q; //priority_queue<Node *> Q; int n; void Buildtree(){ for (int i = 1; i < n; ++i){ Node *x = Q.top(); Q.pop(); Node *y = Q.top(); Q.pop(); //cout << x->freq << ' ' << y->freq << endl; Node *tmp = new Node; tmp->freq = x->freq + y->freq; tmp->left = x; tmp->right = y; Q.push(tmp); //队列push是值复制,so,队列元素采取指针 } } void deletetree(Node *a){ if (a->left) deletetree(a->left); if (a->right) deletetree(a->right); delete a; } void Print(Node *a){ if (a->left) cout << a->left->freq << ' '; if (a->right) cout << a->right->freq << ' '; if (a->left) Print(a->left); if (a->right) Print(a->right); } deque<int> E; void dfs(Node *a){ //遍历树,并输出二进制码 if (!a->left && !a->right){ deque<int> F = E; while (!F.empty()){ cout << F[0]; F.pop_front(); } cout << endl; return; } if (a->left){ E.push_back(0); dfs(a->left); E.pop_back(); } if (a->right){ E.push_back(1); dfs(a->right); E.pop_back(); } } int main(){ int f; cin >> n; for (int i = 0; i < n; ++i){ cin >> f; Node *c = new Node; c->freq = f; Q.push(c); //delete c;不能delete,队列中的元素指针和这里的临时指针同时指向Node } Buildtree(); Node *root = Q.top(); Q.pop(); cout << root->freq << endl; //dfs(root); //Print(root); deletetree(root); return 0; }
个人敲代码时遇到的一些小问题:
1)优先队列中的元素是值传递
例:
int a[] = {5, 4, 3, 2, 1, 10, 9, 8, 7, 6}; priority_queue<int> Q; for (int i = 0; i < 10; ++i) Q.push(a[i]); for (int i = 0; i < 10; ++i){ a[i] = Q.top(); Q.pop(); cout << a[i] << ' '; }
输出结果为:10 9 8 7 6 5 4 3 2 1
因此优先队列中的元素采用指针
2)循环中使用new定义/赋值指针,都会重新申请内存(原本的依然存在),重定向指针
3)优先队列两种使用方法
方法一如之前代码所示
方法二如下(我在“版本二”的注释中试图重现,但出了点小错误,有机会改吧……)
struct Node{ int x; int y; friend bool operator < (Node a, Node b) { return a.x > b.x; //结构体中,x小的优先级高 } }; priority_queue<Node> q; //定义方法
4)friend bool operator < (Node *a, Node *b)会报错,重载运算符不能对指针单独操作,因为系统已经定义了有关指针的运算,不能改变
5)老毛病,每行代码的空格部分不能有中文空格和标点,否则会stray '\241' in program