• 【DS】实现二叉树的基本操作


    在这里插入图片描述

    博客主页: 心荣~
    系列专栏:【Java实现数据结构】
    一句短话: 难在坚持,贵在坚持,成在坚持!

    1. 二叉树结点的构成

    这里采用的是孩子表示法, 所以节点类(使用的是静态内部类)中除了数值域外要有两个引用来表示节点的左子树和右子树.

    static class TreeNode {
            public char val;//数值
            public TreeNode left;//左子树引用
            public TreeNode right;//右子树引用
    
            public TreeNode(char val) {
                this.val = val;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2. 二叉树的遍历

    二叉树的遍历 (Traversal) 是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问访问结点所做的操作依赖于具体的应用问题(比如:打印节点内容、节点内容加 1)。 遍历是二叉树上最重要的操作之一,是二叉树上进行其它运算之基础。

    其实不管是前序遍历,中序遍历,还是后续遍历,二叉树的遍历所走的路径都是相同的,三者之间的区别只是获取根节点数据的时机不同

    img

    2.1 前序遍历

    前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点—>根的左子树—>根的右子树。

    我们利用递归解决问题的思想, 可以将一个问题拆解为子问题去解决, 也就是实现下面的过程:

    1. 访问根节点数据;
    2. 前序遍历左子树;
    3. 前序遍历右子树;

    递归结束条件:如果结点root为空,则返回。

    img

    //前序遍历
        public void preOrder(TreeNode root) {
            if(root == null) {
                return;
            }
            System.out.print(root.val+" ");
            preOrder(root.left);
            preOrder(root.right);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.2 中序遍历

    中序遍历(Inorder Traversal)——根的左子树—>根节点—>根的右子树;

    和上面的实现思想相同, 只是访问根节点的时机不同;

    1. 中序遍历左子树;
    2. 访问根节点数据;
    3. 中序遍历右子树;

    递归结束条件:如果结点root为空,则返回。

    img

    //中序遍历
        public void InOrder(TreeNode root) {
            if(root == null) {
                return;
            }
            InOrder(root.left);
            System.out.print(root.val+" ");
            InOrder(root.right);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.3 后序遍历

    同样的, 实现过程如下,

    1. 后序遍历左子树;
    2. 后序遍历右子树;
    3. 访问根结点数据;

    递归结束条件:如果结点root为空,则返回。

    img

    //后序遍历
        public void postOrder(TreeNode root) {
            if(root == null) {
                return;
            }
            postOrder(root.left);
            postOrder(root.right);
            System.out.print(root.val+" ");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3. 获取整棵二叉树的节点个数

    获取树中的节点个数, 最容易想到的就是遍历一遍树, 通过计数实现了, 代码写起来也不难;

    也可以通过递归解决子问题的思想来实现 , 本质上还是在遍历二叉树

    • 节点的个数等于根节点(1) + 左子树节点个数 + 右子树节点个数 ,

    递归结束条件: 如果结点root为空,则返回。

        //获取树中节点的个数,遍历计数法
        public static int nodeSize;
        public int size(TreeNode root) {
            //先将nodeSzie置为0
            nodeSize = 0;
            sizefunc(root);
            return nodeSize;
        }
        public void sizefunc(TreeNode root) {
            if(root == null) {
                return;
            }
            nodeSize++;
            sizefunc(root.left);
            sizefunc(root.right);
        }
    
        //获取树中节点的个数,子问题思想
        public int size2(TreeNode root) {
            if(root == null) {
                return 0;
            }
            return size2(root.left) + size2(root.right) + 1;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4. 获取二叉树叶子节点的个数

    同样的思考的话和上面一样, 可以采用计数和子问题来实现, 不过本质上是差不多的;

    递归思路:

    1. 如果结点为空,表示该树没有结点返回0,
    2. 如果结点的左右子树都为空,表示该结点为叶子结点,计算器+1或者返回1。
    3. 一棵二叉树的叶子结点数为左右子树叶子结点数之和。
        //获取叶子节点的个数,子问题思想
        public int getLeafNodeCount(TreeNode root){
            if(root == null) {
                return 0;
            }
            if(root.left == null && root.right == null) {
                return 1;
            }
            return getLeafNodeCount(root.left) + getLeafNodeCount(root.right);
        }
        //获取叶子节点的个数,遍历计数法
        public static int leafSize;
        public int getLeafNodeCount2(TreeNode root){
            leafSize = 0;
            getLeafNodeCount2func(root);
            return leafSize;
        }
        public void getLeafNodeCount2func(TreeNode root) {
            if(root == null) {
                return;
            }
            if(root.left == null && root.right == null) {
                leafSize++;
            }
            getLeafNodeCount2func(root.left);
            getLeafNodeCount2func(root.right);
        }
    
    • 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

    5. 获取第K层节点的个数

    递归思路:

    1. 如果结点为空,返回0,k为1,返回1。
    2. 一棵二叉树第k层结点数为 左子树和右子树第k-1层次的结点数之和。

    当k=1时,表示第一层次的结点个数,结点个数为1,每递归一层,从根节点来说是第k层, 那么相对于根节点的子树来说就是k-1层,所以一棵二叉树第k层结点数为左子树,右子树第k-1层次的结点数之和。

    public int getKLevelNodeCount(TreeNode root, int k) {
            if(root == null || k <= 0) {
                return 0;
            }
            if(k == 1) {
                return 1;
            }
            return getKLevelNodeCount(root.left, k-1) + getKLevelNodeCount(root.right, k-1);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    6. 获取二叉树的高度(深度)

    递归思路:

    1. 如果根结点为空,则这棵树的高度为0,返回0。
    2. 一棵二叉树的最深深度即为左右子树深度的最大值加上1。
    // 获取二叉树的高度
        public int getHeight(TreeNode root) {
            if(root == null) {
                return 0;
            }
            int leftHight = getHeight(root.left);
            int rightHight = getHeight(root.right);
            return leftHight>rightHight ? leftHight+1 : rightHight+1;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    7. 在二叉树中寻找目标值

    通过遍历去搜索比较即可, 前中后序遍历都可以.

    //检测值为val的元素是否存在
        public boolean find(TreeNode root, char val) {
            if(root == null) {
                return false;
            }
            if(root.val == val) {
                return true;
            }
            boolean ret1 = find(root.left, val);
            if(ret1){
                return true;
            }
            boolean ret2 = find(root.right, val);
            if(ret2){
                return true;
            }
            return false;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    8. 判断二叉树是不是完全二叉树

    判断一棵树是不是完全二叉树,我们可以设计一个队列来实现,

    完全二叉树按照从上至下, 从左到右的顺序节点之间是连续着没有空位置的, 这里如果有不了解的可以看一看二叉树概念篇的博客; 如果一颗二叉树不是完全二叉树 , 那么树中的节点之间是有空着的位置的(null); 只要找到这个位置, 后面再没有节点了就是完全二叉树; 如果空位置后面还有节点就不是完全二叉树;

    我们可以设计一个队列来实现, 首先将根节点入队,然后循环每次将队头元素出队同时将出队节点的左右孩子结点(包括空结点)依次入队,以此类推,直到获取的结点为空(就是上面说的空位置),此时判断队列中的所有元素是否为空,如果为空,就表示这棵二叉树为完全二叉树 ; 否则就不是完全二叉树.

    //判断一棵树是不是完全二叉树
        public boolean isCompleteTree(TreeNode root) {
            if(root == null) {
                return true;
            }
            Queue<TreeNode> queue = new LinkedList<>();
            queue.offer(root);
            while(!queue.isEmpty()) {
                TreeNode cur = queue.poll();
                if(cur == null) {
                    break;
                }
                queue.offer(cur.left);
                queue.offer(cur.right);
            }
            //判断队列中是否有不为空的元素
            int size = queue.size();
            while(size != 0) {
                size--;
                if(queue.poll() != null) {
                    return false;
                }
            }
            return true;
        }
    
    • 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

    9. 层序遍历

    层序遍历的实现方式与判断一棵二叉树是否是完全二叉树类似,都是使用队列来进行实现,只有一点不同, 入队时如果结点为空,则不需要入队,其他的地方完全相同, 出队时获取到节点中的元素, 直到最终队列和当前结点均为空时,表示遍历结束。

    //层序遍历
        public void levelOrder(TreeNode root) {
            if(root == null) {
                return;
            }
            Queue<TreeNode> queue = new LinkedList<>();
            queue.offer(root);
            while(!queue.isEmpty()) {
                TreeNode cur = queue.poll();
                System.out.print(cur.val+" ");
                if(cur.left != null) {
                    queue.offer(cur.left);
                }
                if(cur.right != null) {
                    queue.offer(cur.right);
                }
            }
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  • 相关阅读:
    c#仿热血江湖
    一体机支持使用硬件设备国密加密,JumpServer堡垒机v2.26.0发布
    美团图灵机器学习平台性能起飞的秘密(一)
    AIR32F103(四) 27倍频216MHz,CoreMark跑分测试
    智能手术机器人技术与原理(二)
    认识Vue
    电子商务商城源码 Spring Cloud、Spring Boot、Mybatis、Redis
    Java基础
    【华为OD机试真题 python】猜密码 【2022 Q4 | 200分】
    vim的简单使用
  • 原文地址:https://blog.csdn.net/Trong_/article/details/127435306