在前一篇《仲舟の原创算法——树与二叉树的相互转换》中,出现了一个非常离谱的问题——我们知道,树转二叉树,只要是树的孩子就是二叉树的左孩子,只要是树的兄弟就是二叉树的右孩子,这样的话,你每次输入一个树,他总能转成二叉树,但是,细心的你总会发现,你每次转换出来的二叉树,头节点总是没有右孩子,这是为什么呢?
原来,如果头节点存在右孩子,又因为右孩子是父亲的兄弟,那么头节点的兄弟,岂不是头节点?就会出现多个头节点变成森林的情况,如下图所示:
然而以前的程序只是从左孩、右孩、右孩处理,管不到其他的树,那么我们就可以用一个不存在的结点“@”连接这几个树,在返回一个“@”的指针,在输出的时候不输出@就行,如图:
这样就解决了二叉树转森林的问题。同时,由于我们学过了括号表示法,我将树的显示由前序输出升级成了括号表示输出,所有代码如下:
///*题目:树与二叉树的相互转换2.0
///*作者:仲舟
///*难度:★★★★★★★
///*1.0完成时间:2021.05.10
///*2.0完成时间:2021.05.25
#include<stdio.h>
#define M 3//树的度数
#define MAXSIZE 100//最大结点数
typedef struct bnode { /*二叉树结构定义*/
char data;
struct bnode *lchild,*rchild;
} binnode;
typedef binnode *bintree;
typedef struct tnode {/*树的结构定义*/
char data;
struct tnode *child[M];
} node;
typedef node *tree;
tree CreateTree();/*按前序遍历顺序建立一棵树,返回树根地址*/
bintree CreateBinTree();/*按前序遍历顺序建立一棵二叉树,返回树根地址*/
bintree FromTreeToBintree(tree t);/*将树转化为二叉树*/
tree FromBintreeToTree(bintree bt);/*将二叉树转化为树*/
void DispTree(tree t);/*括号表示树*/
void DispBinTree(bintree t);/*括号表示二叉树*/
int main() {
printf("这里是仲舟的树与二叉树转化程序\n");
tree a;//a是普通树
bintree b;//b是二叉树
/*1.树转二叉树*/
printf("请用前序遍历的方法输入度数为%d的树:",M);
a=CreateTree();//创建a树
getchar();//读取回车
printf("此树括号表示法为:");
DispTree(a);//输出a前序遍历
printf("\n");
b=FromTreeToBintree(a);//将a树转化为二叉树b
printf("转换成二叉树后为:");
DispBinTree(b);//输出b树
printf("\n");
/*2.二叉树转树*/
printf("请用前序遍历的方法输入二叉树树:");
b=CreateBinTree();
getchar();//读取回车
printf("此树括号表示法为:");
DispBinTree(b);
printf("\n");
a=FromBintreeToTree(b);
printf("转换成树后为:");
for(int i=0; i<M; i++)
if(a->child[i]) {
printf("第%d颗树是:",i+1);
DispTree(a->child[i]);
printf(" ");
}
printf("\n");
return 0;
}
tree CreateTree() {
int i;
char ch;
tree t;
if ((ch=getchar())=='#') t=NULL;
else {
t=(tree) malloc (sizeof(node));
t->data=ch;
for (i=0; i<M; ++i)
t->child[i]= CreateTree();
}
return t;
}
bintree CreateBinTree() {
char ch;
bintree t;
if ((ch=getchar())=='#') t=NULL;
else {
t=(bintree)malloc(sizeof(binnode));
t->data=ch;
t->lchild=CreateBinTree();
t->rchild=CreateBinTree();
}
return t;
}
bintree FromTreeToBintree(tree t) {
/*仲舟提示:在树转二叉树的时候,由于二叉树左边孩子,右边兄弟,如果要访问兄弟,就不能用递归,
和中序遍历一样使用循环+队列的形式,因为访问兄弟需要把父亲先存起来*/
/*1.声明变量*/
tree tqueue[MAXSIZE],tnow;//队列,正在处理的结点
bintree bqueue[MAXSIZE],bt,bnow,blast;//同步队列(和前面的队列同步,建立这个是后续需要引用此类型的指针,存储内容和前队列一样),新生二叉树的根,正在处理的结点,上一个处理的结点(类似链表,要连接就要两个结点,所以要保存前后数据)
int l=0,r=0;//队列最左下标,队列最右下标
/*2.队列初始化*/
bt=(bintree)malloc(sizeof(binnode));//开辟空间
bt->data=t->data;//新建树的根
bt->lchild=NULL;//左孩子初始化
bt->rchild=NULL;//右孩子初始化
tqueue[r]=t;//树根入队
bqueue[r]=bt;//树根入队
r++;
/*3.开始转化*/
while(l<r) { //队列还有结点
tnow=tqueue[l];//开始处理最左边的结点
blast=bqueue[l];//上一个处理的结点
l++;//左下标右移
int flag=0;//第一个孩子标记
for(int i=0; i<M; i++) //开始连接孩子
if(tnow->child[i]!=NULL) {//如果有孩子
/*1.开辟空间*/
bnow=(bintree)malloc(sizeof(binnode));//【新建】开辟空间
bnow->data=tnow->child[i]->data;//【赋值】拷贝
bnow->lchild=NULL;//左孩子初始化
bnow->rchild=NULL;//右孩子初始化
/*2.分类讨论*/
if(flag==0) { //对二叉来说,如果他还没有孩子的时候,这个孩子就是now的左孩子
blast->lchild=bnow;//【连接1】与上一个结点相连
flag=1;//对树来说,第一个孩子已出现
} else { //对二叉来说,如果他有孩子了,这个孩子就是那个孩子的右孩子
blast->rchild=bnow;//【连接2】与上一个结点相连
}
/*3.记忆结点*/
blast=bnow;//处理完了,这个就是上一个结点
tqueue[r]=tnow->child[i];//它的孩子入队
bqueue[r]=bnow;//同步入队
r++;//右下标右移
}
}
return bt;
}
tree FromBintreeToTree(bintree bt) {
/*仲舟提示:根据上述函数逆向思维,一个二叉树结点的左、右、右、右……孩子就是树的所有孩子*/
///注意:二叉树的头结点如果有右孩子,那孩子就是头节点的兄弟,所以该二叉树会转出森林,故我们先用一个父结点将所有头节点连接起来,返回一个父节点将所有结点带出,然后再去掉父节点输出所有树///
bintree bqueue[MAXSIZE],bnow;//队列,正在处理的结点
tree tqueue[MAXSIZE],t,tnow,tlast;//同步队列(和前面的队列同步,建立这个是后续需要引用此类型的指针,存储内容和前队列一样),新生树的根,正在处理的结点,上一个处理的结点(类似链表,要连接就要两个结点,所以要保存前后数据)
int l=0,r=0;//队列最左下标,队列最右下标
/*2.新建不存在头节点*/
tnow=(tree)malloc(sizeof(node));//新建不存在结点“@”,管理所有头节点,最后return带出来
tnow->data="@";
bnow=(bintree)malloc(sizeof(binnode));//在原二叉树新建一个“@”,将原二叉树连到“@”的左孩子上,再用原来的方法就可以新建一个二叉树
bnow->data="@";
bnow->rchild=NULL;//这个树不许有右孩子,也就是兄弟,因为他的作用就是管理头节点
bnow->lchild=bt;//连接头节点
for(int i=0; i<M; i++)//孩子初始化
tnow->child[i]=NULL;
tqueue[r]=tnow;//入队
bqueue[r]=bnow;//同步入队
r++;
t=tnow;
/*3.开始转化*/
while(l<r) { //队列还有结点
bnow=bqueue[l];//开始处理最左边的结点
tlast=tqueue[l];//上一个处理的结点(父结点)
l++;//左下标右移
for(int i=0; i<M; i++)//父节点最多M个孩子
if(i==0&&bnow->lchild||i!=0&&bnow->rchild) {//如果树有孩子的条件(即二叉树有左、右、右……孩子)
/*1.开辟空间*/
tnow=(tree)malloc(sizeof(node));//【新建】开辟空间
for(int j=0; j<M; j++)//孩子初始化
tnow->child[j]=NULL;
/*2.分类讨论*/
if(i==0) {//如果是树的第一个孩子,那么是二叉树的左孩子
tnow->data=bnow->lchild->data;//【赋值1】拷贝
bnow=bnow->lchild;//处理完了,这个就是上一个结点
} else {//如果是树的第二、三个孩子,那么是二叉树左孩子的右孩子的右孩子……
tnow->data=bnow->rchild->data;//【赋值2】拷贝
bnow=bnow->rchild;//处理完了,这个就是上一个结点
}
tlast->child[i]=tnow;//【连接】与父结点连接
/*3.记忆结点*/
tqueue[r]=tnow;//它的孩子入队
bqueue[r]=bnow;//同步入队
r++;//右下标右移
}
}
return t;
}
void DispTree(tree t) {
if (t!=NULL) {
printf("%c",t->data);//先输出
if (t->child[0] || t->child[1]|| t->child[2]) {//如果有孩子
printf("(");//子孩子开始
DispTree(t->child[0]);//孩子1树输出
if (t->child[1])printf(",");//如果有孩子就要分隔
DispTree(t->child[1]);//孩子2树输出
if (t->child[2])printf(",");//分隔
DispTree(t->child[2]);//孩子3树输出
printf(")");//子孩子结束
}
}
}
void DispBinTree(bintree t) {
if (t!=NULL) {
printf("%c",t->data);//先输出
if (t->lchild || t->rchild) {//如果有孩子
printf("(");//子孩子开始
DispBinTree(t->lchild);//左孩子树输出
if (t->rchild) printf(",");//分隔
DispBinTree(t->rchild);//右孩子树输出
printf(")");//子孩子结束
}
}
}
//附:常规用例
//树:AB###CE###F###G###D###
//二叉:ABD##EF##G##C##
//问题二叉:AB##CD##EF##G##
细心的同学能发现,如果二叉树转化成的树度数为三以上,三以上的树会消失,这是因为代码设置的树的度数为3,如果需要转化成度数为3以上的树,需要另开一个结构体来存储,有兴趣的同学可以自行完成。
仲舟原创,未经允许禁止转载!