二叉树
1.基本概念
二叉树是每个节点最多有两个子树的树结构,度可能是0,1,2;
完成二叉树:从左到右依次填满;
满二叉树:除了叶子节点,所有节点都有两个孩子,并且所有叶子节点在同一层;
2.性质
1.完全二叉树除了最后一层外,下一层节点个数是上一层两倍,
如果一颗完全二叉树的节点总数是n,那么叶子节点个数为n/2(n为偶数)或(n+1)/2(n为奇数);
2. if一颗二叉树的层数是n,那么满二叉树的节点数为2^n-1; 叶子节点的总数为2^(n-1)
3.递归在二叉树中的应用
写递归算法的关键就是明确函数的定义是什么,然后相信这个定义,利用这个定义推道出最终结果,绝不要跳入递归的细节 (人的小脑袋才能堆几层栈)
写树相关的算法,简单的就是说,去想象一个最小单元,搞清楚当前最小单元中t节点“该做什么,什么时候做”这两点,然后根据函数定义递归调用子节点。
- 该做什么:就是我们的最小单元中的root节点(可能不止一个)想要实现功能,需要得到什么信息,然后能做什么;
- 做点什么能够提供信息给下面的子树利用(先序)
- 能从下面的子树上获得什么信息然后利用(后序)
- 什么时候做:刚才写的代码应该放在前序、中序还是后序的代码位置上。
把题目的要求细化,搞清楚根节点应该做什么,然后剩下的事情抛给前/中/后序的遍历框架就行了,难点在于如何通过题目的要求思考出每一个节点需要做什么。
写完之后自己代入一个最基本的看能不能实现功能,会不会出不来,检验一下。
4.遍历
4.1 概念
- 前序遍历:根节点 -> 左子树 -> 右子树;
- 中序遍历:左子树 -> 根节点 -> 右子树;
- 后序遍历:左子树 -> 右子树 -> 根节点;
4.2 递归实现
递归的时候不要太在意实现的细节,其本质上是通过栈来实现的,每次在方法里自己调自己,就把新调用的自己压栈,是一个有去有回的过程。
对于递归,关键就是要清楚函数的功能,什么时候停下来。
对于前序遍历,想先打印根节点,再左再右;那就先输出,再去递归调用,传入当前节点的左子树,左子树同样打印它的根,传入根的左子树,知道整个左子树处理完了,再去处理右子树;
时间复杂度:0(N),每个节点遍历N次;
空间复杂度:O(N),递归过程中栈的开销;
4.3 迭代实现
前序遍历
前序遍历就是我们来手动实现在递归过程中的栈。
想要实现先左再右,那压栈的时候右先入栈,左再入栈。
如下图所示;

规则:
压入根节点;
1.弹出就打印;
2.如有右孩子,压入右;
3.如有左孩子,压入左;
重复1.2.3
思考一下这个过程;其实就是相当于两个孩子来替换栈里的根节点,这就很符合前序的定义,根先走,左子树干到了最顶部,要是我左子树还有左孩子,那我也走,两个孩子来替我,每次都是我先走,我左孩子这边整个都完事了,再来我右孩子,因为我右孩子被压在最下面。
中序遍历
规则:
1.整条左边界依次入栈;
2.条件1执行不了了,弹出就打印;
3.来到弹出节点的右子树上,继续执行条件1;(右树为空,执行2.弹出就打印;右树有节点,执行1.压栈;
说明:将整个树全用左边界去看,都是先处理了左边界,将左边界分解成了左头,头先入,左再入,然后弄不动了,弹出,然后看其右节点,再把右节点里的左依次进去;就这样往返;
如下如所示;

后序遍历
后序遍历可以用前序遍历来解决,想一下前序遍历:根左右,我们先压右树再压左树。怎么实现根右左呢,可以先压左树再压右树嘛,然后反过来不就是左右根了吗?(反过来用栈来实现,栈一个很大的作用就是实现逆序)
4.4 Morris实现
二叉树的结构中只有父节点指向孩子节点,孩子节点不能向上指,所以需要栈。
而Morris遍历的实质就是让下层节点也能指向上层,怎么办呢,一个节点有两个指针,左和右,如果这两个指针都有指向具体节点了那指定用不上了。
但是二叉树可是有很多空闲指针啊,比如说所有的叶子节点,它们的指针就都指向null,所以可以利用其right指针指向上层。
这样把下层往上层建立连接以后,cur指针就可以完整的顺着一个链条遍历完整个树。
因为不用堆栈,所以其空间复杂度变为O(1);
如下图所示:

cur指针走的顺序:1 2 4 2 5 1 3 6 3 7;
核心: 以某个根节点开始,找到其左子树的最右节点(必然是个叶子节点),然后利用其right指针指向根节点(建立从下到上的连接)
- 到达两次的是有左子树的节点;
- 到达一次的是没有左子树的节点;
原则:
- 1.如果cur无左孩子,cur向右移动(cur=cur.right)【图中有4到2】
- 2.如果cur有左孩子,找到cur左子树上最右的节点,记为mostRightNode;
- 1.如果mostRightNode的right指针指向空,让其指向cur,cur向左移动(cur=cur.left)
- 2.如果mostRightNode的right指针指向cur,让其指向空,cur向右移动(cur=cur.right)【图中由2到5】
(迭代法的中序遍历我们可以将整个树全部分成左边界去看,如上面的图,其实在morris遍历里我们可以将整个树全部分成右边界来看)
前序遍历
1.对于cur只达到一次的节点(没有左子树),cur达到就打印;
2.对于cur到达两次的节点(有左子树),到达第一次时打印;
中序遍历
1.对于cur只达到一次的节点(没有左子树),cur达到就打印;
2.对于cur到达两次的节点(有左子树),到达第二次时打印;
后序遍历
后序遍历比前面两个要复杂一点;
将一个节点的连续右节点当成是一个单链表看,如下图所示:

当我们到达最左侧,也就是左边连线已经创建完毕了。
打印 4
打印 5 2
打印 6
打印 7 3 1
我们将一个节点的连续右节点当成一个单链表来看待。
当我们返回上层之后,也就是将连线断开的时候,打印下层的单链表。
比如返回到 2,此时打印 4
比如返回到 1,此时打印 5 2
比如返回到 3,此时打印 6
最后别忘记头节点那一串,即1 3 7
那么我们只需要将这个单链表逆序打印就行了。
这里不应该打印当前层,而是下一层,否则根结点会先与右边打印。
4.5 层次遍历
层次遍历顾名思义就是一层一层的遍历。从上到下,从左到右,那需要借助什么结构呢?
可以采用队列的结构,利用其先进先出的特性,每一层依次入队,再依次出队。
对该层节点进行出队时,将这个节点的左右节点入队,这样当一层所有节点出队完成后,下一层也入队完成了。
5.深度
二叉树的最大深度是根节点到最远叶子结点的距离;
5.1 最大深度
递归实现
1.终止条件:在二叉树为空的时候,深度为1;
2.缩小范围,等价关系:给定一个二叉树,其深度为左子树的深度和右子树的深度的最大值+1;
非递归实现(层次遍历)
关键点:每遍历一层,则计数器加+1;直到遍历完成,得到树的深度。
采用二叉树的层次遍历,来计数总共有多少层,采用队列的结构,当前层节点出队,计数器加1,然后把下一层的节点全部入队,直到队为空。
5.2 最小深度
二叉树的深度是根节点到最近叶子节点的距离;
递归实现
此题不能像最大深度那样直接求两颗子树的最大然后+1,最大深度可以是因为取大值不会影响一棵树为空的时候。但是取最小就不一样了,如果一棵树为空,那最小的应该是不为空的那边的值,但是还按原来方式就变成了0+1;比如下面这个例子:最小深度应该我2.但是按原来方式写的话最小深度就会变为1.所以,在处理每一个节点的时候,如果有两个孩子,那就可以继续取小+1,如果只有一个孩子,那就只能去递归它的孩子。

非递归实现(层次遍历)
关键点:每遍历一层,则计数器加+1;在遍历的过程中,如果出现了没有叶子节点的节点,那就可以结束了,就是最小深度。
采用二叉树的层次遍历,来计数总共有多少层,采用队列的结构,当前层节点出队,计数器加1,然后把下一层的节点全部入队,直到遇到叶子节点或队为空。
6.重构二叉树
根据二叉树的前序或后序中的一个再加上中序来还原出整个二叉树。
注意: 中序是必须有的,因为其可以明确的把左右子树分开。
先看下3种遍历的特点(如下图):

特点
- 1.前序的第一个节点是root,后序的最后一个节点是root。
- 2.每种排序的左右子树分布都是有规律的。
- 3.每一个子树又可以看成是一颗全新的树,仍然遵循上述规律。
6.1 前序+中序
前序的遍历顺序是根左右,中序的遍历顺序是左中右,
递归实现
1.前序的第一个节点是root节点,对应能够找到在中序中的位置。
2.根据中序遍历的特点,在找到的根前边序列是左子树的中序遍历序,后边序列是右子树的中序遍历。
3.求出左边序列的个数,比如设为leftSize,那在前序序列中紧跟着根的leftSize个元素是左子树的前序序列,后边的为右子树的前序序列。
4.这样就又获得了两个子树的前序遍历和中序遍历,开始递归。
6.2 后序+中序
后序的遍历顺序是左右根,中序的遍历顺序是左根右,
递归实现
1.后序的第一个节点是root节点,对应能够找到在中序中的位置。
2.根据中序遍历的特点,在找到的根前边序列是左子树的中序遍历,后边序列是右子树的中序遍历。
3.求出左边序列的个数,比如设为leftSize,那在后序序列中的leftSize个元素是左子树的后序序列,后边的为右子树的后序序列。
4.这样就又获得了两个子树的后序遍历和中序遍历,开始递归。
6.3 层序遍历
除了前中后序外,其实有些题目里都会给出层序遍历的数组,有时候需要能够根据层序遍历来重构出来树;
附录(全程序)
package xin.utils;
import jdk.internal.dynalink.beans.StaticClass;
import sun.reflect.generics.tree.VoidDescriptor;
import javax.sound.midi.Soundbank;
import java.awt.font.TransformAttribute;
import java.util.*;
public class Tree {
}
class TreeNode<T>{
public T value;
public TreeNode<T> left;
public TreeNode<T> right;
public TreeNode(){}
public TreeNode(T value){
this.value = value;
}
public TreeNode(T value, TreeNode left, TreeNode right){
this.value = value;
this.left = left;
this.right = right;
}
}
class BinaryTreeTraverse<T> {
public static void preOrderByRecursion(TreeNode node) {
if (node == null) return;
System.out.println(node.value);
preOrderByRecursion(node.left);
preOrderByRecursion(node.right);
}
public static void preOrder(TreeNode node) {
Stack<TreeNode> stack = new Stack<>();
if (node != null) {
stack.push(node);
}
while (!stack.isEmpty()) {
TreeNode top = stack.pop();
System.out.println(top.value);
if (top.right != null) stack.push(top.right);
if (top.left != null) stack.push(top.left);
}
}
public static void inOrderByRecursion(TreeNode node) {
if (node == null) return;
inOrderByRecursion(node.left);
System.out.println(node.value);
inOrderByRecursion(node.right);
}
public static void inOrder(TreeNode node) {
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || node != null) {
if (node != null) {
stack.push(node);
node = node.left;
} else {
TreeNode top = stack.pop();
System.out.println(top.value);
node = top.right;
}
}
}
public static void postOrderByRecursion(TreeNode node) {
if (node == null) return;
postOrderByRecursion(node.left);
postOrderByRecursion(node.right);
System.out.println(node.value);
}
public static void postOrder(TreeNode node) {
Stack<TreeNode> stackA = new Stack<>();
Stack<TreeNode> stackB = new Stack<>();
if (node != null) {
stackA.push(node);
}
while (!stackA.isEmpty()) {
TreeNode top = stackA.pop();
stackB.push(top);
if (top.left != null) stackA.push(top.left);
if (top.right != null) stackA.push(top.right);
}
while (!stackB.isEmpty()) {
System.out.println(stackB.pop().value);
}
}
public static void morris(TreeNode node) {
if (node == null) {
return;
}
TreeNode cur = node;
TreeNode mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue;
} else {
mostRight.right = null;
}
}
cur = cur.right;
}
}
public static void preOrderMorris(TreeNode node) {
if (node == null) {
return;
}
TreeNode cur = node;
TreeNode mostRightNode = null;
while (cur != null) {
mostRightNode = cur.left;
if (mostRightNode != null) {
while (mostRightNode.right != null && mostRightNode.right != cur) {
mostRightNode = mostRightNode.right;
}
if (mostRightNode.right == null) {
mostRightNode.right = cur;
System.out.println(cur.value);
cur = cur.left;
continue;
} else {
mostRightNode.right = null;
}
} else {
System.out.println(cur.value);
}
cur = cur.right;
}
}
public static void inOrderMorris(TreeNode node) {
if (node == null) {
return;
}
TreeNode cur = node;
TreeNode mostRightNode = null;
while (cur != null) {
mostRightNode = cur.left;
if (mostRightNode != null) {
while (mostRightNode.right != null && mostRightNode.right != cur) {
mostRightNode = mostRightNode.right;
}
if (mostRightNode == null) {
mostRightNode.right = cur;
cur = cur.left;
continue;
} else {
System.out.println(cur.value);
mostRightNode.right = null;
}
} else {
System.out.println(cur.value);
}
cur = cur.right;
}
}
public static void postOrderMorris(TreeNode node) {
if (cur == null) {
return;
}
TreeNode cur = node;
TreeNode mostRightNode = null;
while (node != null) {
mostRightNode = cur.left;
if (mostRightNode != null) {
while (mostRightNode.right != null && mostRightNode.right != cur) {
mostRightNode = mostRightNode.right;
}
if (mostRightNode.right == null) {
mostRightNode.right = cur;
cur = cur.left;
continue;
} else {
mostRightNode.right = null;
postMorrisPrint(cur.left);
}
}
cur = cur.right;
}
postMorrisPrint(node);
}
public static void postMorrisPrint(TreeNode node) {
TreeNode reverseList = postMorrisReverseList(node);
TreeNode cur = reverseList;
while (cur != null) {
System.out.println(cur.value);
cur = cur.right;
}
postMorrisReverseList(reverseList);
}
public static TreeNode postMorrisReverseList(TreeNode node) {
TreeNode cur = node;
TreeNode pre = null;
while (cur != null) {
TreeNode next = cur.right;
cur.right = pre;
pre = cur;
cur = next;
}
return pre;
}
public static void layerOrder(TreeNode node) {
if (node == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(node);
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode front = queue.poll();
System.out.println(front.value);
if (front.left != null) queue.add(front.left);
if (front.right != null) queue.add(front.right);
}
}
}
}
class Depth{
public static int maxDepthByRecursion(TreeNode node){
if(node == null) return 0;
int leftDepth = maxDepthByRecursion(node.left);
int rightDepth = maxDepthByRecursion(node.right);
return Math.max(leftDepth,rightDepth)+1;
}
public static int maxDepth(TreeNode node){
if (node == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
int level = 0;
queue.add(node);
while (!queue.isEmpty()){
level++;
int levelNum = queue.size();
for (int i = 0; i < levelNum; i++){
TreeNode front = queue.poll();
if (front.left != null) queue.add(front.left);
if (front.right != null) queue.add(front.right);
}
}
return level;
}
public static int minDepthByRecursion(TreeNode node){
if (node == null) return 0;
if (node.right == null && node.left == null) return 1;
if (node.left == null && node.right != null) return minDepthByRecursion(node.right) + 1;
if (node.right == null && node.left != null) return minDepthByRecursion(node.left) + 1;
return Math.min(minDepthByRecursion(node.left), minDepthByRecursion(node.right))+1;
}
public static int minDepth(TreeNode node){
if (node == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
int level = 0;
queue.add(node);
while (!queue.isEmpty()){
level++;
int levelnum = queue.size();
for(int i = 0; i < levelnum; i++){
TreeNode front = queue.poll();
if (front.left == null && front.right == null){
return level;
}
if (front.right != null){
queue.add(front.right);
}
if (front.left != null){
queue.add(front.left);
}
}
}
return level;
}
}
class BuildBinaryTree{
public static TreeNode buildTreeByPreOrder(int[] preorder, int[] inorder){
if (preorder == null){
return null;
}
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < inorder.length; i++){
map.put(inorder[i], i);
}
return buildTreeByPreOrder(preorder, inorder, 0, preorder.length-1, 0, inorder.length-1,map);
}
private static TreeNode buildTreeByPreOrder(int[] preorder, int[] inorder, int preleft, int preright, int inleft, int inright, Map<Integer,Integer> map){
if (preleft > preright) return null;
TreeNode root = new TreeNode(preorder[preleft]);
int rootIndex = map.get(root.value);
int leftTreeSize = rootIndex - inleft;
root.left = buildTreeByPreOrder(preorder,inorder,preleft+1, preleft+leftTreeSize, inleft, rootIndex-1, map);
root.right = buildTreeByPreOrder(preorder,inorder,preleft+leftTreeSize+1, preright, rootIndex+1, inright,map);
return root;
}
public static TreeNode buildTreeByPostOrder(int[] postorder, int[] inorder){
if (postorder == null){
return null;
}
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < inorder.length; i++){
map.put(inorder[i], i);
}
return buildTreeByPostOrder(inorder, postorder, 0, inorder.length-1, 0, postorder.length-1,map);
}
private static TreeNode buildTreeByPostOrder(int[] inorder, int[] postorder, int inleft, int inright, int postleft, int postright, Map<Integer,Integer> map){
if (postleft > postright) return null;
TreeNode root = new TreeNode(postorder[postleft]);
int rootIndex = map.get(root.value);
int leftTreeSize = rootIndex - inleft;
root.left = buildTreeByPostOrder(inorder, postorder, inleft, rootIndex-1,postleft,postleft+leftTreeSize-1,map);
root.right = buildTreeByPostOrder(inorder,postorder,rootIndex+1, inright, postleft+leftTreeSize,postright-1,map);
return root;
}
}
参考链接
史上最全遍历二叉树详解
二叉树总结
算法基地-二叉树
__EOF__
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 我与微信审核的“相爱相杀”看个人小程序副业
· DeepSeek “源神”启动!「GitHub 热点速览」
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库