> 文档中心 > 【数据结构】二叉树详解

【数据结构】二叉树详解

目录

  • 1.树的概念
  • 2.二叉树的概念、结构及其性质
    • 2.1二叉树的概念
    • 2.2二叉树的特点
    • 2.3二叉树的结构
    • 2.4特殊的二叉树
    • 2.5二叉树的性质
  • 3.二叉树的遍历
    • 3.1前序遍历
    • 3.2中序遍历
    • 3.3后序遍历
  • 4.遍历的代码实现
    • 4.1前序递归实现(preorder)
    • 4.2中序递归实现(inorder)
    • 4.3后序递归实现(postorder)
  • 5.判断叶子结点的个数
  • 6.二叉树第 K 层的结点个数
  • 7.二叉树的层序遍历(levelOrder)
  • 8.前、中、后序遍历的非递归写法

1.树的概念

在数据结构中树是一种非线性的结构,它是由 n (n >= 0) 个有限的结点组成的一个具有层次的结构,结构上看起来像一个倒置的树
在这里插入图片描述

根结点:没有父节点(双亲结点),如结点 A
叶子结点:没有孩子结点的结点,如结点 D、E、F、G、H
兄弟结点:有相同的父节点两个结点,如结点 G 和 H
祖先结点:根到该结点之间经过的所有结点都是该结点的祖先结点
子孙结点:以某一结点为根结点的树中的任意结点都是该结点的子孙结点
结点的度:结点含有的子树的个数为该结点的度,叶子结点的度为 0
树的度:在树中最大的结点的度,该树的度为 2
树的层次:从根开始算,根为第一层,根的子结点为第二层,以此类推
森林:由 m 棵互不相交的树组成的集合

2.二叉树的概念、结构及其性质

2.1二叉树的概念

二叉树是由多个结点构成的集合,该集合可能为空,或者是一个根结点与其左右两个二叉子树所构成
二叉树是一种特殊的树

2.2二叉树的特点

1.二叉树的度是 <= 2 的,也就是说如果有孩子,最多就只有两个孩子
结点可以有 0,1,2 个孩子
在这里插入图片描述
2.二叉树是一种有序树
在这里插入图片描述

2.3二叉树的结构

将二叉树根结点的两个孩子,分别为左孩子 (left child) 和右孩子 (right child)
在这里插入图片描述

区分:左孩子和左子树,右孩子和右子树
孩子是一个结点
子树是一个结构(其中无论是否有结点 )

2.4特殊的二叉树

1.满二叉树
二叉树的每一层的结点个数都达到最大值,对于一个层数为 K 的树,其结点总数为 (2 ^ K) - 1 ,这种树就是满二叉树
2.完全二叉树
二叉树的叶子结点只出现在最后两层,而且最后一层的叶子结点都是靠左对齐的,这种树就是完全二叉树
在这里插入图片描述

2.5二叉树的性质

1.若规定根结点的层数为 1,那么一棵非空二叉树第 K 层上最多有 2 ^ (K - 1) {K > 0} 个结点
2.若规定只有根结点的二叉树的深度为 1,那么深度为 K 的二叉树最大结点个数为 (2 ^ K) - 1 {K >= 0} ,也就是一棵满二叉树
3.对任意一棵二叉树,如果其叶子结点的个数为 n0,度为 2 的结点个数为 n2,那么有 n0 = n2 + 1
在这里插入图片描述

3.二叉树的遍历

3.1前序遍历

在这里插入图片描述

一棵树的前序遍历 :根【左子树的前序遍历】【右子树的前序遍历】
有 9 个结点,可以看作有 9 棵树存在
A : A B H I C D F G C
B : B H I
C : C D E F G
D : D E F G
E : E
F : F G
G : G
H : H
I : I

所以这棵树的前序遍历为:A B H I C D F G C

3.2中序遍历

在这里插入图片描述

一棵树的中序遍历 :【左子树的中序遍历】根【右子树的中序遍历】
A : H B I A E D F G C
B : H B I
C : E D F G C
D : E D F G
E : E
F : F G
G : G
H : H
I : I

所以这棵树的中序遍历为:H B I A E D F G C

3.3后序遍历

在这里插入图片描述

一棵树的后序遍历 : 【左子树的后序遍历】【右子树的后序遍历】根
A : H I B E G F D C A
B : H I B
C : E G F D C
D : E G F D
E : E
F : G F
G : G
H : H
I : I

所以这棵树的后序遍历为 :H I B E G F D C A

4.遍历的代码实现

4.1前序递归实现(preorder)

前序遍历时首先访问根结点,然后前序遍历其左子树,最后前序遍历其右子树,当二叉树为空树时直接返回即可,总结为:

1.访问根结点
2.前序遍历其左子树
3.前序遍历其右子树

代码实现
TreeNode 类

public class TreeNode {    public int val;     // 使用 public 是方便我们之后操作    public TreeNode left;   // 左孩子,如果左孩子不存在,通过 null 表示,左孩子不存在    public TreeNode right;    public TreeNode(int val) { this.val = val; this.left = null; this.right = null;    }
public static void preorder(TreeNode root) { if (root != null) {     System.out.printf("%c ", root.val);//首先打印根结点     preorder(root.left);//前序遍历其左子树     preorder(root.right);//前序遍历其右子树 }    }

4.2中序递归实现(inorder)

中序遍历时首先中序遍历左子树,然后访问根结点,最后中序遍历右子树,当二叉树为空树时直接返回即可,总结为:

1.中序遍历其左子树
2.访问根结点
3.中序遍历其右子树

代码实现

public static void inorder(TreeNode root) { if (root != null) {     inorder(root.left);//中序遍历左子树     System.out.printf("%c ", root.val);//打印根结点     inorder(root.right);//中序遍历右子树 }    }

4.3后序递归实现(postorder)

后序遍历时首先后序遍历左子树,然后后序遍历右子树,最后访问根结点,当二叉树为空树时直接返回即可,总结为:

1.后序遍历左子树
2.后序遍历右子树
3.访问根结点

代码实现

public static void postorder(TreeNode root) { if (root != null) {     postorder(root.left);//后序遍历左子树     postorder(root.right);//后序遍历右子树     System.out.printf("%c ", root.val);//打印根结点 }    }

5.判断叶子结点的个数

给定一棵二叉树,我们要求出其叶子结点的个数
1.首先我们的思路是,对二叉树进行遍历,对每一个结点进行确认是否为叶子结点,发现叶子结点则统计变量++
2.我们可以将大问题转化为小问题进行解决
【数据结构】二叉树详解

我们规定:
左子树中的叶子结点个数为 leftLeafCount
右子树中的叶子结点的个数为 rightLeafCount
整棵树的叶子结点个数为 leftLeafCount + rightLeafCount
情况分为如上图所示的几种:
1.当二叉树为空树时,叶子结点个数为 0
2.当只有根结点时,叶子结点个数为 1
3.剩余情况,叶子结点个数为 leftLeafCount + rightLeafCount

还是以这棵树为例,该树的叶子结点个数为 4 ,分别是 H I E G
我们简单举例:
A 树的叶子结点个数 = B 树的叶子结点个数 + C 树的叶子结点个数
B 树的叶子结点个数 = H 树的叶子结点个数 + I 树的叶子结点个数
H 树和 I 树都没有左右孩子,那么满足第二种情况
则 H 和 I 树的叶子结点个数为 1
所以 B 树的叶子结点个数为 2
同理也可以得出 C 树的叶子结点个数进而得知 A 树的叶子结点个数,即整棵树的叶子结点个数

在这里插入图片描述
判断叶子结点的逻辑我们已经懂了,那么接下来就要将思路逻辑转化为我们的代码了,其实代码十分简单,我们已经有了思路就很好写了
代码实现

public static int leafCount(TreeNode root){ if(root == null){//空树的情况     return 0; } if(root.left == null && root.right == null){//只有根结点的情况     return 1; }//剩余情况 int leftLeafCount = leafCount(root.left);//计算其左子树的叶子结点个数 int rightLeafCount = leafCount(root.right);//计算其右子树的叶子结点个数 return leftLeafCount + rightLeafCount;//返回叶子结点总数    }

6.二叉树第 K 层的结点个数

给定一棵二叉树,求出该树第 K 层的结点个数
1.我们要求第 K 层的结点个数,那么我们可以先求出该层作为第 K - 1 层时分布在左右子树中的结点个数,将大问题简化为小问题
2.收敛到一个特殊的情况:书的结点会越来越小,K 的值也会越来越小

root == null -> 0
root != null && k == 1 -> 1

在这里插入图片描述
以上图中的树为例,求第 3 层的结点个数
先求以 B 和 C 为根的两棵子树中该层为第 2 层时的结点个数
求该层为第 2 层时的结点个数,就要求出以 D E F G为根的 4 棵子树中该层为第1 层时的结点个数

代码实现

public static int calcLevelNodeCount(TreeNode root,int k){ if(root == null){//空树的情况     return 0; } if(k == 1){//处于第一层时结点个数就是 1      return 1; } int leftCount = calcLevelNodeCount(root.left,k - 1);//计算左子树中 k - 1 层时的个数 int rightCount = calcLevelNodeCount(root.right,k - 1);//计算右子树中 k - 1 层时的个数 return leftCount + rightCount;//返回结点总数    }

7.二叉树的层序遍历(levelOrder)

层序遍历就是首先从根结点出发,先访问第 1 层的根结点,然后从左向右访问第 2 层的结点,以此类推,自上而下,从左往右
层序遍历需要用到队列,需要将先进入队列的结点进行取出打印,先来的先服务

基本步骤如下:
1.首先将根结点放入队列,判断队列是否为空,不为空则进行循环
2.循环内容:
(1).取出队首结点并打印
(2).然后将其左右孩子按顺序放入队列(要对左右孩子是否为空进行判断)
3.按照前两部一直循环,直到遍历完所有结点,即队列为空

在这里插入图片描述
代码实现

 public static void levelOrder(TreeNode root) { if (root == null) {//空树则直接返回     return; } Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root);//将根结点放入队列 while (!queue.isEmpty()) {//若队列不为空一直循环     TreeNode node = queue.poll();//取出队首元素     System.out.printf("%c ", node.val);     if (node.left != null) {//左孩子不为空则放入队列  queue.offer(node.left);     }     if (node.right != null) {//右孩子不为空则放入队列  queue.offer(node.right);     } }    }

8.前、中、后序遍历的非递归写法

【数据结构】二叉树的遍历(非递归)