数据结构 第五章 树-应用-并查集与哈夫曼树
** 树的应用**
【树的并查集】
并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。
合并:合并两个集合
查找:判断两个元素是否在一个集合
A E H 为根结点的三棵树:
root(F) == E root(D) == A, 他们不是同一个根结点,所以可以对 D 所在的集合 与 F 所在的集合进行合并,如下:
继续合并:
双亲表示法的并查集操作:
并查集的结构定义:
int UFSets[SIZE]; //集合元素数组(双亲指针数组)
Find操作(函数在并查集S中查询并返回包含元素x的树的根):
int Find(int S[],int x) {
while(S[x]>=0) //循环寻找x的根
x=S[x];
return x; //根的S[]小于0
}
Union操作(函数求两个不相交子集合的并集):
void Union(int S[],int Root1, int Root2) //Root1与Root2不同,表示子集合的名字
{
S[Root2]=Root1; //将根Root2连接到另一个根Root1下面
}
** 使用链式存储存储树**
不需要这么麻烦。
查找操作时,直接返回成员变量 root 结点。
合并操作时,直接将A树的root 结点作为 B树的根结点,B.root.father = A.root。
** 并查集的应用:图的最小生成树 -- 克鲁斯卡尔(Kruskal)算法。
【哈夫曼树】
树的带权路径长度(Weighted Path Length of Tree,简记为WPL)
结点的权:在一些应用中,赋予树中结点的一个有某种意义的实数。
结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积。
树的带权路径长度(Weighted Path Length of Tree):定义为树中所有叶结点的带权路径长度之和,通常记为:
WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),n个权值Wi(i=1,2,...n)构成一棵有n个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)
如下图的带权树:
计算如下:
哈夫曼树:
定义: 给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。
性质:
*初始的结点都会成为叶结点,双亲结点都是新生成的结点。
*权值越大的结点离根结点越近,反之,权值越小的结点离根结点越远。
*哈夫曼树中没有度为1的结点,要么是度为0的叶结点、要么是度为2的双亲结点。
*n个叶子结点的哈夫曼树的结点总度数为 2n - 1,度为2的结点为 n-1
哈夫曼树构造:
*哈夫曼树结点结构:
哈夫曼树及前缀码生成的 流程图:
*应用: 编码压缩,在数据传输中使用:
在处理字符串序列时,如果对每个字符串采用相同的二进制位来表示,则称这种编码方式为定长编码。若允许对不同的字符采用不等长的二进制位进行表示,那么这种方式称为可变长编码。可变长编码其特点是对使用频率高的字符采用短编码,而对使用频率低的字符则采用长编码的方式。这样我们就可以减少数据的存储空间,从而起到压缩数据的效果。而通过哈夫曼树形成的哈夫曼编码是一种的有效的数据压缩编码。
如果没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码。如0,101和100是前缀编码。由前缀码形成的序列可以被唯一的组成一个字符串序列。如00101100可以被唯一的分析为0,0,101和100。
示例:
我们对一个字符串进行统计发现a-f出现的频率分别为a:45,b:13,c:12,d:16,e:9,f:5,我们对该字符串进行采用哈夫曼编码进行存储。
WPL = 1x45+3x(13+12+16)+4x(5+9)=224
这样算下来使用224二进制位就可以将该字符串存储起来,因为哈夫曼码是前缀码,所以可以唯一的还原出原来的字符序列。如果我们每个字符使用3位进行存储(至少3位),那么需要300bit才能将该字符串存储下。
* 经过左结点的值为0,经过右结点的值为1, 结点结构体中追加 string m_strCode; 经过一个分支结点,在 m_strCode 后面追加 0\1 字符,也可以根据节点,逆序寻找父结点,根据 posflag 中的值,在 m_strCode 后面追加 0\1 字符。
【扩展问题】
相关扩展--- set 与 优先队列 priority_queue
按照分值从大到小输出
#include<iostream> #include<set> #include<string> using namespace std; struct Info { string name; double score; bool operator < (const Info &a) const // 重载“<”操作符,自定义排序规则 { //按score由大到小排序。如果要由小到大排序,使用“>”即可。 return a.score < score; } }; int main() { set<Info> s; Info info; //插入三个元素 info.name = "Jack"; info.score = 80; s.insert(info); info.name = "Tom"; info.score = 99; s.insert(info); info.name = "Steaven"; info.score = 60; s.insert(info); set<Info>::iterator it; for(it = s.begin(); it != s.end(); it++) cout << (*it).name << " : " << (*it).score << endl; return 0; } /* 运行结果: Tom : 99 Jack : 80 Steaven : 60 */
优先队列 priority_queue从小到大输出
#include <iostream> #include <queue> using namespace std; class T { public: int x, y, z; T(int a, int b, int c):x(a), y(b), z(c) { } }; bool operator < (const T &t1, const T &t2) { return t1.z < t2.z; // 按照z的顺序来决定t1和t2的顺序 } main() { priority_queue<T> q; q.push(T(4,4,3)); q.push(T(2,2,5)); q.push(T(1,5,4)); q.push(T(3,3,6)); while (!q.empty()) { T t = q.top(); q.pop(); cout << t.x << " " << t.y << " " << t.z << endl; } return 1; }
结束