1.哈夫曼树和哈夫曼编码
1.1.哈夫曼树的定义
权值:树的结点带有的某种意义的数值
带权路径长度:从树的跟该结点的路径长度(经过的边数)与该点的权值的乘积
树的带权路径长度(WPL):所有叶结点的带权路径长度之和(算法题考过)
哈夫曼树:n个结点形成的所有二叉树中,wpl值最低的树(也称为最优二叉树)
1.2.哈夫曼树的构造
- 找到当前权值最低的两个结点,形成一个新的树,其根节点权值为两点之和
- 在森林中将两个结点删除,并将该树加入。
- 循环进行1、2,直到森林中仅有一棵树为止
- cd结点权值最低,形成新的结点
- b(cd)结点权值最低,形成新的结点
- a(b(cd))权值最低,形成新的结点
1.3.哈夫曼树的性质
- 哈夫曼树不唯一
- 权值越小的结点路径长度越大
- 哈夫曼树的结点总数为2n - 1
- 哈夫曼树构造前,结点总数为n
- 构造哈夫曼树需要执行n - 1次合并,每次合并新增一个结点,即新增n - 1个结点
- n + n - 1 = 2n - 1
- 哈夫曼树不存在度为1的点(要么为0(叶子结点),要么为2(分支节点))
1.4.哈夫曼编码
前缀编码:没有一个编码是另一个编码的前缀
由哈夫曼树构造哈夫曼编码,0向左子树,1向右子树(01指向左还是右没有明确规定)
哈夫曼编码的作用是可以不采用固定长度编码方式,从而压缩数据
2.并查集
2.1.并查集的基本操作
并查集:将元素划分为若干个互不相干的子集
并:将两个集合归并为一个集合(让一个树成为另一个树的子树)
查:确定一个指定元素的集合(查看其根节点是哪一个)
采用双亲表示法:并和查仅需更改或查看指向其双亲结点的伪指针
1.初始化:将每个结点的值设置为-1,表示每个结点都是一颗单独的树,即n个子集
#define MAXSIZE 100 int UFsets[MAXSIZE]; //集合数组 //初始化并查集 void Initial(int S[]){ //-1表示集合中(森林)每个元素都是独立的个体(树) for (int i = 0; i < MAXSIZE; i++) S[i] = -1; }
2.查(最坏O(n)):找该结点的根节点
并(O(1)):①两个结点是根节点:直接修改root2的值为root1的下标
②两个结点是非根节点:通过查操作,分别找到root1和root2的根节点,然后将root2的根节点的值为root1的根节点的下标
//查找,传入数组和数组下标 int Find(int S[], int x){ //循环遍历查找其根节点,根节点的值为-1 while(S[x] >= 0) x = S[x]; return x; } //并,两个集合合并为一个 void Union(int S[], int root1, int root2){ //两个元素是同一个集合 if (root1 == root2) return; //将root2的根节点改为root1 S[root2] = root1; }
2.2.并查集的Union操作的优化(小树合并到大树)
1.如果每次都是大树合并到小树,则树的高度每次都会 + 1,导致并查集的使用效率降低→find最坏时间复杂度O(n)
2.每次都让让小树合并到大树,可以延缓树的增高→find最坏时间复杂度O(log n)
优化:根节点的数据的绝对值等于其结点总数,结点总数更大的树为大树,将更小的树并入大树
//并优化 void Union(int S[], int root1, int root2){ //root1的树结点更多,相较下root2为大树 if (root1 < root2){ S[root1] += S[root2]; //root1的结点数更新 S[root2] = root1; //root2并入root1 } else{ //root2为大树 S[root2] += S[root1]; //root2的结点数更新 S[root1] = root2; //root1并入root2 } }
根节点代表的是该树的总结点个数,因此合并时,可以通过两个树根节点的值相加的方式得到合并后的树的总结点个数
2.3.并查集的Find操作的优化(压缩路径)
第一轮循环:和之前find操作一样,找到该结点的根节点
第二轮循环:将该结点find操作经历的每个结点挂到根节点上(修改路径上每个结点的值为根节点的下标)
find操作可以进一步优化为O(α(n)) << O(log n)α(n)通常为常数级
int Find(int S[], int x){ int root = x; //向上循环遍历树,找到其根节点 while(S[root] >= 0) root = S[root]; while (x != root){ int temp = S[x]; S[x] = root; //将x的双亲结点改为root x = temp; } return root; }
3.王道课后题
1.(AB)CDEF→((AB)C)EF→((AB)C)(DE)F→(((AB)C)(DE))F→((((AB)C)(DE))F)
- 最坏情况下,两个表的元素是交错的,即m1<n1<m2<n2....<m<n,除了最后一个元素不用比较以外,所有元素都要比较一次
- 第一次合并:10 + 35 -1 = 44
- 第二次合并:45 + 40 - 1 = 84
- 第三次合并:50 + 60 - 1 = 109
- 第四次合并:110 + 85 - 1 = 194
- 第五次合并:195 + 200 - 1 = 394
2..每次找当前最少的两个表合并成为一个新表
- 二叉树
- a.遍历当前字符集的每个字符
b.如果当前扫描到的字符为0,则进入其左子树;如果当前扫描到的字符为1,则进入其右子树,直到碰到叶子结点,取出叶子结点所存储的数据
- 扫描字符集
1.如果在还没有遍历完当前字符集时就到了叶子结点,则不具有前缀特性
2.如果在遍历完当前字符集,并没有创建新的结点,则不具有前缀特性
3.1和2都没有发生,则具有前缀特性