• 【数据结构】二叉树的基本操作


    目录:

    二叉树的基本操作

    1. 二叉树的创建

    二叉树的存储方式哦同样有两种,一种是顺序存储,一种是链式存储

    1.1. 顺序存储

    #define MAXSIZE 100
    struct TreeNode{
        int data;
        bool isEmpty;
    }
    TreeNode t[MAXSIZE];
    ```·
    与其他数据结构类似,树的结构同样是先来一个结点,再来一个树的整体。
    在树的结构体中,data表示数据域,用来存放树的节点的数据,isEmpty表示树的这个节点是否为空。
    
    对树的节点声明以后,再创建一个以节点类型为数组元素类型的数组,这样树的结构就定义好了。
    
    但由于顺序二叉树对于空间的浪费和查询的困难,顺序二叉树只适合于完全二叉树,所以我们一般使用链式二叉树。
    
    ### 链式存储
    
    一个典型的存储结构如下
    
    ```c
    typedef struct BTNode{
        int data;
        struct node *lchild;
        struct node *rchild;
    }BTNode,*BTree;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    该结构采用的是二叉树的左右链表示,即一个结构体中有三部分,一部分是二叉树节点本事的数据,其他两部分是指向二叉树下一个节点的指针,一个指向左子树,一个指向右子树。
    其中data为数据域,left和right为指针域,分别指向左孩子和右孩子。

    2. 二叉树的初始化

    二叉树的初始化只需要创建一个二叉树,并让他等于NULL即可。

    void InitBTree(BTree *root)
    {
        *root=NULL;
    }
    
    • 1
    • 2
    • 3
    • 4

    如果我们要对根节点进行初始化的话,可以采取以下的方式:

    (*root)=(BTree)malloc(sizeof(BTNode));
    (*root)->lchild=NULL;
    (*root)->rchild-NULL;
    (*root)->data=2;
    
    • 1
    • 2
    • 3
    • 4

    3. 二叉树插入节点

    如果需要对一个二叉树插入一个节点,我们只需要申请内存然后赋值即可。

    BTNode *p=(BTNode *)malloc(sizeof(BTNode));
    p->data=2;
    p->lchild=NULL;
    p->rchild=NULL;
    root->lchild=p;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4. 二叉树的遍历

    二叉树的遍历有四种基本的方式,分别为先序遍历,中序遍历,后序遍历和层次遍历。其中每一种方式又有两种方式,分别为递归和非递归。

    4.1. 递归遍历

    递归遍历的方式比较简单,只需要按照先序遍历的顺序,依次递归遍历二叉树即可,并且不同遍历方式的代码相似。

    void firstTree(BTree BT)//先序遍历递归
    {
        if(BT!=NULL){
            printf("%c",BT->data);
            displayTree(BT->left);
            displayTree(BT->right);
        }
        else
            printf("#");
    }
    void middleTree(BTree BT)//中序遍历递归
    {
        if(BT!=NULL){
            displayTree(BT->left);
            printf("%c",BT->data);
            displayTree(BT->right);
        }
        else
            printf("#");
    }
    void lastTree(BTree BT)//后序遍历递归
    {
        if(BT!=NULL){
            displayTree(BT->left);
            displayTree(BT->right);
            printf("%c",BT->data);
        }
        else
            printf("#");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    4.2. 层序遍历

    层序遍历的基本原理是借助队列的形式,当一个节点被读入以后,我们先将其压入队列末尾。接下来如果队列元素不空的话,我们就将队头的节点弹出,然后访问其值,并且将其左右子树压入队列末尾。以此类推循环往复,就达到了层序遍历的目的。
    具体代码实现如下

    void levelOrder(BTree BT)
    {
        LinkQuene Q;
        InitQuene(Q);
        BTree p;
        EnQuene(Q,BT);
        while(!isEmpty(Q))
        {
            DeQueneu(Q,p);
            visit(p);
            if(p->lchild!=NULL)
                EnQuene(Q,p->lchild);
            if(p->rchild!=NULL)
                EnQuene(Q,p->rchild);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4.3. 非递归遍历

    非递归遍历需要通过栈的操作来进行,
    示例代码如下:

    //前序遍历
    void firstTree(BTNode* root)
    {
        if (root == NULL)
            return;
        BTNode* p = root;
        stack<BTNode*> s;  //创造一个栈,用来存放树节点
        while (!s.empty() || p!=NULL)
        {
            //如果p不为空的话,酒将p压入栈中,然后让p指向左子树
            while (p!=NULL)
            {
                printf("%c",p->data);
                s.push(p);
                p = p->lchild;
            }
            //当p为空时,说明根和左子树都遍历完了,该进入右子树了
            if (!s.empty())
            {
                p = s.top();
                s.pop();
                p = p->rchild;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    
    //中序遍历
    void middleTree(BTNode* root)
    {
        if (root == NULL)
            return;
        BTNode* p = root;
        stack<BTNode*> s;  //创造一个栈,用来存放树节点
        while (!s.empty() || p!=NULL)
        {
            //如果p不为空的话,酒将p压入栈中,然后让p指向左子树
            while (p!=NULL)
            {
                s.push(p);
                p = p->lchild;
            }
            //当p为空时,说明根和左子树都遍历完了,该进入右子树了
            if (!s.empty())
            {
                p = s.top();
                s.pop();
                printf("%c",p->data);
                p = p->rchild;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    其中前序遍历和中序遍历的方式较为相似,只需要先建立一个栈,然后将访问到的节点存入栈中,节点一直向左指向,直到节点为空。
    当节点为空时让指针指向栈顶元素并弹出,然后开始右子树的访问。
    前序遍历和中序遍历的区别仅仅在于打印数据的时间不同。

    而后序遍历则更加复杂一点

    //后序遍历
    void PostOrderWithoutRecursion(BTNode* root)
    {
        if (root == NULL)
            return;
        stack<BTNode*> s;
        BTNode *p=root;//p用来表示当前访问的节点
        BTNode *q=NULL;//q用来表示上一个访问的节点
        //先把p移动到左子树最下边
        while (p!=NULL)
        {
            s.push(P);
            p = p->lchild;
        }
        while (!s.empty())
        {
            //此时让p已经为空了,先从栈中提取一个元素赋给p
            p = s.top();//现在p的值就是最左下角的节点
            s.pop();
            if (p->rchild == NULL || p->rchild == q) //如果右子树为空或者刚访问过右子树
            {
                printf("%c",p->data);
                q = p;//每次输出值以后,修改上一次访问的节点
            }
            else if(p->lchild==q)//如果左子树已经访问过了
            {
                s.push(p);//让根结点入栈
                p = p->rchild; //进入右子树
                while (p!=NULL)
                {
                    s.push(p);
                    p = p->lchild;
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    后序遍历需要两个指针,其中一个指针指向的是当前访问的元素,另一个指针指向的是上次访问过的元素。
    这样是为了保证左右子树都访问过了以后在访问根节点:当我们访问过左子树以后,弹出根节点,然后需要判断右子树是否访问过或者右子树是否为空,如果是的话就访问根节点,否则就将右子树的根节点压入栈中,然后再访问右子树的根节点的左子树,直到右子树为空或者右子树被访问过,就返回去访问根节点弹栈。

  • 相关阅读:
    java计算机毕业设计高校迎新管理系统源码+数据库+系统+lw文档+部署
    ORACLE XXX序列 goes below MINVALUE 无法实例化的处理办法
    【牛客网-公司真题-前端入门篇】——奇安信秋招笔试-前端-卷2
    如何系统 如何进行SQL监控-执行SQL分析打印
    【无标题】
    docker run和docker start的区别
    MyBatis 笔记
    PHP调用接口京东API封装的例子( 获得JD商品详情,按关键字搜索商品, 按图搜索京东商品(拍立淘),获得店铺)
    Jetson nano 环境配置
    “哪李贵了”主播带货电商被喷,说到底还是服务问题
  • 原文地址:https://blog.csdn.net/qq_34168477/article/details/133621479