[郝斌] 数据结构C语言—树

概念

专业定义

树(Tree)是n(n≥0)个节点的有限集,分为空树和非空树,非空树T: 有且仅有一个被称为根的节点;除根节点以外的其他节点可分为m(m>0)个互不相交的有限集,其中每一个集合本身又是一棵树,而且被称为根的子树。

通俗定义:

  1. 树是由节点和边组成;

  2. 每个节点只有一个父节点但可以有多个子节点;

  3. 有一个节点例外,该节点没有父节点,此节点称为根节点。

专业术语:

节点 父节点 子节点 子孙 堂兄弟 叶子(没有子节点的节点) 非终端节点(非叶子节点)

节点的度(子节点的个数) 树的度(各节点度的最大值)

树的深度(从根节点到最底层节点的层数被称为深度)

树分类:

一般树:任意一个节点的子节点的个数都不受限制

二叉树:任意一个节点的子节点个数最多两个,且子节点的位置不可更改

二叉树分类:

  1. 一般二叉树

  2. 满二叉树(在不增加树的层次的前提下无法再多添加一个节点的二叉树就是满二叉树)

  3. 完全二叉树(如果只是删除了满二叉树最底层最右边的连续若干个节点,这样形成的二叉树就是完全二叉树)

森林:n个互不相交的树的集合

树的存储:

  1. 二叉树的存储

    连续存储[完全二叉树]:

    优点——方便查找某个节点的父节点和子节点,也方便查找是否有子节点;

    缺点——耗用内存过大

  2. 链式存储

  3. 一般树的存储

    • 双亲表示法(存数值和父节点的编号,求父节点方便)

    • 孩子表示法(存数值和所有孩子节点的指针域,求子节点方便)

    • 双亲孩子表示法(前两种的综合,求父节点和子节点都很方便)

    • 二叉树表示法(把一个普通树转化二叉树来存储)

      设法保证任意一个节点的:左指针域指向它的第一孩子,右指针域指向它的兄弟节点。 只有满足此条件,就可以把一个普通树转化成二叉树。一个普通树转化成的二叉树一定没有右子树。

  4. 森林的存储

  • 森林中独立的普通树当作其他树的兄弟节点,森林转化成的二叉树就存在右子树,森林会先转化成二叉树然后进行存储

  1. 二叉树的操作

    • 遍历(每个子树的遍历都要遵循按序遍历)

      先序遍历:先访问根节点,再先序访问左子树,再先序访问右子树(先访问根节点

      中序遍历:中序遍历左子树,再访问根节点,再中序遍历右子树(中间访问根节点

      后序遍历:后序遍历左子树,中序遍历右子树,再访问根节点(最后访问根节点

    • 已知两种遍历序列求原始二叉树

      通过先序遍历和中序遍历 或者 通过中序遍历和后序遍历都可以还原出原始的二叉树。

      但是通过先序和后序无法还原出原始的二叉树。

      因为还原二叉树的首要步骤在于确定根节点,先序遍历和后序遍历作为先访问根节点和后访问根节点的方式,最先出现在先序中或者最后出现在后序中的节点都可以认为是根节点,然后中序遍历的序列中就可以找到根节点的位置,然后根节点左边为左子树,右边为右子树,递归的思想进一步在先序或者后序中寻找子树的根节点。因此先序和后序只需其中之一,而中序必不可少。

  2. 应用

    • 树是数据库中数组组织的一种重要形式

    • 操作系统子父进程的关系本身就是一棵树

    • 面向对象语言中类的继承关系就是一棵树

    • 赫夫曼树

  3. 链式二叉树程序

     

    #include  <stdio.h>
    #include  <malloc.h>
    #include  <stdbool.h>
    #include<stdlib.h>
    ​
    typedef struct treeNode
    {
        char data;
        struct treeNode *pLeftTreeNode; //左孩子节点
        struct treeNode *pRightTreeNode;//右孩子节点
    }TREENODE, *PTREENODE;
    ​
    PTREENODE createTreeQuite(void);
    //void createTreeDynamic(PTREENODE ptreeNew);
    bool firstShowTree(PTREENODE ptreenode);//先序遍历
    bool inShowTree(PTREENODE ptreenode);   //中序遍历
    bool lastShowTree(PTREENODE ptreenode);//后序遍历
    ​
    int main()
    {
        PTREENODE ptree= NULL;
        ptree = createTreeQuite();
    ​
        //createTreeDynamic(ptree);
    ​
        printf("先序遍历的结果:\n");
        firstShowTree(ptree);
        printf("\n");
    ​
        printf("中序遍历的结果:\n");
        inShowTree(ptree);
        printf("\n");
    ​
        printf("后序遍历的结果:\n");
        lastShowTree(ptree);
        return 0;
    }
    ​
    PTREENODE createTreeQuite(void)
    {
        PTREENODE ptreeA = (PTREENODE)malloc(sizeof(TREENODE));
        PTREENODE ptreeB = (PTREENODE)malloc(sizeof(TREENODE));
        PTREENODE ptreeC = (PTREENODE)malloc(sizeof(TREENODE));
        PTREENODE ptreeD = (PTREENODE)malloc(sizeof(TREENODE));
        PTREENODE ptreeE = (PTREENODE)malloc(sizeof(TREENODE));
        PTREENODE ptreeF = (PTREENODE)malloc(sizeof(TREENODE));
        PTREENODE ptreeG = (PTREENODE)malloc(sizeof(TREENODE));
    ​
        ptreeA ->data = 'A';
        ptreeA ->pLeftTreeNode = ptreeB;
        ptreeA ->pRightTreeNode = ptreeC;
    ​
        ptreeB ->data ='B';
        ptreeB ->pLeftTreeNode =NULL;
        ptreeB ->pRightTreeNode = NULL;
    ​
        ptreeC ->data = 'C';
        ptreeC ->pLeftTreeNode = ptreeD;
        ptreeC ->pRightTreeNode = ptreeE;
    ​
        ptreeD ->data = 'D';
        ptreeD ->pLeftTreeNode = NULL;
        ptreeD ->pRightTreeNode = NULL;
    ​
        ptreeE ->data ='E';
        ptreeE ->pLeftTreeNode = ptreeF;
        ptreeE ->pRightTreeNode =ptreeG;
    ​
        ptreeF ->data ='F';
        ptreeF ->pLeftTreeNode = NULL;
        ptreeF ->pRightTreeNode = NULL;
    ​
        ptreeG -> data = 'G';
        ptreeG ->pLeftTreeNode = NULL;
        ptreeG ->pRightTreeNode = NULL;
    ​
        return ptreeA;
    }
    ​
    bool firstShowTree(PTREENODE ptreenode)
    {
        printf (" %c", ptreenode->data);
        if (ptreenode ->pLeftTreeNode != NULL)
        {
            firstShowTree(ptreenode->pLeftTreeNode);
        }
        if (ptreenode ->pRightTreeNode != NULL)
        {
            firstShowTree(ptreenode->pRightTreeNode);
        }
        return true;
    }
    ​
    bool inShowTree(PTREENODE ptreenode)
    {
        if (ptreenode->pLeftTreeNode != NULL)
        {
            inShowTree(ptreenode ->pLeftTreeNode);
        }
        printf (" %c", ptreenode->data);
        if(ptreenode->pRightTreeNode)
        {
            inShowTree(ptreenode->pRightTreeNode);
        }
        return true;
    }
    ​
    ​
    bool lastShowTree(PTREENODE ptreenode)
    {
        if (ptreenode->pLeftTreeNode != NULL)
        {
            lastShowTree(ptreenode ->pLeftTreeNode);
        }
    ​
        if(ptreenode->pRightTreeNode)
        {
            lastShowTree(ptreenode->pRightTreeNode);
        }
    ​
        printf (" %c", ptreenode->data);
        return true;
    }
    

    我根据自己画的的二叉树写了静态的二叉树创建函数,惊鸿一瞥郝老师的动态创建二叉树函数心向往之,哼哧哼哧写下来以后,遇见了各种bug和疑问,啧,老郝上课时候不写动态创建函数也是有道理的,先show代码:

    void createTreeDynamic(PTREENODE ptreeNew)
    {
        int tempdata;
        printf("enter please:\n");
        scanf("%d", &tempdata);
        if(tempdata != 0)
        {
            ptreeNew = (PTREENODE)malloc(sizeof(TREENODE));
            if(ptreeNew == NULL)
            {
                printf("createTreeDynamic malloc error!\n");
                exit(-1);
            }
            ptreeNew->data = tempdata;
    ​
            createTreeDynamic(ptreeNew->pLeftTreeNode);
            createTreeDynamic(ptreeNew->pRightTreeNode);
        }
        else
        {
            ptreeNew= NULL;
        }
        return;
    }

    我直接将动态创建函数塞进上面的代码里面之后,开始输入char类型的节点数据,每一次都会打印两行

    enter please:,debug都找不到,后来发现是输入char类型,字母A和回车\n都被作为了输入节点的数据。

    然后我将代码改成如下以后,enter please:就不再出现两次了,可是随着不断输入0并不能从无尽的递归中出来。

    scanf("\n%c",&temp);//作为字符比对,由\n去接受输入的回车换行

    最终无奈只能将所有输入和遍历输出的结果都改成了%d,这样就能不再接受\n,然后正常从递归中转出来了。

    可是依旧有问题,遍历函数的参数不对,通过动态创建库之后得到的指针ptree已经不再指向树的根节点了,应该是已经指向了最后一层的最后一个节点的右孩子树。递归调用中我也没办法把根节点的指针保存下来。

    就把程序放在这里了,大家有什么想法提一提,这可咋弄?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值