美文网首页
二叉树的存储、遍历及应用

二叉树的存储、遍历及应用

作者: 在安言庆 | 来源:发表于2020-07-16 18:10 被阅读0次

    1 存储

    1.1 顺序存储

    设二叉树深度为h,那么我们就按照深度为h的满二叉树的结点个数(2^h-1)创建对应元素个数的数组,然后按照自上而下,从左到右的顺序,依次将树中结点存入数组即可。
    例如下图所示:

    待存储的二叉树

    我们需要存储一个深度为4的二叉树,所以先在脑海里构建如下相同深度的满二叉树,并且编号(对应数组下标,我这里从1开始,看官请自便)。


    深度为4的满二叉树

    由于待存储二叉树的最后一个结点 h 对应满二叉树编号为 12 的结点,并且编号(即数组下标)是从1开始的,因此,我们创建一个长度为(12+1)的数组node[13],具体如下:

    Index Value
    0 0
    1 a
    2 b
    3 c
    4 d
    5 e
    6 f
    7 0
    8 0
    9 g
    10 0
    11 0
    12 h

    至此,我们就完成了树的顺序存储,代码实现简单不做赘述。
    我们同时也能发现,长度为13的数组还有5个元素只是打了个酱油,这就是顺序存储的特点,也是缺点:它只适合存储满二叉树或者完全二叉树,否则就会存在空间浪费。
    那么,我们来看看下面的重点内容。

    1.2 链式存储

    其实,很简单,就是创建链表存储二叉树各结点,直接上个结构体定义帮助理解,如下:

    // 二叉树结点结构体定义
    struct node {
        char data;      //结点值
        node *left;     //指向左子树的结点指针
        node *right;    //指向右子树的结点指针
    };
    

    紧接着,我们如何创建链表存储上述的二叉树呢,给大家个葫芦,自己照着画葫芦好吧。

    // 链式存储二叉树
    node root;  // 创建根结点
    root.data = a;
    root.left = new node;   // 创建左结点
    memset(root.left, 0, sizeof(node)); // 初始化内存
    root.left->data = b;
    root.right = new node;  // 创建右结点
    memset(root.right, 0, sizeof(node));
    root.right->data = c;
    

    完全是翻译操作,毫无技术可言,难点在于真正面临问题给出数据时该怎么快捷实现二叉树存储。

    2 遍历

    2.1 先序、中序和后序

    对于先序、中序和后序三种遍历来说,它们实际遍历路径完全相同,先、中、后只是在强调何时访问结点而已。
    举个栗子~还是它吧!


    在这里插入图片描述

    三种遍历路径实际都是酱紫的,自己跟着编号走一遍:

    graph TD
    a((a))--1-->b((b))
    b((b))--2-->d((d))
    d((d))--3-->dl((NULL))
    dl((NULL))--4-->d((d))
    d((d))--5-->g((g))
    g((g))--6-->gl((NULL))
    gl((NULL))--7-->g((g))
    g((g))--8-->gr((NULL))
    gr((NULL))--9-->g((g))
    g((g))--10-->d((d))
    d((d))--11-->b((b))
    b((b))--12-->e((e))
    e((e))--13-->el((NULL))
    el((NULL))--14-->e((e))
    e((e))--15-->er((NULL))
    er((NULL))--16-->e((e))
    e((e))--17-->b((b))
    b((b))--18-->a((a))
    a((a))--19-->c((c))
    c((c))--20-->f((f))
    f((f))--21-->h((h))
    h((h))--22-->hl((NULL))
    hl((NULL))--23-->h((h))
    h((h))--24-->hr((NULL))
    hr((NULL))--25-->h((h))
    h((h))--26-->f((f))
    f((f))--27-->fr((NULL))
    fr((NULL))--28-->f((f))
    f((f))--29-->c((c))
    c((c))--30-->cr((NULL))
    cr((NULL))--31-->c((c))
    c((c))--32-->a((a))
    

    这就相当清楚明了了,我们可以看到每个存在的结点都会遍历三次,是吧?掰手指头数数指向或被指向箭头的个数就知道啦!
    不会有人站出来喷我不对吧?

    老师坏坏,老师骗人 ,根结点只有两次!

    根结点谁去访问的,别忘了,你和根结点之间还串根遍历路径呢!
    综上所述,可得:

    1. 先序:第一次遍历到就访问。
      该二叉树对应先序序列为:abdgecfh
    2. 中序:第二次遍历到再访问。
      该二叉树对应中序序列为:dgbeahfc
    3. 后序:第三次遍历到才访问。
      该二叉树对应后序序列为:gdebhfca

    2.2 层序

    层序:自上而下,从左到右遍历并访问二叉树各结点。
    该二叉树对应层序序列为:abcdefgh

    3 应用

    好了,最后搞个例题爽一把~

    现给出若干个各不相同的整数用以构建二叉树,规则如下:
    1、以第一个数为根结点;
    2、后续整数依序与根结点数值比较后决定其位置:
    (1)若大于根结点值,则将该数往根结点的右子结点移动,若此右子结点为空,则将该数存入,否则就重复比较直到找到合适的空结点;
    (2)若小于或等于根结点值,则将该数往根结点的左子结点移动,若此左子结点为空,则将该数存入,否则就重复比较直到找到合适的空结点。
    分别以层序、先序、中序、后序输出所有整数。
    输入:仅一行,若干个不相同的整数,以空格隔开。
    输出:共四行,分别以层序、先序、中序和后序输出,各整数以空格隔开。

    #include <iostream>
    #include <vector>
    #include <queue>
    #include <cstring>
    using namespace std;
    
    struct tree {
        int num;
        struct tree * l;
        struct tree * r;
    };
    
    // 后序遍历
    void back_sort(tree * node) {
        if (!node)
        {
            return ;
        }
    
        back_sort(node->l); 
        back_sort(node->r); 
        cout << node->num << ' ';   // 左右子树遍历后,此次为第三次到达该结点,则输出
    }
    
    // 中序遍历
    void mid_sort(tree * node) {
        if (!node)
        {
            return ;
        }
    
        mid_sort(node->l);    
        cout << node->num << ' ';   // 左子树遍历后,此次为第二次到达该结点,则输出
        mid_sort(node->r);
    }
    
    // 先序遍历
    void front_sort(tree * node) {
        if (!node)
        {
            return ;
        }
        
        cout << node->num << ' ';   // 第一次到达该结点,输出
        front_sort(node->l);
        front_sort(node->r);
    }
    
    // 层序遍历
    void level_sort(tree * root) { 
        queue<tree *> q;    // 队列使用
        if (!root)
        {
            return ;
        }
        
        // 从根结点开始,层序方式入队存储结点,则出队也必然是层序
        // 队列特性:先入先出(FIFO)
        q.push(root);
        while (!q.empty())
        {
            tree * tmp = q.front();
            cout << tmp->num << ' ';
            if (tmp->l)
            {
                q.push(tmp->l);
            }
            if (tmp->r)
            {
                q.push(tmp->r);
            }
            q.pop();
        }
        
    }
    
    // 构建二叉树
    tree * push_tree(tree * node, int num) {
        if (node)
        {
            if (num <= node->num)
            {
                // 新存入结点权值小于等于当前结点权值,则向左子树存储
                node->l = push_tree(node->l, num);  
            } else
            {
                // 新存入结点权值大于当前结点权值,则向右子树存储
                node->r = push_tree(node->r, num);
            }
        } else
        {
            node = new tree;
            // 不使用memset那就辛苦点,一个个初始化结构体各数据成员
            memset(node, 0, sizeof(tree));
            //node->num = 0;
            //node->l = NULL;
            //node->r = NULL;
            node->num = num;
        } 
        return node;
    }
    
    int main() {
        int value, count;
        // 使用容器可以按需存储结点,比上来定义一个长度超大的数组要有逼格些
        vector<int> a;
        tree * root = NULL;
        cin >> value;
        a.push_back(value);
        while(cin.get() != '\n')
        {
            cin >> value;
            a.push_back(value);
        }
    
        // 每次从根结点开始创建,旨在进行权值判断,从而明确二叉树中各结点位置
        count = a.size();
        for (int i = 0; i < count; i++)
        {
            root = push_tree(root, a[i]);
        }
    
        level_sort(root);   // 层序
        cout << endl;
        front_sort(root);   // 先序
        cout << endl;
        mid_sort(root);     // 中序
        cout << endl;
        back_sort(root);    // 后序
        cout << endl;
    
        return 0;
    }
    

    为了我可爱而勇猛的学生们,码一下午字也值了~
    奥利给~妙啊~

    相关文章

      网友评论

          本文标题:二叉树的存储、遍历及应用

          本文链接:https://www.haomeiwen.com/subject/hzrmhktx.html