数据结构 -- 树的应用(哈夫曼树和并查集)

发布于:2025-03-31 ⋅ 阅读:(20) ⋅ 点赞:(0)

树的应用

哈夫曼树

带权路径长度

结点的权:有某种现实含义的数值(如:表示结点的重要性等)

结点的带权路径长度:从树的根到该结点的路径长度(经过的边数)与该结点上权值的乘积

树的带权路径长度:所有的叶子结点带权路径长度之和

哈夫曼树的定义

在含有n个带权叶节点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树

哈夫曼树的构造

在给定n个权值分别为w1,w2……wn的结点,构造哈夫曼树的算法描述如下:

① 将这n个结点分别作为n棵仅含有一个结点的二叉树,构成森林F。

② 构造一个新结点,从F中选取两颗根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。

③ 从F中删除刚才选出的两棵子树,并将新结点的权值置为左、右子树上根结点的权值之和。

④ 从F中删除刚才选出的两棵树,同时将新得到的树加入F中。

④ 重复步骤②和③,直至F中只剩下一棵树为止。

【特点】

① 每个初始节点最终都成为叶节点,且权值越小的结点到根结点的路径长度越大

② 哈夫曼树的结点总数为2n - 1

③ 哈夫曼树中不存在度为1的结点

④ 哈夫曼树并不唯一,但WPL必然相同且为最优

哈夫曼编码

固定长度编码 – 每个字符用相等长度的二进制位表示

可变长度编码 – 允许对不同字符用不等长的二进制位表示

若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码

前缀编码解码无歧义

非前缀编码解码有歧义

由哈夫曼树得到哈夫曼编码 – 字符集中的每个字符作为一个叶子节点,各个字符出现的频度作为结点的权值,根据之前介绍的方法构造哈夫曼树

使用哈夫曼编码可以进行数据压缩

并查集

逻辑结构 – 集合

将各个元素划分为若干个互不相交的子集

用互不相交的树,表示多个“集合”

如何“查”到一个元素到底属于哪个集合?

– 从指定元素出发,一路向上,找到根结点

如何判断两个元素是否属于同一个集合?

– 分别查到两个元素的根,判断根结点是否相同即可

如何把两个集合“并”为一个集合?

– 让一棵树称为另一棵树的子树即可

并查集的基本操作

使用 双亲表示法、孩子表示法、孩子兄弟表示法 中的哪种方法?

– 双亲表示法

【原因】

双亲表示法:每个结点保存指向双亲的“指针”

优点:找根节点比较方便

集合的两个基本操作 – “并”和“查”

Find – ”查“操作:确定一个指定元素所属集合

Union – ”并“操作:将两个不相交的集合合并为一个

注:并查集(Disjoint Set)是逻辑结构 – 集合的一种具体实现,只进行”并“和”查“两种基本操作

#define SIZE 13
int UFSet[SIZE];	//集合元素数组

//初始化并查集
void Initial(int S[]){
    for(int i = 0;i<SIZE;i++)
        S[i] = -1;
}
//Find "查"操作,找x所属集合(返回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是不同的集合
    if(Root1 == Root2)	return ;
    //将根Root2连接到另一个根Root1下面
    S[Root2] = Root1;
}

”查“操作时间复杂度(最坏情况下):O(n)

”并“操作时间复杂度:O(1)

Union操作的优化

优化思路:在每次Union操作构建树的时候,尽可能让树不长高

① 用根节点的绝对值,表示树的结点总数

② Union操作,让小树合并到大树

//Union "并"操作,小树合并到大树
void Union(int S[],int Root1,int Root2){
    if(Root1 == Root2)	return;
    if(s[Root2]>S[Root1]){		//Root2结点数更少
        S[Root1] += S[Root2];		//累加结点总数
        S[Root2] =Root1;			//小树合并到大树        
    }else{
        S[Root2] += S[Root1];		//累加结点总数
        S[Root1] = Root2;			//小树合并到大树
    }
}

该方法构造的树高不超过[log2n]+1

Find操作最坏的时间复杂度:O(log2n)

并查集的进一步优化

Find操作的优化(压缩路径)

压缩路径 – Find操作,先找到根结点,再将查找路径上所有的结点都挂到根结点下

(在下一次查找时,查找路径就被压缩了)

//Find "查"操作优化,先找到根结点,再进行"压缩路径"
int Find(int S[],int x){
    int root = x;
    while(S[root]>=0)	root = S[root];		//循环找到根
    while(x!=root){		//压缩路径
        int t = S[x];		//t指向x的父节点
        S[x] = root;		//x直接挂到根结点下
        x = t;
    }
    return root;			//返回根结点编号
}

并查集优化的核心思想:尽量让树变矮

在这里插入图片描述

点开链接玩一玩:https://www.cs.usfca.edu/~galles/visualization/DisjointSets.html

更多算法:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html