• 数据结构与算法:树 线索化二叉树(中,前,后序)(三)


    Tips: 采用java语言,关注博主,底部附有完整代码

    工具:IDEA

    本系列介绍的是数据结构:

    这是第三篇目前计划一共有12篇:

    1. 二叉树入门
    2. 顺序二叉树
    3. 线索化二叉树 (本篇)
    4. 堆排序
    5. 赫夫曼树(一)
    6. 赫夫曼树(二)
    7. 赫夫曼树(三)
    8. 二叉排序树
    9. 平衡二叉树
    10. 2-3树,2-3-4树,B树 B+树 B*树 了解
    11. 红黑树(一)
    12. 红黑树(二)

    敬请期待吧~~

    高光时刻

    中序线索化前序线索化后续线索化
    image-20220630160524721image-20220630164056323image-20220630165130764

    回顾

    先来回顾一下中序遍历

    中序遍历gif

    每一个结点有2个对象,最终导致叶子结点就有所浪费

    当前一共有6个元素,分别是 [1,3,6,8,10,14], 最终就会导致有7个对象浪费(null)掉

    出现问题

    image-20220630160144118

    线索化二叉树就是尽量少浪费对象,那么空对象如何处理呢?

    如何解决

    先来看完整流程图,在逐步分析:

    解决问题gif

    最终结果图:

    image-20220630160524721

    中序线索化二叉树 就是按照中序遍历的结果将

    • 叶子结点
    • 单子结点

    按照中序的排列连接起来

    当前中序遍历结果是 [8, 3, 10, 1, 14, 6]

    以10举例,10是叶子结点,那么就按照中序遍历10左右的结点连接起来

    • 10左侧的值3 当作10的左子结点
    • 10右侧的值1 当作10的右子结点

    因为10是叶子结点,如果连接起来的也叫左子结点和右子结点容易和真正有左子/右子结点的结点搞混

    所以在线索化中,如果是通过连接连起来的就叫做前驱结点后继结点

    例如 :

    • 10的前驱结点是3

    • 10的后继结点是1

    • 8的前驱结点是null

    • 8的后继结点是3

    • 3的左子结点是8

    • 3的右子结点是10

    先按照树状图来看结点,如果他有左子结点或者右子结点,那么就按照排列好的顺序来,就是前驱结点和后继结点

    直接来看一眼代码

    中序

    中序线索化

    结点:

    public class HeroNode {
        public int id;
        public String name;
        // 左子结点
        public HeroNode leftNode;
        // 右子结点
        public HeroNode rightNode;
    
        // [0 左子节点] [1 前驱节点]
        public int leftType;
    
        // [0 右子节点] [1 后继节点]
        public int rightType;
    
        public HeroNode(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "HeroNode{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", leftType='" + leftType + '\'' +
                    ", rightType='" + rightType + '\'' +
                    '}';
        }
    }
    
    • 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

    Tree:

    public class ThreadedBinaryTree {
       // root 结点
       private final HeroNode root;
    
        public ThreadedBinaryTree(HeroNode root) {
            this.root = root;
        }
      
       public void threadedCenter() {
            threadedCenter(root);
        }
    
        // 用于记录中序线索化上一个元素
        private HeroNode centerPre;
    
        public void threadedCenter(HeroNode node) {
            // 如果 node == null 就退出递归
            if (node == null) {
                return;
            }
    
            // 线索化左子树
            threadedCenter(node.leftNode);
    
    
            // 处理左子树 如果 leftNode == null 就认为他是前驱结点
            if (node.leftNode == null) {
                // 设置为前驱节点
                node.leftNode = centerPre;
                // 设置为前驱类型
                node.leftType = 1;
            }
    
            // 处理右子树 如果rightNode == null 就认为他是后继结点
            if (centerPre != null && centerPre.rightNode == null) {
                centerPre.rightNode = node;
                // 设置为后继结点类型
                centerPre.rightType = 1;
            }
    
            // pre后移
            centerPre = node;
    
            // 线索化右子树
            threadedCenter(node.rightNode);
        }
    }
    
    • 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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    中序线索化遍历

    public void showThreadedCenter(HeroNode node) {
            if (node == null) {
                return;
            }
    
            // 如果是左子结点就遍历
            if (node.leftType == 0) {
                showThreadedPre(node.leftNode);
            }
      
            System.out.println(node);
    
            // 如果是右子结点就遍历
            if (node.rightType == 0) {
                showThreadedPre(node.rightNode);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    中序线索化和普通树结构遍历是类似的,只需要判断是否是左子 / 右子结点即可!

    前序

    前序遍历解决前解决后
    image-20220630164041936image-20220630164050608image-20220630164056323

    可以看到前序遍历,如果前序线索化后,只浪费了一个结点 !

    前序线索化

    # ThreadedBinaryTree.java
      
    // 记录前序线索化上一个元素
    public HeroNode prePre;
    
    public void threadedPre(HeroNode node) {
        // 如果node == null 就退出递归
        if (node == null) {
            return;
        }
    
        // 和中序思路一样
        if (node.leftNode == null) {
            node.leftNode = prePre;
            node.leftType = 1;
        }
        if (prePre != null && prePre.rightNode == null) {
            prePre.rightNode = node;
            prePre.rightType = 1;
        }
    
        prePre = node;
    
        // 如果是左子结点点就去线索化 是前序结点就不走
        if (node.leftType == 0) {
            // 线索化左子树
            threadedPre(node.leftNode);
        }
    
        // 如果是右子结点就去线索化 是后继结点就不走
        if (node.rightType == 0) {
            // 线索化右子树
            threadedPre(node.rightNode);
        }
    }
    // endregion
    
    • 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

    这里思路和中序线索化一样,就不过多赘述了,来看看遍历吧

    前序线索化遍历

    # ThreadedBinaryTree.java
      
    // region TODO 前序线索化遍历
    // @author: android 超级兵
    // @create: 2022/6/8 09:55
    //
    public void showThreadedPre() {
      showThreadedPre(root);
    }
    public void showThreadedPre(HeroNode root) {
        if (root == null) {
            return;
        }
        System.out.println(root);
        if (root.leftType == 0) {
            showThreadedPre(root.leftNode);
        }
    
        if (root.rightType == 0) {
            showThreadedPre(root.rightNode);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    后续

    后续遍历线索前线索后
    image-20220630165037838image-20220630165049816image-20220630165130764

    代码思路和前序一样:直接看

    后续线索化

    # ThreadedBinaryTree.java
      
    /*
     * 作者:android 超级兵
     * 创建时间: 6/7/22 5:48 PM
     */
    private HeroNode lastPre;
    
    public void threadedLast() {
        threadedLast(root);
    }
    
    public void threadedLast(HeroNode node) {
        if (node == null) {
            return;
        }
    
        // 如果是左子结点才遍历
        if (node.leftType == 0) {
            // 线索化左子树
            threadedLast(node.leftNode);
        }
    
        // 如果是右子结点才遍历
        if (node.rightType == 0) {
            // 线索化右子树
            threadedLast(node.rightNode);
        }
    
        if (node.leftNode == null) {
            node.leftNode = lastPre;
            node.leftType = 1;
        }
    
        if (lastPre != null && lastPre.rightNode == null) {
            lastPre.rightNode = node;
            lastPre.rightType = 1;
        }
    
        lastPre = node;
    }
    //endregion
    
    • 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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    后续线索化遍历

    # ThreadedBinaryTree.java
      
    public void showThreadedLast() {
        showThreadedLast(root);
    }
    
    public void showThreadedLast(HeroNode root) {
    		// 左子结点就遍历
        if (root.leftType == 0) {
            showThreadedLast(root.leftNode);
        }
    
      	// 右子结点就遍历
        if (root.rightType == 0) {
            showThreadedLast(root.rightNode);
        }
    
        System.out.println(root);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    完整代码

    原创不易,您的点赞就是对我最大的支持!

    下一篇:堆排序[开发中…]

  • 相关阅读:
    docker离线安装和使用
    SQL 基础知识梳理(一)- 数据库与 SQL
    实习日报-2022-7-29
    计算机系统基础·第3章【程序的转换及机器级表示】
    低代码发展趋势解读|低代码成为企业数字化转型“加速器”
    JS之闭包
    【规范】看看人家Git提交描述,那叫一个规矩
    【Java 数据结构及算法实战】系列 017:HJ3 明明的随机数
    autox.js的三个版本universal、armeabi-v7a、arm64-v8a的区别
    C++ 内存分区模型
  • 原文地址:https://blog.csdn.net/weixin_44819566/article/details/125544208