You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
python/TangDou/AcWing/BeiBao/【总结】多叉树转二叉树.md

6.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

多叉树(森林)转二叉树

原文链接

一、思路

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

二、兄弟连

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

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

四、其他

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

#### 调整层次

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

五、森林的处理

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

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

### 六、实现

#### 连接兄弟&切断右儿子 介绍最好理解的方法 输入一对关系,如x,y(表示yx的儿子) 判断: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;
}