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

6.0 KiB
Raw Blame History

多叉树(森林)转二叉树

原文链接

一、思路

大体就两步,很好理解,如图是用来举例的多叉树:

二、兄弟连

将所有的兄弟间连一条线,如图:

### 三、右子断 将所有右儿子与父亲的边删掉,如图:

四、其他

事实上这已经是一棵二叉树了,还有一点小细节。

#### 调整层次

很容易发现,兄弟(即黄色线相连的结点) 都在它们的大哥右子树中,且 根结点一定没有右儿子(因为根结点没有兄弟)。

五、森林的处理

有的坑爹题目,转为二叉树后有多个结点的父亲都为$0$(即为根结点),如图:

(原谅我直接$copy$的第一棵树,不过没影响)解决这个很容易,从右到左依次将根结点接到它前一个根结点的右儿子上即可:

### 六、实现

#### 连接兄弟&切断右儿子 介绍最好理解的方法 输入一对关系,如$x,y$(表示$y$为$x$的儿子) 判断:$x$有没有左儿子

  • 没有:$y$直接成为$x$的左儿子;
  • 有:设$x$的左儿子为$x.l$,就找到$x.l$的右儿子的右儿子的右儿子...,然后接上去

注: 为什么不用切断右儿子?因为根本就还没有连接右儿子!

就这样。(这么简单?)

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
}  

森林

有森林怎么?按照刚刚说的硬行实现即可:

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];//处理根结点
}

七、练习题

恶心的是这道题兄弟之间要求顺序(编号小的在左边),所以不能边输入边处理,需要全部输入后再进行处理。

实现代码

//输入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;
}