【数据结构六】图文结合详解二叉树(五千字)

二叉树

树是一种非线性 的数据结构,它是由n个结点组成的具有层次关系的集合,把他叫做树是因为它的根朝上,叶子朝下,看起来像一颗倒挂的树。二叉树是一种最多只有两个节点的树型结构。这篇文章会用Java代码手撕二叉树的实现,从概念到实现,到oj题训练,你不仅能学会二叉树,还能加深对它的理解和运用。

1.树形结构的概念

在树形结构中,子树之间不能有交集,否则就不是树型结构,它具有以下的特点:

  • 子树是不相交的;
  • 除了根节点外,每个节点有且仅有一个父节点;
  • 一颗N个结点的树有N-1条边。

树中的相关概念:

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

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

叶子结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I...等节点为叶结点

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

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

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

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

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

非终端结点或分支结点:度不为0的结点; 如上图:D、E、F、G...等节点为分支结点

兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点

堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点

结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先

子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙

森林 :由m(m>=0)棵互不相交的树组成的集合称为森林

树的应用:

树的思想和应用在我们周围应用非常广泛也非常重要,比如树的遍历运用递归的思想,电脑中的文件管理系统(目录和文件)运用树形结构存储。

2.二叉树的性质

二叉树是一个节点的有限集合,要么为空,要么是由一个根节点加上两颗被称为左子树和右子树的二叉树组成。如下图所示:

两种特殊的二叉树:

  1. 满二叉树: 一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。

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

二叉树的性质:

  1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^i-1 (i>0)个结点
  2. 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2^k - 1 (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,否则无右孩

3.如何构造一个二叉树

常见树的表示方式有很多种,如:双亲表示法,孩子表示法,孩子双亲表示法,孩子兄弟表示法等等。

二叉树用代码进行表示时有两种方法,一是通过数组形式的顺序存储,二是类似于链表的链式存储。

顺序存储:将元素存储到数组中,利用二叉树的性质5进行存储,设i为节点在数组中的下标,则:

  • 如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
  • 如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
  • 如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子

完全二叉树

一般二叉树

tips: 对于非完全二叉树,则不适合使用顺序方式进行存储,,空间中必须存储空节点,导致空间利用率很低。

链式存储:二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下:

java 复制代码
// 孩子表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
} 

// 孩子双亲表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
Node parent; // 当前节点的根节点
}

4.二叉树的基本操作

二叉树遍历分为四种:

  • 前序遍历------访问根结点--->根的左子树--->根的右子树。
  • 中序遍历------根的左子树--->根节点--->根的右子树。
  • 后序遍历------根的左子树--->根的右子树--->根节点。
  • 层序遍历------第一层--->第二层--->第三层--->...最后一层

下面是用代码分别实现这四种遍历方式的过程。

java 复制代码
package Mytree;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:莎土比亚
 * Date:2024-03-08
 * Time:18:06
 */
public class Mytree {
    class Node{
        public Node(int val) {
            this.val=val;
        }
        int val;
        Node left;
        Node right;
    }
    private int usedsize;
    Node root;
    public Mytree(){
        root=null;
        usedsize=0;
    }
    public void createBinaryTree(){
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);
        Node node6 = new Node(6);
        root = node1;
        node1.left = node2;
        node2.left = node3;
        node1.right = node4;
        node4.left = node5;
        node5.right = node6;
    }
    // 前序遍历
    public void preOrder(Node root) {
        if(root==null){
            return;
        }
        System.out.print(root.val+" ");
        preOrder(root.left);
        preOrder(root.right);
    }
    // 中序遍历
    public void inOrder(Node root) {
        if(root==null){
            return;
        }
        inOrder(root.left);
        System.out.print(root.val+" ");
        inOrder(root.right);
    }
    // 后序遍历
    public void postOrder(Node root){
     if(root==null){
         return;
     }
     postOrder(root.left);
     postOrder(root.right);
     System.out.print(root.val+" ");
    }
    //层序遍历
    public void LeOrder(Node root){
        Queue<Node> list=new LinkedList<>();
        list.add(root);
        while (!list.isEmpty()){
            Node cur=list.poll();
            if(cur!=null){
            System.out.print(cur.val+" ");
            list.add(cur.left);
            list.add(cur.right);
           }
        }
    }

    public static void main(String[] args) {
        Mytree tree=new Mytree();
        tree.createBinaryTree();
        System.out.println("前序遍历");
        tree.preOrder(tree.root);
        System.out.println("\n中序遍历");
        tree.inOrder(tree.root);
        System.out.println("\n后序遍历");
        tree.postOrder(tree.root);
        System.out.println("\n层序遍历");
        tree.LeOrder(tree.root);
    }
}

二叉树的常见方法:

java 复制代码
// 获取树中节点的个数
int size(Node root);

// 获取叶子节点的个数
int getLeafNodeCount(Node root);

// 获取第K层节点的个数
int getKLevelNodeCount(Node root,int k);

// 获取二叉树的高度
int getHeight(Node root);

// 检测值为value的元素是否存在
Node find(Node root, int val);

//层序遍历
void levelOrder(Node root);

// 判断一棵树是不是完全二叉树
boolean isCompleteTree(Node root);

创建一个MyTree类实现一个自己的二叉树并包含上述方法:

java 复制代码
    // 获取树中节点的个数
    int size(Node root) {
        int num=0;
        if(root==null){
            return 0;
        }
        num+=size(root.left);
        num+=size(root.right);
        num++;
        return num;
    }

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

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

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

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

    // 判断一棵树是不是完全二叉树
    boolean isCompleteTree(Node root) {
        if(root==null){
            return true;
        }
        if((root.left==null&&root.right!=null)||(root.left!=null&&root.right==null)){
            return false;
        }
        return isCompleteTree(root.left)&&isCompleteTree(root.right);
    }

总结:解决二叉树相关问题时要善用子问题思路,将原问题简化为从左子树和右子树中求原始结果,在套用方法递归即可得到问题答案。

5.二叉树相关oj题训练

1.翻转二叉树

2.检查两颗二叉树是否相同

3.判断一颗二叉树是否是平衡二叉树

4.根据一棵树的前序遍历和中序遍历构造二叉树

5.根据二叉树创建字符串

相关推荐
南宫生33 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
weixin_432702261 小时前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论
passer__jw7672 小时前
【LeetCode】【算法】283. 移动零
数据结构·算法·leetcode
爱吃生蚝的于勒3 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~3 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
脉牛杂德4 小时前
多项式加法——C语言
数据结构·c++·算法
一直学习永不止步4 小时前
LeetCode题练习与总结:赎金信--383
java·数据结构·算法·leetcode·字符串·哈希表·计数
wheeldown12 小时前
【数据结构】选择排序
数据结构·算法·排序算法
躺不平的理查德16 小时前
数据结构-链表【chapter1】【c语言版】
c语言·开发语言·数据结构·链表·visual studio
阿洵Rain16 小时前
【C++】哈希
数据结构·c++·算法·list·哈希算法