Bootstrap

数据结构《二叉树》


一、树是什么?

1.1 树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
在这里插入图片描述
注意:树形结构中,子树之间不能有交集,否则就不是树形结构
在这里插入图片描述


1.2 树的基本术语

在这里插入图片描述
结点的度:一个结点含有子树的个数称为该结点的度; 如上图:A的度为2,B的度为3,D的度为0。

树的度:一棵树中,在所有结点中度的最大值称为树的度; 如上图:树的度为3。

叶子结点或终端结点:度为0的结点称为叶结点; 如上图:D,E,F,H节点为叶结点。

双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点,B是D、E、F的父节点。

孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点。

根结点:一棵树中,没有双亲结点的结点;如上图:A。

结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推。

树的高度或深度:树中结点的最大层次; 如上图:树的高度为4。

森林:由m(m>=0)棵互不相交的树组成的集合称为森林。
在这里插入图片描述
树有些像我们的文件存储,一个文件下有不同的文件目录,一层一层的。


二、二叉树

2.1 二叉树的概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 树为空
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

在这里插入图片描述


2.2 特殊的二叉树

在这里插入图片描述
满二叉树: 一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。如果一棵二叉树的层数为K,且结点总数是2^k - 1个 ,则它就是满二叉树。

完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

在这里插入图片描述


2.3 二叉树的性质

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

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

  3. 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1。
    在这里插入图片描述

  4. 具有n个结点的完全二叉树的深度k为log2(n+1)上取整

  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
    若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
    若2i+1<n,左孩子序号:2i+1,否则无左孩子
    若2i+2<n,右孩子序号:2i+2,否则无右孩子
    在这里插入图片描述


二叉树的存储有两种,一个是链式存储一个顺序存储,根据之前的学习我们不难理解这两种存储的区别,我们这里是用链式存储,便于理解和学习,更加直观。

三、二叉树的遍历

遍历的实现运用的是递归的思想

public static class MyTreeNode{
        int val;
        MyTreeNode left;
        MyTreeNode right;

        public MyTreeNode(int val) {
            this.val = val;
        }
    }
    private MyTreeNode root;

    //简单组建出一个二叉树便于使用和理解
    public MyTreeNode createBinaryTree(){
        MyTreeNode node1 = new MyTreeNode(1);
        MyTreeNode node2 = new MyTreeNode(2);
        MyTreeNode node3 = new MyTreeNode(3);
        MyTreeNode node4 = new MyTreeNode(4);
        MyTreeNode node5 = new MyTreeNode(5);
        MyTreeNode node6 = new MyTreeNode(6);
        root = node1;
        node1.left = node2;
        node1.right = node3;
        node2.left = node4;
        node2.right = node5;
        node3.left = node6;
        return root;
    }

在这里插入图片描述

3.1 深度优先遍历

从树的某个初始节点开始,沿着一条路径尽可能深地探索下去,直到无法继续或者达到目标节点,然后回溯到上一个未完全探索的分支点,继续探索其他分支,直到遍历完所有节点。

3.1.1 前序遍历

访问的顺序为—根,左,右

在我们向下遍历树的时候,在遇到这个节点时就将其打印,然后再向左遍历,左边遍历完,再向右遍历。
在这里插入图片描述

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

在这里插入图片描述


3.1.2 中序遍历

访问的顺序为—左,根,右

在我们向下遍历树的时候,在遇到这个节点时先不打印,然后向左遍历,左边遍历完,返回到这个节点时再打印然后向右遍历。

在这里插入图片描述

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

在这里插入图片描述


3.1.3 后序遍历

访问的顺序为—左,右,根

在我们向下遍历树的时候,在遇到这个节点时先不打印,然后向左遍历,左边遍历完,然后向右遍历最后右边返回到父亲节点时再打印父亲节点。

在这里插入图片描述

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

在这里插入图片描述


3.2 广度优先遍历

从树的某个初始节点开始,首先访问该节点的所有邻接节点(对于树来说就是子节点),然后再依次访问这些邻接节点的邻接节点,按照层次依次向外扩展,直到遍历完所有节点。

3.2.1 层序遍历

运用队列的知识,首先将根节点放入队列中,然后开始循环出队列的队头节点,出的同时打印它的val,并且判断是否有左右孩子,如果有就放入队列中。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

void levelOrder(MyTreeNode root){
        if(root == null){
            return;
        }
        Queue<MyTreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            MyTreeNode node = queue.poll();
            System.out.print(node.val+" ");
            if(node.left != null){
                queue.offer(node.left);
            }
            if(node.right != null){
                queue.offer(node.right);
            }

        }
    }

在这里插入图片描述

四、二叉树的基本操作

4.1 获取树中节点的个数

public void size(MyTreeNode root){
        if(root == null){
            return;
        }
        nodesize++;
        size(root.left);
        size(root.right);
    }

在这里插入图片描述


4.2 获取叶子节点的个数

// 获取叶子节点的个数
    public int getLeafNodeCount(MyTreeNode root){
        if (root == null){
            return 0;
        }
        if(root.right == null && root.left == null){
            return 1;
        }
        return getLeafNodeCount(root.left) + getLeafNodeCount(root.right);
    }

在这里插入图片描述


4.3 获取第K层节点的个数

// 获取第K层节点的个数
    public int getKLevelNodeCount(MyTreeNode root,int k){
        if(root == null){
            return 0;
        }
        if(k == 1){
            return 1;
        }
        return getKLevelNodeCount(root.left,k-1)+getKLevelNodeCount(root.right,k-1);
    }

在这里插入图片描述


4.4 获取二叉树的高度

// 获取二叉树的高度
    int getHeight(MyTreeNode root){
        if(root == null){
            return 0;
        }
        return Math.max(getHeight(root.left), getHeight(root.right))+1;
    }

在这里插入图片描述


4.5 检测值为value的元素是否存在

// 检测值为value的元素是否存在
    MyTreeNode find(MyTreeNode root, int val){
        if(root == null){
            return null;
        }
        if(root.val == val){
            return root;
        }
        MyTreeNode left = find(root.left,val);
        if(left != null){
            return left;
        }
        MyTreeNode right = find(root.right,val);
        if(right != null){
            return right;
        }
        return null;
    }

在这里插入图片描述


4.6 判断一棵树是不是完全二叉树

boolean isCompleteTree(MyTreeNode root){
        if(root == null){
            return true;
        }
        Queue<MyTreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            MyTreeNode node = queue.poll();
            if(node == null){
                break;
            }
            queue.offer(node.left);
            queue.offer(node.right);
        }
        while (!queue.isEmpty()){
            MyTreeNode node = queue.poll();
            if(node != null){
                return false;
            }
        }
        return true;

    }

在这里插入图片描述


五、二叉树的例题

例题1 检查两颗树是否相同
在这里插入图片描述

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

例题2 另一颗树的子树
这题和上面那题类似,只是我们要遍历那棵被主树,然后判断对应子树是否和要判断的树相同

//检查两颗树是否相同
    public boolean isSameTree(MyTreeNode p, MyTreeNode q) {
        if(p == null && q != null){
            return false;
        }
        if(p != null && q == null){
            return false;
        }
        if(p == null){
            return true;
        }
        if(p.val == q.val){
            isSameTree(p.left,q.left);
            return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
        }
        return false;
    }
    
    public boolean isSubtree(MyTreeNode root, MyTreeNode subRoot) {
        if(root == null){
            return false;
        }
        if(subRoot == null){
            return true;
        }
        if(isSameTree(root,subRoot)){
            return true;
        }
        if(isSubtree(root.left,subRoot)){
            return true;
        }
        if(isSubtree(root.right,subRoot)){
            return true;
        }
        return false;
    }

例题3 翻转二叉树
在这里插入图片描述

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;
    }

例题4 判断一颗二叉树是否是平衡二叉树。
在这里插入图片描述

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

例题5 对称二叉树
在这里插入图片描述

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

    }
    private boolean isSymmetricLeftAndRight(MyTreeNode rootLeftTree, MyTreeNode rootRightTree){
        if(rootLeftTree == null && rootRightTree != null){
            return false;
        }
        if(rootLeftTree != null && rootRightTree == null){
            return false;
        }
        if(rootLeftTree == null){
            return true;
        }
        if(rootLeftTree.val != rootRightTree.val){
            return false;
        }
        return isSymmetricLeftAndRight(rootLeftTree.left,rootRightTree.right)&&
                isSymmetricLeftAndRight(rootLeftTree.right,rootRightTree.left);

    }

例题6 二叉树的遍历

在这里插入图片描述

import java.util.Scanner;
import java.util.LinkedList;
import java.util.Queue;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
class TreeNode{
        char val;
        TreeNode left;
        TreeNode right;
        public TreeNode(char val) {
            this.val = val;
        }
    }
//这个类是为了确保字符串的下标,即在每次调用时,可以刷新i的值,
//同时可以让i的值在每次调用后正常使用,如果直接用public static int i的话main方法中
//while (in.hasNextLine()) { // 注意 while 处理多个 case
//        }无法刷新i的值,相当于全局变量,会出现上一次我i到了5下标,这次还是从5下标开始的问题。
class Cur{
    int i;
}
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextLine()) { // 注意 while 处理多个 case
            Cur cur = new Cur();
            String str = in.nextLine();
            TreeNode treeNode = createTree(str,cur);
            inOrder(treeNode);
        }
    }

    private static TreeNode createTree(String str,Cur cur){
        TreeNode root = null;
        if(str.charAt(cur.i) != '#'){
            root = new TreeNode(str.charAt(cur.i));
            cur.i++;
            root.left = createTree(str,cur);
            root.right = createTree(str,cur);
        }else{
            cur.i++;
        }
        return root;
    }


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

例题7 二叉树的层序遍历
在这里插入图片描述

public List<List<Integer>> levelOrder2(MyTreeNode root) {

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

            listout.add(listin);
        }
        return listout;
    }

例题8 二叉树的最近公共祖先
在这里插入图片描述

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null){
            return null;
        }
        if(root == p || root == q){
            return root;
        }
        TreeNode leftNode = lowestCommonAncestor(root.left,p,q);
        TreeNode rightNode = lowestCommonAncestor(root.right,p,q);
        if(leftNode == null && rightNode == null){
            return null;
        }
        if(leftNode == null){
            return rightNode;
        }
        if(rightNode == null){
            return leftNode;
        }
        return root;
    }

例题9 从前序与中序遍历序列构造二叉树
在这里插入图片描述

class Solution {
    int preIndex;//定义一个成员变量,在类中充当全局变量来记录前序遍历的位置
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTreeMethod(preorder,inorder,0,inorder.length -1);
    }
    private TreeNode buildTreeMethod(int[] preorder,int[] inorder,int begin,int end){
        if(begin > end){//如果开始位置大于结束位置说明是空的
            return null;
        }
        TreeNode root = new TreeNode(preorder[preIndex]);
        
        //rootIndex是为了标中序遍历中对应的前序遍历的节点,从而分数组的左右部分
        int rootIndex = findTheNode(inorder,begin,end,preorder[preIndex]);
       
        preIndex++;//让前序遍历的数组往后走
        root.left = buildTreeMethod(preorder,inorder,begin,rootIndex-1);
        root.right = buildTreeMethod(preorder,inorder,rootIndex+1,end);
        return root;
    }
    private int findTheNode(int[] inorder,int begin,int end,int val){
        for(int i = begin;i <= end;i++ ){
            if(val == inorder[i]){
                return i;
            }
        }
        return -1;
    }
}

例题10 从中序与后序遍历序列构造二叉树
在这里插入图片描述

class Solution {
    public int postIndex;
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        postIndex = postorder.length-1;
        return buildTreeMethod(inorder,postorder,0,inorder.length-1);
    }
    private TreeNode buildTreeMethod(int[] inorder, int[] postorder,int begin,int end){
        if(begin > end){
            return null;
        }
        
        TreeNode root = new TreeNode(postorder[postIndex]);
        int rootIndex = findTheNode(inorder,begin,end,postorder[postIndex]);
        postIndex--;
        root.right = buildTreeMethod(inorder,postorder,rootIndex + 1,end);
        root.left = buildTreeMethod(inorder,postorder,begin,rootIndex -1);
        return root;
    }
    private int findTheNode(int[] inorder,int begin,int end,int val){
        for(int i = begin ;i <= end;i++) {
            if(inorder[i] == val) {
                return i;
            }
        }
        return -1;
    }
}

例题11 根据二叉树创建字符串
在这里插入图片描述

class Solution {
    public String tree2str(TreeNode root) {
        if(root == null){
            return null;
        }
        StringBuffer stringBuffer = new StringBuffer();
        return tree2strMethod(root,stringBuffer).toString();
    }
    private StringBuffer tree2strMethod(TreeNode root,StringBuffer stringBuffer){
        stringBuffer.append(root.val);
        if(root.left != null){
            stringBuffer.append('(');
            tree2strMethod(root.left,stringBuffer);
            stringBuffer.append(')');
        }else{
            if(root.right != null){
                stringBuffer.append("()");
            }else{
                return stringBuffer;
            }
        }
        if(root.right != null){
            stringBuffer.append('(');
            tree2strMethod(root.right,stringBuffer);
            stringBuffer.append(')');
        }else{
            return stringBuffer;
        }
        return stringBuffer;
    }
}

这后面的三个题很简单,就是将前序后序中序遍历放入一个顺序表中
例题12 二叉树前序非递归遍历实现

    List<Integer> list = new ArrayList<>();
    public List<Integer> preorderTraversal(TreeNode root) {
        if(root == null){
            return list;
        }
        list.add(root.val);
        preorderTraversal(root.left);
        preorderTraversal(root.right);
        return list;
    }

例题13 二叉树中序非递归遍历实现

class Solution {
    List<Integer> list = new ArrayList<>();
    public List<Integer> inorderTraversal(TreeNode root) {
        if(root == null){
            return list;
        }
        inorderTraversal(root.left);
        list.add(root.val);
        inorderTraversal(root.right);
        return list;
    }
}

例题14 二叉树后序非递归遍历实现

List<Integer> list = new ArrayList<>();
    public List<Integer> inorderTraversal(TreeNode root) {
        if(root == null){
            return list;
        }
        inorderTraversal(root.left);
        list.add(root.val);
        inorderTraversal(root.right);
        return list;
    }

总结

本篇文章介绍了有关数据结构中《二叉树》相关的内容,包括什么是树,什么是二叉树,二叉树的遍历,以及遍历的模拟实现,还有关于二叉树的例题,如果有什么不正确、不严谨的地方,还望指正,谢谢大家!

;