仲舟の原创算法——树与二叉树的相互转换2.0

本文详细介绍了如何将树转换为二叉树以及将二叉树转换回树的过程,通过循环和队列解决二叉树转森林的问题。作者提供了C语言代码实现,并展示了括号表示法的输出,便于理解。文章还提到了转换过程中可能遇到的度数超过3的树的处理方式。
摘要由CSDN通过智能技术生成

在前一篇《仲舟の原创算法——树与二叉树的相互转换》中,出现了一个非常离谱的问题——我们知道,树转二叉树,只要是树的孩子就是二叉树的左孩子,只要是树的兄弟就是二叉树的右孩子,这样的话,你每次输入一个树,他总能转成二叉树,但是,细心的你总会发现,你每次转换出来的二叉树,头节点总是没有右孩子,这是为什么呢?
原来,如果头节点存在右孩子,又因为右孩子是父亲的兄弟,那么头节点的兄弟,岂不是头节点?就会出现多个头节点变成森林的情况,如下图所示:
原代码出问题原因
然而以前的程序只是从左孩、右孩、右孩处理,管不到其他的树,那么我们就可以用一个不存在的结点“@”连接这几个树,在返回一个“@”的指针,在输出的时候不输出@就行,如图:
用不存在结点进行管理
这样就解决了二叉树转森林的问题。同时,由于我们学过了括号表示法,我将树的显示由前序输出升级成了括号表示输出,所有代码如下:

///*题目:树与二叉树的相互转换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以上的树,需要另开一个结构体来存储,有兴趣的同学可以自行完成。
仲舟原创,未经允许禁止转载!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值