|
|
|
|
## 多叉树(森林)转二叉树
|
|
|
|
|
|
|
|
|
|
[原文链接](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;
|
|
|
|
|
}
|
|
|
|
|
```
|