数据结构复习笔记特别系列4 —— 二叉树

Posted Putarmor

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构复习笔记特别系列4 —— 二叉树相关的知识,希望对你有一定的参考价值。

1.树相关概念

树是一种非线性的数据结构,由n(n>=0)有限个节点组成一个具有层次关系的集合,这里的树是一棵倒挂形状的树。

  • 节点的度:一个节点含有子树的个数为该节点的度
  • 一颗树的度:树中最大节点的度称为树的度
  • 叶子节点:度为0的节点称为叶子节点
  • 双亲节点或父节点:一个节点含有子树,则称为这个节点为其子树根节点的父节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点
  • 根节点:一棵树中没有父节点的节点称为根节点
  • 节点的层次:从根节点开始定义,根为第一层,跟的子节点为第二层,以此类推。。。
  • 树的高度:该节点到叶子结点之间距离
  • 树的深度:该节点到这棵树根节点之间的距离
  • 非终端节点或分支节点:度- 不为0的节点
  • 兄弟节点:具有相同父节点的节点称为兄弟节点 堂兄弟节点:父亲节点在同一层的节点称为堂兄弟节点
  • 节点的祖先:从树的根到该节点所经分支上的所有节点
  • 节点的子孙:以某个节点为根的子树中任一节点都称为该节点的子孙
  • 森林:由m(m>=0)棵互不相交的树的集合称为森林

树的表现形式:

树的表现形式有很多,比如双亲表示法、孩子表示法、孩子兄弟表示法等,这里我们简单了解一下孩子兄弟表示法。

class Node{
	int value; //节点的值
	Node child; //孩子节点引用
	Node brother; //兄弟节点引用
}

如图所示,给一棵树,child代表的是孩子节点,brother表示兄弟节点。
在这里插入图片描述


2.二叉树基本概念

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

特点:
1.每个结点的度为2;
2.二叉树的子树有左右之分,子树的次序不能颠倒,二叉树是有序树;
3.二叉树基本形态有五种:空树、只有根结点的二叉树、结点只有左子树、结点只有右子树、节点的左右子树都存在。

特殊二叉树:

  • 满二叉树:一棵二叉树每一层结点数都达到最大值,这个树就是满二叉树;假设满二叉树的层数为k,该树的结点总数是2^k-1;
  • 完全二叉树:完全二叉树效率很高,它是由满二叉树引申出来的,对于深度为k,有n个结点的二叉树,当每一个结点与深度为k的满二叉树结点编号完全对应时,这棵树就称为完全二叉树。满二叉树是特殊的完全二叉树。
    在这里插入图片描述

在这里插入图片描述
二叉树性质此处不再赘述,以前的博客中讲了,这里我们看一道考察二叉树性质的习题:

题目:假设一棵完全二叉树中总共有1000个结点,则该二叉树中有多少个叶子结点?多个个非叶子结点?几个结点只有左孩子?几个结点只有右孩子?

这里我画图解析一下这道题:
在这里插入图片描述


3.二叉树的存储

二叉树的存储分为:顺序存储(一般针对于完全二叉树)和类似于链表的链式存储,二叉树的链式存储是通过一个个结点的引用连接起来的。常见的表示方法有二叉和三叉表示方法,我们一般用二叉去表示:

//二叉表示法
class Node{
	int val;   //数据域
	Node left;   //左孩子引用,一般代表左孩子为根的整棵左子树
	Node right;   //右孩子引用,一般代表右孩子为根的整棵右子树
}

4.二叉树遍历

遍历概念:沿着某一条搜索路径,依次对树中的每个结点有且仅做一次访问。如果N代表根结点,L代表根结点的左子树,R代表根结点的右子树,根据根结点遍历的先后顺序,将二叉树遍历分为三种方式:

  • 前序遍历NLR:先访问根结点,再访问左子树,再访问右子树
  • 中序遍历LNR:先访问根的左子树,再访问根结点,再访问根的右子树
  • 后序遍历LRN:先访问根的左子树,再访问根的右子树,再访问根结点

牛刀小试,写出下列这棵完全二叉树的前序遍历、中序遍历以及后序遍历的结果。

前序遍历:ABDEHCFG
在这里插入图片描述
中序遍历:DBEHAFCG
在这里插入图片描述
后序遍历:DHEBFGCA
在这里插入图片描述
5.二叉树习题汇总

二叉树操作习题:

/**
 * 二叉树的存储
 */
class BTNode{
    char val;   //结点数值
    BTNode left;   //结点左孩子引用
    BTNode right;   //结点右孩子引用

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

/**
 * 二叉树操作方法
 */
class BinaryTree{
    public BTNode createTree(){
        BTNode A = new BTNode('A');
        BTNode B = new BTNode('B');
        BTNode C = new BTNode('C');
        BTNode D = new BTNode('D');
        BTNode E = new BTNode('E');
        BTNode F = new BTNode('F');
        BTNode G = new BTNode('G');
        BTNode H = new BTNode('H');

        A.left = B;
        A.right = C;
        B.left = D;
        B.right = E;
        E.right = H;
        C.left = F;
        C.right = G;
        return A; //返回根结点A
    }

    //前序遍历
    public void preOrderTraversal(BTNode root){
        if(root == null) return ;
        System.out.print(root.val);
        inOrderTraversal(root.left);
        inOrderTraversal(root.right);
    }

    //中序遍历
    public void inOrderTraversal(BTNode root){
        if(root == null) return ;
        inOrderTraversal(root.left);
        System.out.print(root.val);
        inOrderTraversal(root.right);
    }

    //后序遍历
    public void posterOrderTraversal(BTNode root){
        if(root == null) return ;
        posterOrderTraversal(root.left);
        posterOrderTraversal(root.right);
        System.out.println(root.val);
    }

    //遍历思路求结点个数(根据前中后序遍历方式)
    static int size = 0;
    public void getSize(BTNode root){
        if(root == null) return ;
        size++;
        getSize(root.left);
        getSize(root.right);
    }

    //子问题思路求结点个数  左树结点个数 + 右树结点个数 + 1
    // *重点分析一下递归执行过程
    public int getSize1(BTNode root){
        if(root == null){
            return 0;
        }
        return getSize1(root.left) + getSize1(root.right) + 1;
    }

    //遍历思路求叶子结点个数
    static int leafSize = 0;
    public void getLeafSize(BTNode root){
        if(root == null) return ;
        if(root.left == null && root.right == null){
            leafSize++;
        }
        getLeafSize(root.left);
        getLeafSize(root.right);
    }

    //子问题思路求叶子结点个数   *重点关注一下
    public int getLeafSize1(BTNode root){
        if(root == null) return 0;
        if(root.left == null && root.right == null){
            return 1;
        }
        return getLeafSize1(root.left) + getLeafSize1(root.right) + 1;
    }

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

    //时间复杂度O(n)
    public int height1(BTNode root){
        if(root == null) return 0;
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);
        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    }
    
    //求第k层结点个数
    public int getKLevelSize(BTNode root, int k){
        if(root == null){
            return 0;
        }
        //k为1表明没有左右子树了,直接返回1
        if(k == 1){
            return 1;
        }
        return getKLevelSize(root.left,k-1) + getKLevelSize(root.right,k-1);
    }

    //查找结点  根据遍历方式查找!
    public BTNode findNode(BTNode root, int val){
        if(root == null){
            return null;
        }
        if(root.val == val){
            return root;
        }
        BTNode leftRet = findNode(root.left,val);
        if(leftRet != null) return leftRet;
        BTNode rightRet =  findNode(root.right,val);
        if(rightRet != null) return rightRet;
        //左边与右边都没找到结点,返回null
        return null;
    }

}

测试用例:

public class TestDemo1 {
    public static void main(String[] args) {
        BinaryTree binaryTree = new BinaryTree(); //创建BinaryTree对象
        BTNode root = binaryTree.createTree();  //构建二叉树
        System.out.print("前序遍历:");
        binaryTree.preOrderTraversal(root);
        System.out.println();
        System.out.print("中序遍历:");
        binaryTree.inOrderTraversal(root);
        System.out.println();
        System.out.print("后序遍历:");
        binaryTree.posterOrderTraversal(root);
        System.out.println();

        binaryTree.getSize(root);
        System.out.println("遍历思路求结点个数:"+BinaryTree.size);  //结点个数
        System.out.println("子问题思路求结点个数:"+binaryTree.getSize1(root));  //结点个数

        System.out.println("求第三层结点个数:"+binaryTree.getKLevelSize(root,3)); //第三层结点个数为4

        System.out.println("树的高度:"+binaryTree.height(root));
        //System.out.println(binaryTree.height1(root));

        System.out.println("查找结点:"+binaryTree.findNode(root,'F').val); //查找结点
    }
}

测试用例执行结果:
在这里插入图片描述
二叉树基础习题:

1.判断两棵树是不是相同的树

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

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
在这里插入图片描述
解决思路:子问题思路
先判断根,再递归左子树,然后递归右子树;当两棵树root都为null时,认为这两棵树是相同的;当一棵树root为null而另一棵不为null时,返回false;当两棵树root不为null但是root结点值不相等时返回false;此时对根的判断已经结束,然后递归左子树判断两棵树左子树是否相同,再递归右子树,判断两棵树右子树是否相同,当左右子树都相同时返回true表明两棵树相等,否则返回false表明两棵树不相等。

public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null){
            return true;
        }
        
        //当p和q有一个为空,另一个不为空时直接返回false
        if(p == null && q != null || p != null && q == null){
            return false;
        }

        //当p与q的值不相等时直接返回false;
        if(p.val != q.val){
            return false;
        }
        
        //判断左树和右树是否一致
        if(isSameTree(p.left,q.left) && isSameTree(p.right,q.right)){
            return true;
        } 
        return false;
    }

2.判断一棵树是不是另一棵树的子树

给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
在这里插入图片描述
思路:先判断两颗树s和t是否有一个为空,如果有返回false,然后判断两棵树是否为相同的树,如果不相等就去递归判断s左子树与t,左子树不相等就去递归判断s右子树和t。

public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root == null || subRoot == null){
            return false;
        }
        //先判断t树和s树是不是相等
        if(isSameTree(root,subRoot)){
            return true;
        }
        if(isSubtree(root.left,subRoot)){
            return true;
        }
        if(isSubtree(root.right,subRoot)){
            return true;
        }
        return false;
    }

    //判断两棵树是不是相同的树
    public boolean isSameTree(TreeNode p, TreeNode q){
        if(p == null && q == null){
            return true;
        }
        if(p == null || q == null){
            return false;
        }
        if(p.val != q.val){
            return false;
        }
        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
    }

3.对称二叉树

给定一个二叉树,检查它是否是镜像对称的。

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

    }

    //子问题求解
    //传入左子树 传入右子树
    public boolean isSymmetricChild(TreeNode leftTree, TreeNode rightTree){
        if(leftTree == null && rightTree == null){
            return true;
        }
        if(leftTree == null || rightTree == null){
            return false;
        }
        if(leftTree.val == rightTree.val){
            return isSymmetricChild(leftTree.left, rightTree.right) && 
            isSymmetricChild(leftTree.right, rightTree.left);
        }else{
            return false;
        }
    }

4.判断一棵树是否为平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
在这里插入图片描述
在这里插入图片描述
思想:子问题子问题子问题!!!先判断根结点左右子树高度之差是否小于等于1,再递归左子树和右子树。

时间复杂度:O(n平方)

public boolean isBalanced(TreeNode root) {
        if(root == null) return true;
        int leftLength = maxDepth(root.left);
        int rightLength = maxDepth(root.right);
        //子问题思路,先判断当前结点左右树高度差,再判断左子树和右子树
        return Math.abs(leftLength - rightLength) <= 1 && 
        isBalanced(root.left) && isBalanced(root.right);
    }

    //求二叉树最大高度
    public int maxDepth(TreeNode root){
        if(root == null){
            return 0;
        }
        return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;
    }

时间复杂度:O(n)

核心思想:从下边往上边走,每次判断结点左右子树高度绝对值之差,高度差大于1时直接返回false,小于等于1时继续向上判断。


    public int height(TreeNode root){
        if(root == null) return 0以上是关于数据结构复习笔记特别系列4 —— 二叉树的主要内容,如果未能解决你的问题,请参考以下文章

数据结构复习笔记——二叉树的常用接口的实现(c语言)

数据结构复习笔记——树的基本概念及结构

数据结构关于二叉树的先序遍历中序遍历及后续遍历复习笔记

期末复习笔记——树和二叉树

数据结构关于二叉树的先序遍历中序遍历及后续遍历复习笔记

Python数据结构系列☀️《树与二叉树-基础知识》——知识点讲解+代码实现☀️