认识一下二叉树及其常见的一些操作和练习

认识二叉树

树的概念❔

​ 树是一种非线性数据结构,它是由n(n>=0)个有限节点组成的一个具有层次关系的集合。之所以叫做树,是因为从外观上来看这个结构,其很像一棵树,并且根还在最上面。


关于树的一些基本概念☑️

  1. 节点的度:一个几点向下连了几条边,那这个节点的度就是几
  2. 树的度:一棵树中具有最大度的节点的度数就是整棵树的度
  3. 叶子节点或终端节点:如果一个节点没有孩子,或者说度为0,那这个节点就是叶子
  4. 双亲节点:刚才说的度是一个节点往下连接别的节点,这里的双亲结点就是一个节点往上的第一个节点,就是此节点的双亲节点
  5. 孩子节点:与双亲节点相反呗
  6. 根节点:顾名思义:树根,树根只能生孩子,没有双亲
  7. 节点的层次:根所在层为第1层,根的孩子节点为第二层,一次类推
  8. 树的高度或者深度:一棵树的最大层次就是整棵树的高度
  9. 非终端节点或分支节点:度不为零的节点
  10. 兄弟节点:同一个双亲生下来的节点间叫兄弟节点
  11. 堂兄弟节点:爷爷生了俩孩子,这俩孩子再生俩孙子,这俩孙子就是堂兄弟
  12. 节点的祖先:整棵树的根
  13. 子孙:一个节点只要有孩子甚至孙子。。,那这个节点的后代都是它的子孙
  14. 森林:有m(m>=0)棵互不相交的树的集合,一片森林里可以有很多树,也可有0棵树

树的表示方法🥚

  1. 常见的如孩子兄弟表示法

    class Node{
        int val;
        Node firstChild;
        Node nextBrother;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uBbcX8lX-1645867245859)(C:UsersLebronHardenAppDataRoamingTyporatypora-user-imagesimage-20220226122644542.png)]


二叉树🤜

概念🚡

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

特点😋

  1. 每个节点最多有两颗子树,即二叉树中的每个节点的度最大为2
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒,因此二叉树是有序树

特殊的二叉树💛

  1. 满二叉树:一个二叉树,只不过每一层的节点都达到最大值,如果层次为k,则这一层的最大节点数:2(k-1),很简单的明白:层数为k的满二叉树的总结点数:2k-1个
  2. 完全二叉树:满二叉树缺了右下角若干个节点就是完全二叉树,若干可以为0,即满二叉树是一种特殊的完全二叉树

二叉树的性质🗂

  1. 整棵树的根若层次为1,则二叉树的第k层的最大节点数:2^(k-1)个

  2. 由1可以推导出,一棵k层的二叉树的总的最大节点数为2^k-1个

  3. 任何一棵二叉树,度为2的节点数总是比叶子节点数多1个

  4. 具有n个节点的二叉树的深度为log2(n+1)往上取整

  5. 重要(该性质常用于堆内)👹

    按层序遍历的顺序从0开始给二叉树的节点依次标记序号,对于序号为i的节点:

    1. 若这个节点有双亲,则双亲的标号为:(i-1)/2
    2. 若这个节点有左孩子,则左孩子的序号为:2*i+1
    3. 若这个节点有右孩子,则右孩子的序号为:2*i+2

对3推导:

二叉树的总结点数为n,度为0的节点数为N0,度为1的节点数为N1,度为2的节点数为N2

则:n=N0+N1+N2

因为n个节点的二叉树,具有n-1条边(因为根上面没有边)

则:n-1=N1+2*N2

对上述两个式子计算可得:N2=N0+1,证毕


对4的推导:

层数定死了的话,完全二叉树可以添加若干个节点使自身变成一棵满二叉树,但此时的整棵树的深度并没有发生变化,之所以往满二叉树作改变,是因为满二叉树的总结点数量有现成的公式:2^k-1(k就是整棵树的深度)

∴2^k-1=n

∴k=log2(n+1),证毕


练习:

具有2n个节点的完全二叉树的叶子有多少个?

解:设度为0的节点个数为n0,度为1的节点个数为n1,度为2的节点个数为n2

∴2n=n0+n1+n2

此处n1恒等于1,因为1要和第一层的根加起来组成偶数才能满足整个树的节点个数为偶数

即:2n=n0+n2+1

由∵n0=n2+1

∴2n=n0+n0-1+1=2n0 故:n0=n


自己实现一个简单的二叉树(用最常见的孩子表示法)🥇

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

    public BTNode(int val) {
        this.val = val;
    }
}
public class MyBinaryTree {
    public BTNode root;
    
    public BTNode createTree(){
        BTNode node1=new BTNode(1);
        BTNode node2=new BTNode(2);
        BTNode node3=new BTNode(3);
        BTNode node4=new BTNode(4);
        BTNode node5=new BTNode(5);
        
        //连接
        node1.left=node2;
        node1.right=node3;
        node2.left=node4;
        node2.right=node5;
        
        root=node1;
        
        return root;
    }
}

二叉树的遍历🉑

所有二叉树相关的题目都是需要通过某种遍历来实现

  1. 遍历方式:前序、中序、后序遍历
  2. 前序遍历(Preorder Traversal):根左右为序
  3. 中序遍历(Inorder Traversal):左根右为序
  4. 后序遍历(Postorder Traversal):左右根为序

前序遍历🛰

如上图:前序遍历打印:123456

代码:

public void preOrder(BTNode root){
    if(root==null){
        return;
    }
    System.out.print(root.val+" ");
    preOrder(root.left);
    preOrder(root.right);
}

中序遍历🗡

上图中序遍历结果:321546

代码:

public void inOrder(BTNode root){
    if(root==null){
        return;
    }
    inOrder(root.left);
    System.out.print(root.val+" ");
    inOrder(root.right);
}

后序遍历🚅

上图后序遍历结果:325641

代码:

public void postOrder(BTNode root){
    if(root==null){
        return;
    }
    inOrder(root.left);
    inOrder(root.right);
    System.out.print(root.val+" ");
}

力扣有前中后序遍历相关的例题,见我的力扣博客


层序遍历🥂

从第一层往下,从左往右,依次遍历的方式就叫层序遍历

代码实现的时候,我们可以借助于队列,先将root入队,队列不为空时:出队一个元素,对该元素的左右孩子进行检查,若不为空则入队,为空则不如队,并且每次弹出的元素都用另外一个线性结构存一下,最后按顺序把线性结构里的元素注意打印就是层序遍历的结果

代码:

public void levelOrder(BTNode root){
    if(root==null) return;
    Queue<BTNode> queue=new LinkedList<>();
    Queue<BTNode> ans=new LinkedList<>();

    queue.offer(root);
    while(!queue.isEmpty()){
        BTNode front=queue.poll();//记录好,等下还要入队到ans里头呢
        if(front.left!=null) queue.offer(front.left);
        if(front.right!=null) queue.offer(front.left);

        ans.offer(front);
    }

    //打印ans
    while(!ans.isEmpty()){
        BTNode front=ans.poll();
        System.out.print(front.val+" ");
    }
}

只知道前中后序的其中两个不可以得到唯一的二叉树


二叉树的一些基本操作🎠

  1. 获取树的节点个数

    public int size(BTNode root){
        if(root==null){
            return 0;
        }
        return 1+size(root.left)+size(root.right);
    }
    
  2. 获取叶子的个数

    public int leafCount(BTNode root){
        if(root==null){
            return 0;
        }
        //root!=null
        if(root.left==null&&root.right==null){
            return 1;
        }
        return leafCount(root.left)+leafCount(root.right);
    }
    
  3. 第k层节点个数

    public int getKLevelNodeCount(BTNode 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);
    }
    
  4. 获取一棵二叉树的高度

    public int height(BTNode root){
        if(root==null){
            return 0;
        }
        //当前的root不为null,至少至少能为当前这棵树的高度贡献1
        return Math.max(height(root.left),height(root.right))+1;
    }
    
  5. 查找二叉树中是否包含关键字key

    public BTNode find(BTNode root,int key){
        if(root==null) return null;
        if(root.val==key) return root;
    
        BTNode left=find(root.left,key);
        if(left!=null) return left;
    
        BTNode right=find(root.right,key);
        if(right!=null) return right;
    
        return null;
    }//根左右为序进行遍历的
    
  6. 判断一棵树是不是完全二叉树

    ​ 可以借助队列,把root入队,再弹出,只要弹出的元素不为null,我们就将root.left和root.right都入队,只要某一次出队的元素是null,我们就停止迭代,检查队列中是否都是null,只要存在不是null的元素,就说明这棵树不是完全二叉树,可以画个图理解一下这背后的逻辑。(是不是发现了入进去的元素恰好就是层序遍历的顺序将节点逐一入队的?那如果都弹出null了,队列里还有不是null的元素,是不是就说明这棵树不是完全二叉树了呢?)

    代码:

    public boolean isCompleteTree(BTNode root){
        if(root==null) return true;//空树默认是吧,这个不是最重要的
        Queue<BTNode> queue=new LinkedList<>();
        queue.offer(root);
        while(queue.peek()!=null){
            BTNode front=queue.poll();
            queue.offer(front.left);
            queue.offer(front.right);
        }
        while(!queue.isEmpty()){
            BTNode front=queue.poll();
            if(front!=null){
                return false;
            }
        }
        return true;
    }
    
  7. 检查两棵树是否相同(力扣100)

    递归最重要的就是看终止条件:本题的话两棵树要想相同,首先结构得相同,其次节点的值相同才算完成一个节点之间的比较。最后,遍历到叶子往后了,可不敢影响我们的结果。

    class Solution {
        public boolean isSameTree(TreeNode p, TreeNode q) {
            if((p==null&&q!=null)||(p!=null&&q==null)) return false;//结构都不一样,肯定不对
            //要么都为空,要么都不为空
            if(p==null&&q==null){
                return true;//叶子往后可不敢影响结果
            }
            //都不为空的情况
            if(p.val!=q.val) return false;
    
            //上面这么多终止条件都没拦住,那就继续往下遍历呗
            return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
        }
    }
    
  8. 另一棵树的子树(力扣572)

    因为刚才已经学会了如何判断两棵树是不是相同的,我们就可以基于此,写这道题

    class Solution {
        private boolean isSameTree(TreeNode p,TreeNode q){
            if((p==null&&q!=null)||(p!=null&&q==null)){
                return false;
            }
            if(p==null&q==null) return true;
            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) return false;
            if(isSameTree(root,subRoot)) return true;
            return isSubtree(root.left,subRoot)||isSubtree(root.right,subRoot);
        }//本质还是根左右为序进行遍历
    }
    
  9. 平衡二叉树(力扣110)

    class Solution {
        private int height(TreeNode root){
            if(root==null) return 0;
            return Math.max(height(root.left),height(root.right))+1;
        }
        public boolean isBalanced(TreeNode root) {
            if(root==null) return true;
    
            if(height(root.left)-height(root.right)>1) return false;
            if(height(root.right)-height(root.left)>1) return false;
    
            return isBalanced(root.left)&&isBalanced(root.right);
        }
    }
    

    对本题提供一个巧妙的解法:在算树高的时候就可以判断这棵树是不是平衡二叉树了,思想就是,当前树根的左右子树只有平衡的时候我才正常的去求当前树的高度,否则我就返回一个-1,一旦某个局部子树不平衡了,将会导致整棵树的高度都是负数,那我们就可以很轻松的知道一棵树是不是平衡二叉树了呀。

    class Solution {
        private int height(TreeNode root){
            if(root==null) return 0;
            int leftHeght=height(root.left);
            int rightHeight=height(root.right);
    
            if(leftHeght>=0&&rightHeight>=0&&Math.abs(leftHeght-rightHeight)<=1){
                return Math.max(leftHeght,rightHeight)+1;
            }else{
                return -1;
            }
        }
        public boolean isBalanced(TreeNode root) {
            if(height(root)<0){
                return false;
            }
            return true;
        }
    }
    
  10. 对称二叉树(力扣101)

    可以假想是两棵一模一样的树进行比较,只不过比较位置有所讲究

    class Solution {
        private boolean isSymmetricChild(TreeNode p,TreeNode q){
            if((p==null&&q!=null)||(p!=null&&q==null)) return false;//结构都不一样还比较个毛线
            //能到这说明要么都为空,要么都不为空
            if(p==null&&q==null) return true;//这种情况不需进一步进行节点值的判断
            //都不为空
            if(p.val!=q.val) return false;
    
            //上述是就一个节点的判断
            return isSymmetricChild(p.left,q.right)&&isSymmetricChild(p.right,q.left);
        }
        public boolean isSymmetric(TreeNode root) {
            if(root==null) return true;
    
            return isSymmetricChild(root.left,root.right);
        }
    }
    
    

前面提到了层序遍历,要知道层序遍历可以干很多事,比如阿里巴巴考过的一道题:给出一棵二叉树的左视图,左视图的意思是:将每一层的第一个节点作存储;又如求一棵树的宽度,意思就是说一棵树的某一层具有的节点数比这棵树的其他层的节点数都要多,那这一层的节点数量就是这棵树的宽度

二叉树的继续学习🔐

见我的力扣专栏关于二叉树的题目。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>