JAVADS - 二叉树

目录

1. 树的概念

2.二叉树


1. 树的概念

        1.1 概念
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的

        1.2 数的一些重要概念

①. 度 : 该节点的子树个数

②. 叶子结点或终端结点:度为0的结点称为叶结点

③. 树的高度或深度:树中结点的最大层次

④. 非终端结点或分支结点:度不为0的结点

⑤. 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点

2.二叉树

        2.1 二叉树的概念:

        一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

上图就是一个二叉树 

        2.2 俩种特殊的二叉树

1.完全二叉树

        简而言之就是向左靠齐 没有缺失

2.满二叉树

        每层的结点数都达到最大值

         2.3 二叉树的性质

1,非空二叉树的第 i 层 有2^(i - 1)的节点

2,深度为k的二叉树 最大节点数是 2^k - 1

3,若叶节点个数为 n0 那么 n0 = n2 + 1

4,有n个节点的完全二叉树的深度k 为 log(n + 1)向上取整(log底为2)利用性质2推导

1,一个具有767个节点的完全二叉树,其叶子节点个数为()
A 383
B 384
C 385
D 386

答案:完全二叉树 没有度为1的节点 那么767 = n0 + n2 又因为 n0 = n2 + 1;

所以 答案是 B 

2,.一棵完全二叉树的节点数为531个,那么这棵树的高度为( )
A 11
B 10
C 8
D 12

利用性质4 答案可知是


2.4 二叉树的存储
        二叉树的存储结构分为:顺序存储和类似于链表的链式存储。

        我们先学习链式存储 且使用的是孩子表示法

2.5 二叉树的基本操作

        先建立一个二叉树

代码如下:

  利用静态内部类 给树定义        creatTree是建立一个二叉树

public class MyTree {
    static class TreeNode{
        public char val ;
        public TreeNode left ;
        public TreeNode right;

        public TreeNode(char val) {
            this.val = val;
        }
    }

    public TreeNode createTree() {
        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');

        A.left = B;
        A.right = C;
        B.left = D;
        B.right = E;
        C.left = F;
        C.right = G;
        E.right = H;

        return A;
    }
}

2.5.2 二叉树的遍历

        二叉树遍历可以大致分为: 前序遍历 中序遍历 后序遍历 层序遍历

我们先来学习前中后:

NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点--->根的左子树--->根的右子树。
LNR:中序遍历(Inorder Traversal)——根的左子树--->根节点--->根的右子树。
LRN:后序遍历(Postorder Traversal)——根的左子树--->根的右子树--->根节点。

大家可以通过根结点的访问顺序来记忆

层序遍历就是从上到下 从左到右 依次遍历!

我们来通过代码来实现此遍历:

//前序遍历 利用了遍历思想

List<Character> cur = new ArrayList<>();
    public List<Character> preoderTraversal(TreeNode root){
        if(root == null){
            return null;
        }
        cur.add(root.val);
        preoderTraversal(root.left);
        preoderTraversal(root.right);
        return cur;
    }

 在此基础上我们进阶一下,利用子问题思想来解决 前序遍历

public List<Character> preoderTraversal1(TreeNode root){
        List<Character> ret = new ArrayList<>();
        if(root == null){
            return ret;
        }
        ret.add(root.val);

        List<Character> leftTree = preoderTraversal1(root.left);
        ret.addAll(leftTree);

        List<Character> rightTree = preoderTraversal1(root.right);
        ret.addAll(rightTree);

        return ret;
   }

我们通过画图来理解一下这个子问题的思想


这俩种思想我们大致了解了,那么我们再通过求叶子节点来训练这俩种思想

public static int size = 0;
    //遍历思想
    public int getLeafNodeCount(TreeNode root){
        if(root == null){
            return 0;
        }
        if(root.left == null && root.right == null){
            size ++ ;
        }
        getLeafNodeCount(root.left);
        getLeafNodeCount(root.right);
        return size;
    }

 这是遍历思想,将每一个节点全部遍历一遍 来size++

public int getLeafNodeCount2(TreeNode root){
        if(root == null){
            return 0;
        }
        if(root.left == null && root.right == null){
            return 1;
        }
        return getLeafNodeCount2(root.left) +
                getLeafNodeCount2(root.right) ;
    }

子问题思想,将每个树分为左右子树


再看这一题 求k层的节点个数

我们先讲思路,如果想求k层的节点,我们就可以通过每次递归将k-1,直到k == 1的时候再return1

代码如下:

public int getKLevelNodeCount(TreeNode root,int k){
        if(root == null){
            return 0;
        }
        if(k == 1){
            return 1;
        }
        return getKLevelNodeCount(root.left,k-1) + getKLevelNodeCount(root.right,k-1);
    }

再整一个~

求树的高度:

这题我们可以利用子问题思想,将左右子树的节点个数进行比较,return较大的数

代码如下

public int getHeight(TreeNode root){
        if(root == null){
            return 0;
        }
        int leftSize = getHeight(root.left);
        int rightSize = getHeight(root.right);
        return leftSize > rightSize ? leftSize + 1 : rightSize + 1;
    }

这道题需要注意的是 不能在return语句中使用递归,因为会发生重复递归,在做oj题的时候可能会出现超时问题!


检测value值是否存在

public TreeNode find(TreeNode root,int val){
        if(root == null){
            return null;
        }
        if(root.val == val){
            return root;
        }
        TreeNode ret1 = find(root.left,val);
        if(ret1 != null){
            return ret1;
        }
        TreeNode ret2 = find(root.right,val);
        if(ret2 != null){
            return ret2;
        }
        return null;
    }


100. 相同的树 - 力扣(LeetCode)

先讲思路:判断俩个二叉树是否相同,先判断 是否都是空树,都是空树的话 返回true,接着判断俩个树是否结构都相同?真返回true,假返回false。到下一步就来判断其val值是否相同!

代码如下:

public boolean isSameTree(TreeNode p, TreeNode q){
        if(p == null && q == null){
            return true;
        }
        if(p != null && q == null || p == null && q != null){
            return false;
        }
        if(p.val != q.val){
            return false;
        }
        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
    }

572. 另一棵树的子树 - 力扣(LeetCode)

思路:我们依旧可以利用子问题思路,先将其分为左子树与右子树,再分别判断是否相等。

public boolean isSameTree(TreeNode p, TreeNode q){
        if(p == null && q == null){
            return true;
        }
        if(p != null && q == null || p == null && q != null){
            return false;
        }
        if(p.val != q.val){
            return false;
        }
        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
    }

    public boolean isSubtree(TreeNode root, TreeNode subRoot){
        if(root == null || subRoot == null){
            return false;
        }
        if(isSameTree(root,subRoot)) return true;
        if(isSubtree(root.left,subRoot)) return true;
        if(isSubtree(root.right,subRoot)) return true;
        return false;
    }

226. 翻转二叉树 - 力扣(LeetCode)

思路:将每一层的子树翻转

public TreeNode invertTree(TreeNode root){
        if(root == null){
            return null;
        }
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;
        invertTree(root.left);
        invertTree(root.right);
        return root;
    }

这一道题比较简单哈~


110. 平衡二叉树 - 力扣(LeetCode)

先讲思路:先判断根节点是否平衡,在判断左右子树是否平衡

但这里有缺陷,时间复杂度是O(N^2)

public int maxDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        int leftTree = maxDepth(root.left);
        int rightTree = maxDepth(root.right);
        return leftTree > rightTree ? 
                leftTree + 1 : 
                    rightTree + 1;
    }

    public boolean isBalanced(TreeNode root) {
        if(root == null){
            return true;
        }
        int leftH = maxDepth(root.left);
        int rightH = maxDepth(root.right);
        return Math.abs(leftH - rightH) < 2 && 
                isBalanced(root.left) && 
                isBalanced(root.right);
    }

我们来写个时间复杂度是O(N) 的代码:

简而言之 在遍历的时候就判断是否平衡

public int maxDepth2(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftH = maxDepth2(root.left);
        int rightH = maxDepth2(root.right);
        if(Math.abs(leftH - rightH) <= 1 && leftH >= 0 && rightH >= 0){
            return Math.max(leftH,rightH) + 1;
        }else{
            return -1;
        }
    }

    public boolean isBalanced2(TreeNode root) {
        if(root == null){
            return true;
        }
        return maxDepth2(root) >= 0;
    }

101. 对称二叉树 - 力扣(LeetCode)

思路:子问题思想

public boolean isSymmetric(TreeNode root) {
        if(root == null){
            return true;
        }
        return isSymmetricChild(root.left,root.right);
    }
    private boolean isSymmetricChild(TreeNode leftTree,TreeNode rightTree){
        if(leftTree == null && rightTree == null){
            return true;
        }
        if(leftTree == null){
            return false;
        }
        if(leftTree != null && rightTree == null || 
                leftTree != null && rightTree == null){
            return false;
        }
        if(leftTree.val != rightTree.val){
            return false;
        }
        return isSymmetricChild(leftTree.left,rightTree.right) && 
                isSymmetricChild(leftTree.right,rightTree.left);
    }

·102. 二叉树的层序遍历 - 力扣(LeetCode)

public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> list = new ArrayList<>();
        if(root == null){
            return list;
        }
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            List<Integer> tmp = new LinkedList<>();
            int size = queue.size();
            while(size > 0) {
                TreeNode cur = queue.poll();
                size -- ;
                tmp.add(cur.val);
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
            list.add(tmp);
        }
        return list;
    }

思路:

第一次先将root放到queue中

list用于返回        tmp用于记录当前层的元素

然后通过记录队列queue的size 每次tmp记录-- 来实现当前层的元素记录!


判断一个树是否是完全二叉树?

思路:这道题思路有点难,其关键在于其思想:无论cur的左右节点是否为空 都放入队列!

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();
            queue.offer(cur.left);
            queue.offer(cur.right);
            if(cur == null){
                break;
            }
        }
        while(!queue.isEmpty()){
            TreeNode cur = queue.poll();
            if(cur != null){
                return false;
            }
        }
        return true;
    }

二叉树遍历_牛客题霸_牛客网 (nowcoder.com)

该题意是通过前序遍历,来建立一个二叉树,并将其打印

public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextLine()) { // 注意 while 处理多个 case
            String str = in.nextLine();
            TreeNode root = creatTree(str);
            inorder(root);
        }
    }

    static class TreeNode {
        public char val ;
        public TreeNode left ;
        public TreeNode right;

        public TreeNode(char val) {
            this.val = val;
        }
    }

    private static int i = 0;
    public static TreeNode creatTree(String str){
        TreeNode root = null;
        
        if(str == null){
            return null;
        }
        char ch = str.charAt(i);
        if(ch != '#'){
            root = new TreeNode(ch);
            i++;
            root.left = creatTree(str);
            root.right = creatTree(str);
        }else{
            i++;
        }
        return root;
    }

    private static void inorder(TreeNode root){
        if(root == null){
            return;
        }
        inorder(root.left);
        System.out.print(root.val + " ");
        inorder(root.right);
    }

236. 二叉树的最近公共祖先 - 力扣(LeetCode)

在getPath()函数中的思路是这样的:

        先判断root,subRoot是否是空的,然后先建立一个栈,将第一个root放进栈中,在判断root是否是目标subRoot,再进行递归,这里的递归意思是如果是返回的是ture就代表,找到了目标subRoot,如果找到了进入if的语句return true;如果左右俩个都没找到,就将该节点pop出去,return false!

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || p == null || q == null){
            return null;
        }
        Stack<TreeNode> s1 = new Stack<>();
        Stack<TreeNode> s2 = new Stack<>();
        getPath(root,p,s1);
        getPath(root,q,s2);
        int size1 = s1.size();
        int size2 = s2.size();
        if(size1 > size2){
            int size = size1 - size2;
            while(size > 0){
                s1.pop();
                size--;
            }
        }else{
            int size = size2 - size1;
            while(size > 0){
                s2.pop();
                size--;
            }
        }
        while(!s1.empty()){
            if(s1.peek() == s2.peek()){
                break;
            }else{
                s1.pop();
                s2.pop();
            }
        }
        return s1.peek();
    }

    private boolean getPath(TreeNode root, TreeNode subRoot, Stack<TreeNode> stack){
        if(root == null || subRoot == null){
            return false;
        }
        stack.push(root);
        if(root == subRoot){
            return true;
        }
        if(getPath(root.left,subRoot,stack)){
            return true;
        }
        if(getPath(root.right,subRoot,stack)){
            return true;
        }
        stack.pop();
        return false;
    }

第二种思路:

将其分为三种情况:

1.p或q有一个在root上,直接返回root

2.p q 在俩侧,递归俩次,返回root

3.在一侧,再一次递归,如果找到了,那么就返回第一次的TreeNode

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || p == null || q == null){
            return null;
        }
        if(root == p || root == q){
            return root;
        }
        TreeNode leftTree = lowestCommonAncestor(root.left,p,q);
        TreeNode rightTree = lowestCommonAncestor(root.right,p,q);
        if(leftTree != null && rightTree != null){
            return root;
        }
        TreeNode Tree = null;
        if(leftTree == null && rightTree != null){
             Tree = lowestCommonAncestor(rightTree,p,q);
            
        }
        if(rightTree == null && leftTree != null){
             Tree = lowestCommonAncestor(leftTree,p,q);
        }
        return Tree;
    }

二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)

TreeNode prev = null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null){
            return null;
        }
        convertChile(pRootOfTree);
        //这个时候已经连起来了,下一步要找头节点
        TreeNode head = null;
        while(pRootOfTree.left != null){
            pRootOfTree = pRootOfTree.left;
        }
        head = pRootOfTree;
        return head;
    }
    
    public void convertChile(TreeNode root){
        if(root == null){
            return;
        }
        convertChile(root.left);
        root.left = prev;
        if(prev != null){
            prev.right = root;
        }
        prev = root;
        convertChile(root.right);
    }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值