Files
python/TangDou/AcWing/BeiBao/【总结】多叉树转二叉树.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

150 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 多叉树(森林)转二叉树
[原文链接](https://blog.csdn.net/c20190102/article/details/69946551)
### 一、思路
大体就两步,很好理解,如图是用来举例的多叉树:
<center><img src='https://img-blog.csdn.net/20170410133259616?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQzIwMTkwMTAy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast'></center>
### 二、兄弟连
将所有的兄弟间连一条线,如图:
<center><img src='https://img-blog.csdn.net/20170410133447858?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQzIwMTkwMTAy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast'></center>
### 三、右子断
将所有右儿子与父亲的边删掉,如图:
<center><img src='https://img-blog.csdn.net/20170410133913920?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQzIwMTkwMTAy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast'></center>
### 四、其他
事实上这已经是一棵二叉树了,还有一点小细节。
#### 调整层次
<center><img src='https://img-blog.csdn.net/20170410134402209?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQzIwMTkwMTAy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast'></center>
很容易发现,**兄弟**(即黄色线相连的结点) <font color='blue' size=4><b>都在它们的大哥右子树中</b></font>,且 <font color='red' size=4><b>根结点一定没有右儿子(因为根结点没有兄弟)</b></font>。
### 五、森林的处理
有的坑爹题目,转为二叉树后有多个结点的父亲都为$0$(即为根结点),如图:
<center><img src='https://img-blog.csdn.net/20170410135613646?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQzIwMTkwMTAy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast'></center>
(原谅我直接$copy$的第一棵树,不过没影响)解决这个很容易,从右到左依次将根结点接到它前一个根结点的右儿子上即可:
<center><img src='https://img-blog.csdn.net/20170410140112700?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQzIwMTkwMTAy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast'></center>
### 六、实现
#### 连接兄弟&切断右儿子
<font color='blue' size=4><b>介绍最好理解的方法</b></font>
输入一对关系,如$x,y$(表示$y$为$x$的儿子)
判断:$x$有没有左儿子
* 没有:$y$直接成为$x$的左儿子;
* 有:设$x$的左儿子为$x.l$,就找到$x.l$的右儿子的右儿子的右儿子...,然后接上去
<center><img src='https://img-blog.csdn.net/20170407140220788?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQzIwMTkwMTAy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center'></center>
<font color='red' size=4><b>
注:
为什么不用切断右儿子?因为根本就还没有连接右儿子!
</b></font>
就这样。(这么简单?)
```c++
if(tr[i].l == 0){ //如果i没有左儿子,那么j直接成为i的左儿子
tr[i].l = j; //i的左儿子是j
tr[j].f = i; //j的父亲是i
} else {
int t = tr[i].l; //如果i有左儿子,那么找到左儿子t
while(tr[t].r) t = tr[t].r;//找到x左子树中最右边的结点
tr[t].r = j; //t的右儿子是j
tr[j].f = t; //j的父亲是t
}
```
#### 森林
有森林怎么?按照刚刚说的硬行实现即可:
```c++
for(int i=1;i<=N;i++)
//如果i结点没有配置父亲,也就是森林的某个树根
if(!tr[i].f) root[++rs]=i;//找到所有根结点并保存
//从右到左依次将根结点接到它前一个根结点的右儿子上,只保留第一个树根节点
for(int i = rs;i > 1;i--){
tr[root[i-1]].r = root[i];
tr[root[i]].f = root[i-1];//处理根结点
}
```
### 七、练习题
<center><img src='https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/images/2022-07-27_105656.png'></center>
<font color='red' size=4><b>恶心</b></font>的是这道题兄弟之间要求顺序(编号小的在左边),所以不能边输入边处理,需要全部输入后再进行处理。
#### 实现代码
```c++
//输入N,M表示节点数和边数,再输入M组边关系,从1到N输出每个结点的父亲,根节点父亲为0
#include <bits/stdc++.h>
using namespace std;
int n, m;
/*
测试用例:
4 3
1 2
1 3
1 4
样例输出:
0
1
2
3
*/
const int N = 110;
struct Node {
int l, r, f;
} tr[N];
int root[N], rs;
bool a[N][N];
int main() {
//文件输入
freopen("DuoChaShuToErChaShu.in", "r", stdin);
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) { // m条边
int x, y;
scanf("%d %d", &x, &y);
a[x][y] = true; // x->y有一条边,先保存起来
}
for (int i = 1; i <= n; i++) //双层循环,枚举每个点到点的关系
for (int j = 1; j <= n; j++)
if (a[i][j]) { //如果i->j有一条边
if (tr[i].l == 0) { // i没有左儿子
tr[i].l = j; // j就是i的左儿子
tr[j].f = i; // i是j的父亲
} else {
int t = tr[i].l; //找到i的左儿子
while (tr[t].r) t = tr[t].r; //一路向下找i的右儿子,右儿子,右儿子...
tr[t].r = j; // j做为最终的右儿子
tr[j].f = t; // j的父亲是最终的右儿子
}
}
//处理森林
for (int i = 1; i <= n; i++)
if (!tr[i].f) root[++rs] = i; //将每个子树的根加入到root数组
for (int i = rs; i > 1; i--) { //保留一个根,其它的根合并到前一个根中,从右向左当做右儿子加入
tr[root[i - 1]].r = root[i];
tr[root[i]].f = root[i - 1];
}
//输出每个点的父节点号
for (int i = 1; i <= n; i++) printf("%d\n", tr[i].f);
return 0;
}
```