数据结构——树与二叉树:核心概念全解析 - 实践

目录

一: 树型结构(了解)

 1.1 概念

1.2 概念(重要)

1.3 树的表⽰形式(了解)

二:⼆叉树(重点)

2.1 概念

2.2 两种特殊的⼆叉树

1:满⼆叉树

2:完全二叉树

2.3 ⼆叉树的性质

2.4 ⼆叉树的存储

2.5 二叉树的实现

1 :前置说明

2 :⼆叉树的遍历

3 :⼆叉树的基本操作

三:总结


一: 树型结构(了解)

 1.1 概念

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

有⼀个特殊的结点,称为根结点,根结点没有前驱结点

除根结点外,其余结点被分成M(M > 0)个互不相交的集合T1、T2、......、Tm,其中每⼀个集合Ti
(1 <= i <= m) ⼜是⼀棵与树类似的⼦树。每棵⼦树的根结点有且只有⼀个前驱,可以有0个或多个后继  
树是递归定义的。

注意:树形结构中,⼦树之间不能有交集,否则就不是树形结构

树形结构:
某个节点,往下能够延申出多个节点,就把延申出的节点叫做子节点,当前节点就叫做父节点。
但是,如何一个父节点可以包含多个子节点。任何子节点只有一个父节点。

1.2 概念(重要)

结点的度:⼀个结点含有⼦树的个数称为该结点的度; 如上图: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)棵互不相交的树组成的集合称为森林(多个树)

1.3 树的表⽰形式(了解)

树结构相对线性表就⽐较复杂了,要存储表⽰起来就⽐较⿇烦了,实际中树有很多种表⽰⽅式,如:
双亲表⽰法(一般不使用),孩⼦表⽰法(常见表示方法)、孩⼦双亲表⽰法、孩⼦兄弟表⽰法等等。我们这⾥就简单的了解其中最常⽤的孩⼦兄弟表⽰法。

双亲:
class Node{
String val;
Node parent;
}
孩子表示法:
class{
String val;
List childeren;
}
孩子双亲表示:(功能最强大)
class Node{
String val;
List childern
Node parentl;
}
孩子兄弟:
class Node{
String val;
Node firstChild;
List brotherNodes;
}

对于树来说:
1:有一个专门的类
2:拿到根节点,后续的节点就能拿到

二:⼆叉树(重点)

2.1 概念

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

从上图可以看出:
1⼆叉树不存在度⼤于2的结点(只能小于等于2)
2⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树

注意:对于任意的⼆叉树都是由以下⼏种情况复合⽽成的:

2.2 两种特殊的⼆叉树

1:满⼆叉树

⼀棵⼆叉树,如果每层的结点数都达到最⼤值,则这棵⼆叉树就是满⼆叉树。也就是
说,如果⼀棵⼆叉树的层数为K,且结点总数是 ,则它就是满⼆叉树。
(整棵树,没有存在一个分支的情况下)

2:完全二叉树

完全⼆叉树是效率很⾼的数据结构,完全⼆叉树是由满⼆叉树⽽引出来的。对于深度
为K的,有n个结点的⼆叉树,当且仅当其每⼀个结点都与深度为K的满⼆叉树中编号从0⾄n-1的结
点⼀ 对应时称之为完全⼆叉树。 要注意的是满⼆叉树是⼀种特殊的完全⼆叉树。
(相当于是在满二叉树的基础上,缺失右下角!!)

2.3 ⼆叉树的性质

1 若规定根结点的层数为1,则⼀棵⾮空⼆叉树的第i层上最多有(i>0)个结点
找规律  1   1
             2   2
             3   4
             4   8
    最多有2^(i-1)个节点 i为层数
2 若规定只有根结点的⼆叉树的深度为1,则深度为K的⼆叉树的最⼤结点数是(k>=0)
总节点数
1 1
2 3
3 7
4 15
2^-1
3 对任何⼀棵⼆叉树, 如果其叶结点个数为 n0, 度为2的⾮叶结点个数为 n2,则有n0=n2+1

4具有n个结点的完全⼆叉树的深度k为上取整

5 对于具有n个结点的完全⼆叉树,如果按照从上⾄下从左⾄右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,⽆双亲结点

若2i+1<n,左孩⼦序号:2i+1,否则⽆左孩⼦

若2i+2<n,右孩⼦序号:2i+2,否则⽆右孩⼦

2.4 ⼆叉树的存储

⼆叉树的存储结构分为:顺序存储和类似于链表的链式存储。
⼆叉树的链式存储是通过⼀个⼀个的节点引⽤起来的,常⻅的表⽰⽅式有⼆叉和三叉表⽰⽅式,具体如下:

孩子表示法:
class Node{
String val;
Node left;//左子树
Node right;右子树
}
如果那个树没有,那就设为空
// 孩⼦双亲表⽰法
class Node {
int val; // 数据域
Node left; // 左孩⼦的引⽤,常常代表左孩⼦为根的整棵左⼦树
Node right; // 右孩⼦的引⽤,常常代表右孩⼦为根的整棵右⼦树
Node parent; // 当前节点的根节点
}

2.5 二叉树的实现

1 :前置说明

在学习⼆叉树的基本操作前,需先要创建⼀棵⼆叉树,然后才能学习其相关的基本操作。由于现在⼤家对⼆叉树结构掌握还不够深⼊,为了降低⼤家学习成本,此处⼿动快速创建⼀棵简单的⼆叉树,快速进⼊⼆叉树操作学习,等⼆叉树结构了解的差不多时,我们反过头再来研究⼆叉树真正的创建⽅式。

package tree;
class Node {
    String val;
    Node left;//左子树
    Node right;//右子树
    public Node(String val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}class Tree{
    public Node root;
}
public class Test1 {
    //通过这个方法手动创建一棵树
    public  static Node createTree() {
        //先把所有节点都创建出来
        Node A = new Node("A");
        Node B = new Node("B");
        Node C = new Node("C");
        Node D = new Node("D");
        Node E = new Node("E");
        Node F = new Node("F");
        Node G = new Node("G");
        //然后把他们连起来
        A.left = B;
        A.right = C;
        B.left = D;
        B.right = E;
        C.left = F;
        C.right = G;
        //最后返回根节点
        return A;
    }
    public static void main(String[] args) {
        //root就表示树的根节点
        //Node root = new Node("A");
//        //也可以这样斌表示树 就是封装一下
//        Tree tree = new Tree();
        Node root = createTree();
    }
}

注意:上述代码并不是创建⼆叉树的⽅式,真正创建⼆叉树⽅式后序详解重点讲解。

再看⼆叉树基本操作前,再回顾下⼆叉树的概念,⼆叉树是:
1. 空树
2. ⾮空:根节点,根节点的左⼦树、根节点的右⼦树组成的。

2 :⼆叉树的遍历

遍历:把一个集合中所有节点,按照一定次序,全都访问一遍,访问过程中不重不漏

四种遍历方式:

先序遍历:基于递归 
从根节点出发
1先访问当前节点的值
2递归的针对左子树进行先序遍历(一直到底。null就返回)
3递归的针对右子树进行先序遍历
中序遍历
1先递归左子树
2访问根节点
3后递归右子树
后序遍历
1先递归左子树
2再递归右子树
3打印节点

规律:
1:站在根节点的
先序来说,第一个元素就是根节点
后续来说,最后一个元素就是根节点

2:站在子树的角度
先序遍历中的子树部分,第一个元素也是子树的根节点
后续遍历的字数部分,最后一个元素,也是子树的根节点
3:对于中序来说,先后序就知道根节点,如何中序根节点左侧就是左子树、右边就是右子树
层序遍历
按照层为维度,一层一层访问

代码实现:(代码都有注解)

package tree;
import java.util.LinkedList;
import java.util.Queue;
class Node {
    String val;
    Node left;//左子树
    Node right;//右子树
    public Node(String val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}class Tree{
    public Node root;
}
public class Test1 {
    //通过这个方法手动创建一棵树
    public  static Node createTree() {
        //先把所有节点都创建出来
        Node A = new Node("A");
        Node B = new Node("B");
        Node C = new Node("C");
        Node D = new Node("D");
        Node E = new Node("E");
        Node F = new Node("F");
        Node G = new Node("G");
        //然后把他们连起来
        A.left = B;
        A.right = C;
        B.left = D;
        B.right = E;
        C.left = F;
        C.right = G;
        //最后返回根节点
        return A;
    }
    //先序遍历,访问操作就是打印,先访问根节点,然后左子树,最后右子树
    //递归实现
    public static void preOrder(Node root) {
        if(root==null){
            return ; //空树,返回
        }
        System.out.print(root.val); //先访问根节点
        preOrder(root.left); //左子树
        preOrder(root.right); //右子树
    }
    //中序遍历 就是先左子树,然后访问根节点,最后右子树
    public static void inOrder(Node root) {
        if(root==null){
            return ; //空树,返回
        }
        inOrder(root.left); //左子树
        System.out.print(root.val); //访问根节点
        inOrder(root.right); //右子树、
    }
    //后序遍历 就是先左子树,然后右子树,最后访问根节点
    public static void postOrder(Node root) {
        if(root==null){
            return ; //空树,返回
        }
        postOrder(root.left); //左子树
        postOrder(root.right); //右子树
        System.out.print(root.val); //访问根节点
    }
    //层序遍历 就是按层遍历树,从上到下,从左到右
    public static void levelOrder(Node root) {
        if (root == null) {
            return; //空树,返回
        }
        //创建一个队列
        Queue queue = new LinkedList<>();
        queue.add(root); //根节点入队
        while (!queue.isEmpty()) {
            Node node = queue.poll(); //队首出队
            System.out.print(node.val); //访问节点
            if (node.left != null) {
                queue.add(node.left); //左子树入队
            }
            if (node.right != null) {
                queue.add(node.right); //右子树入队
            }
        }
    }
    public static void main(String[] args) {
        //root就表示树的根节点
        //Node root = new Node("A");
//        //也可以这样斌表示树 就是封装一下
//        Tree tree = new Tree();
        Node root = createTree();
        //先序遍历
        preOrder(root);
        System.out.println();
        //中序遍历
        inOrder(root);
        //后序遍历
        System.out.println();
        postOrder(root);
        //层序遍历
        System.out.println();
        levelOrder(root);
    }
}

3 :⼆叉树的基本操作

二叉树的基本操作:

// 获取树中节点的个数
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);
这些都在一下代码代码中都有注释,如果有不理解的可以在评论区哦·。。
// 获取树中节点的个数
int size(Node root){
    //方法1:public static int size = 0;//如果在递归函数中修改全局变量,会导致结果不准确,所以用一个静态变量来记录节点个数
    public static int size(Node root) {
        if (root == null) {
            return 0; //空树,返回0
        }
//        int size =0;
//        size++;
//         size(root.left);
//         size(root.right);//这个方法会导致结果不准确,因为递归函数中修改了全局变量size,导致结果不准确
//        return size;//也可以这样写;
        return 1 + size(root.left) + size(root.right);//方法二:递归计算左右子树的节点个数,然后加上根节点的个数
        //根节点+左+右=总节点数
    }
// 获取叶⼦节点的个数
int getLeafNodeCount(Node root);
//就是左子树和右子树都为空的节点
public static int getleafSize(Node root){
    if(root==null){
        return 0;
    }
    if(root.left==null&&root.right==null){
        return 1;
    }
    //如果左右子树都不为空,则递归计算左右子树的叶子节点个数
    return getleafSize(root.left)+getleafSize(root.right);
}
// 获取第K层节点的个数
int getKLevelNodeCount(Node root,int k);
public static int getKLevelNodeCount(Node root,int k) {
    if (root == null) {
        return 0;
    }
    if(k<1){
        System.out.println("k值必须大于0");
    }
    if(k==1){//如果是第1层,则只需要返回根节点
        return 1;
    }
     return getKLevelNodeCount(root.left,k-1)+getKLevelNodeCount(root.right,k-1);
    //要计算第k层的节点个数,就是需要计算k-1层递归的返回值
}
// 获取⼆叉树的⾼度
int getHeight(Node root);
//求左子树和右子树的最大值高度,然后加上根节点的高度
public static  int getHeight(Node root) {
if(root==null){
    return 0;
}
int leftHeight=getHeight(root.left);
int rightHeight=getHeight(root.right);
if(leftHeight>rightHeight){
    return leftHeight+1;
}
else {
    return rightHeight+1;
}
}
// 检测值为value的元素是否存在
Node find(Node root, int val);
    public static Node find(Node root, String value){
        if(root==null){
            return null;
        }
        if(root.val.equals(value)){
            return root;
        }
        Node left=find(root.left,value);
        if(left!=null){
            return left;
        }
        return find(root.right,value);
//        Node right=find(root.right,value);
//        if(right!=null){
//            return right;
//        }
//        return null;
    }
//层序遍历
void levelOrder(Node root);
前面已经写过
// 判断⼀棵树是不是完全⼆叉树
boolean isCompleteTree(Node root);
//判断整棵树是不是完全二叉树
public static boolean isCompleteTree(Node root) {
    if (root == null) {
        return true;//空树,返回true
}
    boolean isFirst = true; //是否是第一次遍历
//针对树进行层序遍历
Queue queue = new LinkedList<>();
    queue.offer(root); //根节点入队
while (!queue.isEmpty()) {
        Node node = queue.poll(); //队首出队
if(isFirst){
            //进入一阶段
if(node.left==null&&node.right==null){
                //没有子树,进入二阶段
                isFirst=false;
            }else if(node.left!=null&&node.right==null){
                //只有左子树,
                isFirst=false;
                //左子树入队列
                queue.offer(node.left);
            }else if(node.left==null&&node.right!=null){
                //只有右子树。返回false
                return false;
            }else{
                queue.offer(node.left);
                queue.offer(node.right);
            }
        }else{
            //进入二阶段
if(node.left!=null||node.right!=null){
                //有子树,返回false
                return false;
            }
            //没有子树,继续遍历下一个节点,由于没有子树,所以不用入队
        }
    }
    return true; //如果遍历完了,则是完全二叉树  }
1.要求每个节点必须要有两个子树
a)没有子树,进入二阶段
b)只有左子树,进入二阶段
c)只有右子树,判定false
2.要求每个节点必须没有子树

三:总结

本片博客讲解了树、二叉树的概念、以及实现基本操作和遍历。

posted on 2025-10-30 13:09  blfbuaa  阅读(69)  评论(0)    收藏  举报