第五章:二叉树(下)
第五章:二叉树(下)
一、树与森林
1.树存储结构
(1)双亲表示法:
实现:定义结构数组存放树的结点,每个结点含两个域(找双亲容易、找孩子难)
- 数据域:存放结点本身信息
- 双亲域:指示本结点的双亲结点在数组中的位置。
1 |
|
(2)孩子链表表示法:
实现:将每个结点的孩子结点排列用单链表存储,而n个头指针又组成一个线性表用顺序表存储(找孩子容易、找双亲难)
1 | //孩子结点结构:child + next |
(3)孩子兄弟表示法:
二叉链表表示法/二叉树表示法/孩子兄弟表示法,
实现:用二叉链表表示数的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子结点和下一个兄弟结点。
1 | typedef struct CSNode { |
2.树与二叉树互转
将树转化为二叉树进行处理,利用二叉树的算法来实现对树的操作,由于树和二叉树都可以用二叉树链表作存储结构,则以二叉链表做媒介可以导出树与二叉树之间的对应关系。给定一棵树,可以找到唯一的二叉树与之对应
(1)树转换为二叉树:
- 理解:树转换为二叉树
二叉树转换步骤(兄弟相连留长子):
- 加线:在兄弟之间加一根连线
- 抹线:对每个结点除了其左孩子外,去除其与其他孩子之间的关系
- 旋转:以树的根结点为轴心,将整树顺时针旋转45度
(2)二叉树转换为树:
- 理解:二叉树转换为树
树转换步骤(左孩右右连双亲,去掉原来右孩线):
- 加线:若p为双亲结点的左孩子,则将p的右孩子、右孩子的右孩子…沿分支找到所有的右孩子与双亲连接
- 抹线:抹掉原二叉树中双亲与右孩子之间的连线
- 调整:将结点按层次排列,形成树结构
3.森林与二叉树互转
(1)森林转为二叉树:
- 理解:森林转为二叉树
森林转为二叉树(树变二叉根相连)
- 将各棵树分别转换为二叉树
- 将每棵树的根结点用线相连
- 以第一棵树根结点为二叉树的根,再以根结点为轴心顺时针旋转,构成二叉树形结构
(2)二叉树转为森林:
- 理解:二叉树转为森林
二叉树转为森林(去掉全部右孩线,孤立二叉再还原)。
- 抹线:将二叉树中根节点与其右孩子连线,及沿右分支搜索到的所有右孩子间连线全部抹掉(产生许多孤立的二叉树)
- 还原:将孤立的二叉树还原为树
4.树与森林的遍历
(1)树的遍历:
注:树的遍历没有中根遍历的情况,因为无法确定根的中序位置。
1 | void PreOrder(TreeNode *R) { |
1 | void PostOrder(TreeNode *R) { |
(2)森林的遍历:
二、哈夫曼树
1.哈夫曼树构造
在含有n个带权结点的二叉树中,其中带权路径长度WPL最小的二叉树称为哈夫曼树,也称为最优二叉树。
根据哈夫曼树中权越大的叶子离根结点越近的规律,利用贪心算法的思想:构造哈夫曼树时首先选择权值小的叶子结点。
==哈夫曼树性质==:
- 每个初始结点最终都会成为叶结点,且权值越小的结点到根节点的路径长度越大
- 包含n个叶子结点的哈夫曼树中共有2n-1个结点
- 哈夫曼树结点的度数为0或者2,没有度为1的结点
- 哈夫曼树的构造并不唯一,但是路径长度WPL必然相同且为最优
- 包含n棵树的森林要经过n-1次合并才能形成哈夫曼树,共产生n-1个新结点
2.哈夫曼树构造算法
采用顺序存储结构,一维数组实现:
1 | typedef struct { |
(1)数组初始化:
- 初始化
HT[1...2n-1]
:lch = rch = parent=0
- 输入初始化n个叶子结点:设置
HT[1...n]
的weight值
(2)结点的n-1次合并:
对结点进行n-1次合并,依次产生n-1个结点HT[i],i=n+1, n+2, …2n-1
在HT[1…i-1]中选取两个未被选过(parent==0)且weight最小的结点
HT[s1]
和HT[s2]
修改HT[s1]和HT[s2]的parent值:
HT[s1].parent=i;
、HT[s2].parent=i;
修改新产生的HT[i]:
1
2
3HT[i].weight = HT[s1].weight + HT[s2].weight;
HT[i].lch = s1;
HT[i].rch = s2;
1 | void CreatHuffmanTree(HuffmanTree HT, int n) { |
3.哈夫曼树应用哈夫曼编码
(1)问题引入:
==固定长度编码==:
==可变长度编码==:允许对不同字符用不等长的二进制位表示,
==哈夫曼编码==:字符集中的每个字符作为一个叶子结点,各个字符出现的频度作为结点的权值,来构造哈夫曼树:
- 统计字符集中每个字符在电文中出现的平均概论(概率越大、要求编码越短)
- 将每个字符的平均概论作为权值,利用哈夫曼树的特点(权值越大的叶子离根越近)构造哈夫曼树。(概率越大的结点路径越短)
- 在哈夫曼树的每个结点分支上标记0和1(左分支0,右分支1)
- 把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符编码
- 哈夫曼编码是前缀码(没有一片树叶是另一片树叶的祖先)
- 哈夫曼编码是最优前缀码(哈夫曼树的带权路径长度最短,字符编码总长度一定是最短的)
(2)哈夫曼编码:
1 | //从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中 |
三、并查集
1.并查集(双亲表示法)
使用双亲表示法,每个结点中保存指向双亲的指针,并和查的方式更加方便。(声明一个数组S即可表示集合的关系)
1 |
|
1 | //并查集初始化 |
1 | //并查集基本操作 |
2.并查集优化Union(小树并大树):
在并查集的Find操作中,若结点数量为n则Find最坏时间复杂度为O(n)
优化思路:在每次Union操作构建树的时候,尽可能让树不长太高(小树合并到大树)。
1 | //并查集基本操作 |
3.并查集进一步优化Find(路径压缩):
Find操作的优化:压缩路径,先找到根节点、再将查找路径上的所有结点都挂到根结点下面。
1 | //并查集基本操作 |
优化后的 并查集的Find、Union操作时间复杂度都很低。
四、二叉树经典问题
1.数据压缩
2.表达式求值
- Cpp:https://www.pudn.com/news/62ac953fca7ee606dccd7b69.html
- Java:https://blog.csdn.net/qq_44028290/article/details/106376961