[Java 数据结构] 二叉树知识 及 二叉树相关题目

目录

 

一、一些概念

二、二叉树(binary tree)是一种特殊的树

2.1概念

2.2二叉树的基本形态

 2.3两种特殊的二叉树

 2.4二叉树的性质

2.5二叉树结点的代码表示

2.6二叉树的基本操作

2.6.1二叉树的遍历

2.6.2代码实现

2.7与二叉树相关的一些题

2.7.1统计二叉树一共有多少个结点

2.7.2求二叉树的叶子结点的个数

2.7.3求给定二叉树第 k 层的结点个数

2.7.4 求二叉树的高度

2.7.5查找 val 所在结点,没有找到返回 null

2.7.6前序遍历返回是List

2.7.7 力扣100.相同的树

 2.7.8 力扣101.对称二叉树

101. 对称二叉树https://leetcode-cn.com/problems/symmetric-tree/

2.7.9 力扣572.另一棵树的子树

2.7.10 力扣110.平衡二叉树

2.7.10 二叉树的层序遍历


一、一些概念


1.树:树是一种非线性的数据结构,它是由nn>=0)个有限结点组成一个具有层次关系的集合。

2.空树(empty tree):一个结点都没有的树

3.根节点(root node):一棵树中,没有双亲结点的结点,如上图:A

4.叶子结点(leaf node):度为0的结点称为叶结点; 如上图:BCHI、...等结点为叶子结点

5.非叶子结点(trunk node)

6.结点的度:一个结点含有的子树的个数称为该结点的度; 如上图:A的为6

7.双亲结点(parent)::若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:AB的父节点

8.孩子节点(child):一个节点含有的子树的根节点称为该节点的子节点; 如上图:BA的孩子节点

9.层次(level):从根开始定义起,根为第1层,根的子节点为第2层,以此类推;

10.高度(height):树中节点的最大层次; 如上图:树的高度为4

二、二叉树(binary tree)是一种特殊的树


2.1概念

1.二叉树的度是小于等于2,换言之二叉树中的结点,如果有孩子,最多只能有两个孩子。结点可以有0、1、2个孩子

2.二叉树是一种有序树

如果我们认为两个图表示的是同一棵树(孩子的顺序不重要),称其为无序树。

如果我们认为两个图表示的不是同一棵树(孩子的顺序很重要),称其为有序树。

2.2二叉树的基本形态

直接将二叉树结点的两个孩子命令为左孩子和右孩子

区分:左孩子 vs 右孩子;左子树 vs 右子树

孩子:结点;子树:结构(哪怕里面只有一个结点或者一个结点都没有)

 2.3两种特殊的二叉树

1.满二叉树         结点个数(n)  高度(h)  n = 2^h - 1

 2.完全二叉树        二叉树的结点上限:满二叉树的节点个数

 2.4二叉树的性质

    1.  若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i - 1) (i>0)个结点

    2. 若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2^k - 1 (k>=0)

    3. 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0n21

    4. 具有n个结点的完全二叉树的深度klog_{2}(n +1)上取整

2.5二叉树结点的代码表示

描述的是结点而不是二叉树本身

public static TreeNode buildTree(){
       TreeNode n1 = new TreeNode('A');
       TreeNode n2 = new TreeNode('B');
       TreeNode n3 = new TreeNode('C');
       TreeNode n4 = new TreeNode('D');

       n1.left = n2; n1.right = n3;        
       n2.left = n4; n2.right = null;    //(为null的也可以不写,不写的默认为null)
        
       return n1;
    }

2.6二叉树的基本操作

2.6.1二叉树的遍历

遍历:按照指定规则针对集合中的每个元素进行指定行为

二叉树中的元素之间没有什么前后关系,因此我们要想遍历,就必须认为的规定一些顺序进行元素之间的遍历。

广度优先的(层序遍历)自上而下,自左向右。

深度优先的遍历:整棵树被分为三部分:根结点,左子树,右子树。规定:左子树的遍历优先于右子树的遍历       

    1.前序遍历(Preorder Traversal )——访问根结点--->根的左子树--->根的右子树。

    2. 中序遍历(Inorder Traversal)——根的左子树--->根节点--->根的右子树。

    3.后序遍历 (Postorder Traversal)——根的左子树 ---> 根的右子树 ---> 根节点

 前序遍历:访问根结点,访问根结点的左子树的前序遍历结果,访问根结点的右子树的前序遍历结果

D的前序遍历结果为:D;H的前序遍历结果为:H;F的前序遍历结果为:F;G的前序遍历结果为:G;E的前序遍历结果为:E H; C的前序遍历结果为:C F G; B的前序遍历结果为:BD+E的前序遍历结果,即为BDEH; A的前序遍历结果为:A BDEH CFG(中序遍历和后序遍历的道理相同)

2.6.2代码实现

首先要写一个TreeNode类

public class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;

    public TreeNode(int val){
        this.val = val;
        this.left = null;
        this.right = null;
    }

    @Override
    public String toString(){
        return String.format("TreeNode{%c}",val);
    }
}

然后在TestTree中写遍历的代码,其中要先建立起一个二叉树,表明每个结点之间的关系

public class TreeTest1 {
    public static TreeNode buildTree(){
        // 定义结点
        TreeNode a = new TreeNode('A');
        TreeNode b = new TreeNode('B');
        TreeNode c = new TreeNode('C');
        TreeNode d = new TreeNode('D');
        TreeNode e = new TreeNode('E');
        TreeNode f = new TreeNode('F');
        TreeNode g = new TreeNode('G');
        TreeNode h = new TreeNode('H');
        TreeNode i = new TreeNode('I');
        TreeNode j = new TreeNode('J');
        TreeNode k = new TreeNode('K');
        TreeNode l = new TreeNode('L');
        TreeNode m = new TreeNode('M');
        TreeNode n = new TreeNode('N');
        TreeNode o = new TreeNode('O');
        TreeNode p = new TreeNode('P');
        TreeNode q = new TreeNode('Q');

        // 定义结点之间的关系
        a.left = o; a.right = b;
        b.right = c;
        c.left = d; c.right = e;
        d.left = f; d.right = g;
        e.right = h;
        g.left = i; g.right = j;
        l.right = k;
        m.left = l;
        o.left = p; o.right = q;
        q.left = m; q.right = n;

        // 返回根结点
        return a;
    }


    public static void main(String[] args) {
        TreeNode root = buildTree();

        preorder(root);
        System.out.println();
        inorder(root);
        System.out.println();
        postorder(root);
        System.out.println();
    }

    // 采用递归方法的形式进行编写
    // 打印出各个结点的值即可
    // 传入的参数:一棵树的根结点;如果传入了 null,我们要正确地对空树进行前序遍历
    //前序遍历
    public static void preorder(TreeNode root){
        // 二叉树的常见形态
        // 1. 空树 -> 一个结点都没有 -> 根结点不存在 -> root == null
        // 2. 不是空树 && 根的左右子树为空树
        // 3. 不是空树 && 根的左子树为空树 && 根的右子树不为空树
        // 4. 不是空树 && 根的左子树不为空树 && 根的右子树为空树
        // 5. 不是空树 && 根的左右子树都不为空树

        if(root != null){
            System.out.printf("%c ",root.val);
            preorder(root.left);
            preorder(root.right);
        }
    }

    //中序遍历
    public static void inorder(TreeNode root) {
        if(root != null){
            inorder(root.left);
            System.out.printf("%c ",root.val);
            inorder(root.right);
        }
    }

    //后序遍历
    public static void postorder(TreeNode root){
        if(root != null){
            postorder(root.left);
            postorder(root.right);
            System.out.printf("%c ",root.val);
        }
    }
}

2.7与二叉树相关的一些题

2.7.1统计二叉树一共有多少个结点

方法一:对二叉树进行遍历,每经过一个结点(不是null),统计变量++(选用静态属性作为统计变量,因为局部变量会随着方法的一次调用就结束了,我们要使用递归,而且是静态方法,属性也是不适用的,因此最后选择静态属性);遍历方式可以采用:层序、前序、中序、后序

 private static int nodeCount;

 public static void calcNodeCountVersion1(TreeNode root){
       if(root != null){
            nodeCount++;
            calcNodeCountVersion1(root.left);
            calcNodeCountVersion1(root.right);
        }
    }

 public static void main(String[] args) {
        TreeNode root = buildTree();

        //因为有可能统计很多次,所以每一次nodeCount 都让他的默认值为0,这样就不会造成累计
        nodeCount = 0;      
        calcNodeCountVersion1(root);
        System.out.println("结点个数:" + nodeCount);
        nodeCount = 0;
        calcNodeCountVersion1(root);
        System.out.println("结点个数:" + nodeCount);

方法二:一棵树由根和左右子树构成,我们求出左子树的节点个数,记为 leftCount,右子树的结点个数,记为 rightCount,那么整棵树的结点个数 = leftCount + rightCount + 1。这种方法采用了递归的思想,问题的性质没有变,但问题的规模在减小,总有一天规模会减为0(也就是空树),如果是空树了的话,那结点个数就是0。

 public static int calcNodeCountVersion2(TreeNode root){
        if(root == null){
            return 0;
        }
        int leftCount = calcNodeCountVersion2(root.left);
        int rightCount = calcNodeCountVersion2(root.right);

        return leftCount + rightCount + 1;
    }

 public static void main(String[] args) {
        TreeNode root = buildTree();

        int count = calcNodeCountVersion2(root);
        System.out.println("结点个数:" + count);
        count = calcNodeCountVersion2(root);
        System.out.println("结点个数:" + count);
        count = calcNodeCountVersion2(root);
        System.out.println("结点个数:" + count);

    }

2.7.2求二叉树的叶子结点的个数

方法一:通过遍历的思路,针对每个结点判断该结点是不是叶子结点(左孩子和右孩子都不存在null),统计变量++。

 private static int leafCount;
    private static void calcLeafCount(TreeNode root){
        if(root != null){
            if(root.left == null && root.right == null){
                leafCount++;
            }
            calcLeafCount(root.left);
            calcLeafCount(root.right);
        }
    }

 public static void main(String[] args) {
        TreeNode root = buildTree();

        leafCount = 0;
        calcLeafCount(root);
        System.out.println("叶子结点个数:" + leafCount);
        leafCount = 0;
        calcLeafCount(root);
        System.out.println("叶子结点个数:" + leafCount);
        leafCount = 0;
        calcLeafCount(root);
        System.out.println("叶子结点个数:" + leafCount);
}

方法二:①空树就是0;②整棵树只有根结点的情况就是1③左子树中的叶子结点个数leftLeafCount + 右子树中的叶子结点个数rightLeafCount

private static int calcLeafCount2(TreeNode root){
        if(root == null){
            return 0;
        }

        if(root.left == null && root.right == null){
            return 1;
        }

        int leftLeafCount = calcLeafCount2(root.left);
        int rightLeafCount = calcLeafCount2(root.right);

        return leftLeafCount + rightLeafCount;
    }

public static void main(String[] args) {
        TreeNode root = buildTree();

        int count = 0;
        count = calcLeafCount2(root);
        System.out.println("叶子结点个数:" + count);
        count = calcLeafCount2(root);
        System.out.println("叶子结点个数:" + count);
        count = calcLeafCount2(root);
        System.out.println("叶子结点个数:" + count);
    }

2.7.3求给定二叉树第 k 层的结点个数

需要两个参数(树的根结点,k)k >= 1

比如求第k层的结点个数,可以转化为求左子树的第k - 1层结点个数 + 右子树第k - 1层结点个数,最终转化成左子树第一层结点的个数 + 右子树第一层结点的个数,只要这颗树不是空的,在k == 1时,结点个数一定是1。收敛到特殊情况下:树的结点越来越小,k的值也是越来越小的  root == null ->   0;        root != null && k ==1 ->   1。

public static int calcKLevelNodeCount(TreeNode root, int k) {
        if (root == null) {
            return 0;
        }

        if (k == 1) {
            return 1;
        }

        int leftCount = calcKLevelNodeCount(root.left, k - 1);
        int rightCount = calcKLevelNodeCount(root.right, k - 1);

        return leftCount + rightCount;
    }

public static void main(String[] args) {
        TreeNode root = buildTree();
        for (int i = 1; i <= 5; i++) {
            int count = calcKLevelNodeCount(root, i);
            System.out.println("第 " + i + " 层上一共有结点多少个: " + count);

            count = calcKLevelNodeCount(root, i);
            System.out.println("第 " + i + " 层上一共有结点多少个: " + count);

            count = calcKLevelNodeCount(root, i);
            System.out.println("第 " + i + " 层上一共有结点多少个: " + count);
            System.out.println("========================================");
        }
    }

2.7.4 求二叉树的高度

求左子树的高度和右子树的高度,再求他俩的最大值 + 1,就是整个二叉树的高度

pubilc static int calcHeight(TreeNode root){
    if(root == null){
        return 0;
    }

    int leftHeight = calcHeight(root.left);
    int rightHeight = calcHeight(root.right);
    
    return Integer.max(leftHeight,rightHeight) + 1;
}

2.7.5查找 val 所在结点,没有找到返回 null

public static TreeNode find(TreeNode root,int val){
    if(root == null){
        return null;
    }

    if(root.val == val){
        return root;
    }

    TreeNode node = find(root.left,val);
    if(node != null){
        return node;
    } else {
           return find(root.right,val);
    }
}

2.7.6前序遍历返回是List

public List<Integer> preorderTraversal(TreeNode root){
    List<Integer> list = new ArrayList<>();
    if(root != null){
        List<Integer> leftList = preorderTraversal(root.left);
        List<Intrger> rightList = preorderTraversal(root.right);

        list.add(root.val);
        list.addAll(leftList);
        list.addAll(rightList);
    }
    return list;
}

2.7.7 力扣100.相同的树

题目链接:力扣https://leetcode-cn.com/problems/same-tree/

题目描述:给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的

分析:整棵树 = 左子树 + 右子树。在两棵树都不为空树的情况下:根的值相等&&p左和q左 is same;根的值相等&&p右和q右 is same。

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null){
            return true;
        }

        if(p == null || q == null){
            return false;
        }

        return p.val == q.val && isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
    }
}

 2.7.8 力扣101.对称二叉树

题目链接:

101. 对称二叉树https://leetcode-cn.com/problems/symmetric-tree/

同 2.7.7力扣100.相同的树 这道题很相似,直接给出代码

class Solution {
    public boolean isSymmetric(TreeNode root) {
    if(root == null){
        return true;
        }
    return isMirror(root.left,root.right);

    }  
    
     private boolean isMirror(TreeNode p,TreeNode q){
        if(p == null && q == null){
            return true;
        }
        if(p == null || q == null){
            return false;
        }

        return p.val == q.val && isMirror(p.left,q.right) && isMirror(p.right,q.left);
    }

}

2.7.9 力扣572.另一棵树的子树

题目链接:572. 另一棵树的子树https://leetcode-cn.com/problems/subtree-of-another-tree/

分析:去以 root 为根的二叉树中,查找一个结点,条件是以这个结点为根的子树 和 subRoot 树相等。我们可以把 root 中每个结点作为根结点和subRoot进行比较,这样问题就转化成了查找判断两棵树是否相等

class Solution {
    // 判断两棵树是否相等
    private boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q == null) {
            return true;
        }

        if (p == null || q == null) {
            return false;
        }

        return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }

   // 查找
   private boolean find(TreeNode root, TreeNode subRoot) {
        if (root == null) {
            return false;
        }

        if (isSameTree(root, subRoot)) {
            return true;
        }

        if (find(root.left, subRoot)) {
            return true;
        }

        return find(root.right, subRoot);
    }

    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if (subRoot == null) {
            return true;
        }

        return find(root, subRoot);
    }
}

2.7.10 力扣110.平衡二叉树

题目链接:110. 平衡二叉树https://leetcode-cn.com/problems/balanced-binary-tree/

题目描述:

给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

分析

这棵树不是平衡二叉树,因为 左子树的高度为3;右子树的高度为1。两者差的绝对值大于1了,因此不是。

一棵树的构成是 根 + 左子树 + 右子树

一个二叉树,左右两个子树的高度差的绝对值不超过1 && 左子树满足平衡二叉树 && 右子树满足平衡二叉树

这里有两个递归,求高度是递归,判断是否平衡也是递归

class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root == null){
            return true;
        }
        int left = height(root.left);
        int right = height(root.right);
        int diff = left - right;

        if(diff < -1 || diff > 1){
            return false;
        }
        return isBalanced(root.left) && isBalanced(root.right);

    }

    // 求高度
    public int height(TreeNode root){
        if(root == null){
            return 0;
        }
        return Integer.max(height(root.left),height(root.right)) + 1;
    }
}

2.7.10 二叉树的层序遍历

层序遍历用队列

我们需要定义一个队列,队列中放的元素的类型是 TreeNode 类型。然后把根放进去,开启循环,当队列是 empty 时,循环结束。当队列不是空的时候,不断地从队列中取出元素,取出的元素就是我们下一个要遍历的结点,然后把这个结点的孩子也放入队列中(前提是该结点有孩子)

    public static void levelOrder(TreeNode root){
        if(root == null){
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            System.out.printf("%c ",node.val);
            if(node.left != null){
                queue.offer(node.left);
            }
            if(node.right != null){
                queue.offer(node.right);
            }
        }
        System.out.println();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值