本来不怎么想写这个,但发现网上的都是“残疾”博客,讲得不是很详细,所以我还是要写一下。
多叉转二叉有“左儿子右兄弟”的说法,然而对于什么都不知道的小白,这句话没有任何用……
思路
大体就两步,很好理解,如图是用来举栗的多叉树:
兄弟连
将所有的兄弟间连一条线,如图:
右子断
将所有右儿子与父亲的边删掉,如图:
其他
事实上这已经是一棵二叉树了,还有一点小细节。
调整层次
很容易发现,“兄弟”(即黄色线相连的结点)都在它们的大哥右子树中,且根结点一定没有右儿子(因为根结点没有兄弟)。
森林的处理
有的坑爹题目,转为二叉树后有多个结点的父亲都为0(即为根结点),如图:
(原谅我直接copy的第一棵树,不过没影响)解决这个很容易,从右到左依次将根结点接到它前一个根结点的右儿子上即可:
实现
连接兄弟&切断右儿子
这里介绍最好理解的方法:
输入一对关系,如x,y(表示y为x的儿子)
判断:x有没有左儿子
没有:y直接成为x的左儿子;
有:设x的左儿子为x.l,右儿子为x.r,就找到x.l的右儿子的右儿子的右儿子……
为什么不用切断右儿子?因为根本就还没有连接右儿子!
如图:
就这样。(这么简单?!)
代码:
if(tree[i].l==0)
{
tree[i].l=j;
tree[j].f=i;//改变父子关系
}
else
{
int t=tree[i].l;
while(tree[t].r)
t=tree[t].r;//找到x左子树中最右边的结点
tree[t].r=j;
tree[j].f=t;
}
森林
按照刚刚说的硬行实现即可:
for(int i=1;i<=N;i++)
if(!tree[i].f)
root[++rs]=i;//找到所有根结点并保存
for(int i=rs;i>1;i--)
{
tree[root[i-1]].r=root[i];
tree[root[i]].f=root[i-1];//处理根结点
}
完整代码
恶心的是这道题兄弟之间要要求顺序(编号小的在左边),所以不能边输入边处理
//输入N,M表示节点数和边数,再输入M组边关系,从1到N输出每个结点的父亲,根节点父亲为0
#include<cstdio>
#include<algorithm>
using namespace std;
void read(int &x)
{
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
#define MAXN 100
struct node
{
int l,r,f;
}tree[MAXN+5];
int N,M;
int root[MAXN+5],rs;
bool d[MAXN+5][MAXN+5];
void rtb()//处理森林
{
for(int i=1;i<=N;i++)
if(!tree[i].f)
root[++rs]=i;
for(int i=rs;i>1;i--)
{
tree[root[i-1]].r=root[i];
tree[root[i]].f=root[i-1];
}
}
int main()
{
read(N),read(M);
for(int i=1;i<=M;i++)
{
int x,y;
read(x),read(y);
d[x][y]=1;//先保存起来
}
for(int i=1;i<=N;i++)//再处理
for(int j=1;j<=N;j++)
if(d[i][j])
{
if(tree[i].l==0)
{
tree[i].l=j;
tree[j].f=i;
}
else
{
int t=tree[i].l;
while(tree[t].r) t=tree[t].r;
tree[t].r=j;
tree[j].f=t;
}
}
rtb();
for(int i=1;i<=N;i++)
{
if(i<N) printf("%d\n",tree[i].f);
else printf("%d",tree[i].f);
}
}