二叉树遍历详解:前序、中序、后序、层序 4 种方式图文讲解与 Go 实现
概述
二叉树遍历是数据结构与算法领域的基础知识点,也是各大厂面试中的高频考题。简单来说,遍历就是按照某种特定顺序,依次访问二叉树中的每一个节点,且每个节点恰好被访问一次。
根据访问顺序的不同,二叉树遍历主要分为以下 4 种方式:
| 遍历方式 | 访问顺序 | 搜索策略 |
|---|---|---|
| 前序遍历 | 根 → 左 → 右 | 深度优先(DFS) |
| 中序遍历 | 左 → 根 → 右 | 深度优先(DFS) |
| 后序遍历 | 左 → 右 → 根 | 深度优先(DFS) |
| 层序遍历 | 逐层从左到右 | 广度优先(BFS) |
下面我们以这棵二叉树为例,逐一讲解每种遍历方式的原理和记忆方法:

前序遍历(Pre-order)
访问规则
前序遍历的口诀很简单:根 → 左 → 右。也就是说,每到一个节点,先记录当前节点的值,然后递归遍历左子树,最后递归遍历右子树。
图解记忆法
你可以想象有一个小人,从根节点出发,沿着二叉树的外沿逆时针走一圈,最终回到根节点。小人在路途中第一次经过某个节点时,就把该节点的值记下来,按顺序排列出来就是前序遍历的结果。
以上图的二叉树为例,前序遍历结果为:A B D H I E J C F K G

Go 语言实现
递归写法:
// TreeNode 定义二叉树节点
type TreeNode struct {
Val string
Left *TreeNode
Right *TreeNode
}
// PreorderTraversal 前序遍历(递归)
func PreorderTraversal(root *TreeNode) []string {
if root == nil {
return nil
}
result := []string{root.Val}
result = append(result, PreorderTraversal(root.Left)...)
result = append(result, PreorderTraversal(root.Right)...)
return result
}迭代写法(利用栈):
// PreorderIterative 前序遍历(迭代)
func PreorderIterative(root *TreeNode) []string {
if root == nil {
return nil
}
var result []string
stack := []*TreeNode{root}
for len(stack) > 0 {
// 弹出栈顶
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
result = append(result, node.Val)
// 注意:先压右子节点,再压左子节点
// 这样出栈时才是先左后右
if node.Right != nil {
stack = append(stack, node.Right)
}
if node.Left != nil {
stack = append(stack, node.Left)
}
}
return result
}迭代写法的核心思路是用一个栈来模拟递归过程。由于栈是后进先出的结构,所以要先把右子节点压入栈中,再压左子节点,这样弹出时的顺序才符合"先左后右"的要求。
中序遍历(In-order)
访问规则
中序遍历的口诀是:左 → 根 → 右。即先递归遍历左子树,再记录当前节点,最后递归遍历右子树。
图解记忆法
中序遍历有一个非常直观的理解方式:把二叉树的每个节点垂直向下投影到一条水平线上,就像每个节点受到重力作用掉落到地面一样。然后从左到右依次读取地面上节点的值,得到的就是中序遍历的结果。
以上图的二叉树为例,中序遍历结果为:H D I B J E A F K C G

Go 语言实现
递归写法:
// InorderTraversal 中序遍历(递归)
func InorderTraversal(root *TreeNode) []string {
if root == nil {
return nil
}
var result []string
result = append(result, InorderTraversal(root.Left)...)
result = append(result, root.Val)
result = append(result, InorderTraversal(root.Right)...)
return result
}迭代写法(利用栈):
// InorderIterative 中序遍历(迭代)
func InorderIterative(root *TreeNode) []string {
var result []string
var stack []*TreeNode
curr := root
for curr != nil || len(stack) > 0 {
// 一路向左,把沿途的节点全部压入栈
for curr != nil {
stack = append(stack, curr)
curr = curr.Left
}
// 弹出栈顶节点并记录值
curr = stack[len(stack)-1]
stack = stack[:len(stack)-1]
result = append(result, curr.Val)
// 转向右子树
curr = curr.Right
}
return result
}中序遍历的迭代写法稍微复杂一些。核心思路是:先沿着左子树一直往下走,把经过的节点依次压栈;走到头后弹出栈顶节点处理;然后转向该节点的右子树,重复这个过程。
补充知识:对于二叉搜索树(BST)来说,中序遍历的结果恰好是一个有序序列,这个性质在很多算法题中非常有用。
后序遍历(Post-order)
访问规则
后序遍历的口诀是:左 → 右 → 根。即先递归遍历左子树,再递归遍历右子树,最后才记录当前节点。所以根节点一定是后序遍历结果中的最后一个元素。
图解记忆法
后序遍历可以用 “剪葡萄” 的方式来记忆。想象你面前有一串葡萄挂在树上,你需要把葡萄一颗一颗地摘下来——每次只能摘掉一颗处于末端的、一剪刀就能剪下来的葡萄。你沿着树的外围转一圈,看到能直接摘下来的就摘掉,按摘下的先后顺序排列,就是后序遍历的结果。
以上图的二叉树为例,后序遍历结果为:H I D J E B K F G C A

Go 语言实现
递归写法:
// PostorderTraversal 后序遍历(递归)
func PostorderTraversal(root *TreeNode) []string {
if root == nil {
return nil
}
var result []string
result = append(result, PostorderTraversal(root.Left)...)
result = append(result, PostorderTraversal(root.Right)...)
result = append(result, root.Val)
return result
}迭代写法(逆序技巧):
// PostorderIterative 后序遍历(迭代)
func PostorderIterative(root *TreeNode) []string {
if root == nil {
return nil
}
var result []string
stack := []*TreeNode{root}
for len(stack) > 0 {
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
// 把值插入结果切片的最前面
result = append([]string{node.Val}, result...)
// 先压左再压右(和前序相反)
if node.Left != nil {
stack = append(stack, node.Left)
}
if node.Right != nil {
stack = append(stack, node.Right)
}
}
return result
}后序遍历的迭代写法有一个巧妙的思路:前序遍历的顺序是"根 → 左 → 右",如果我们把它改成"根 → 右 → 左",再将整个结果逆序,就得到了"左 → 右 → 根"——也就是后序遍历。上面的代码正是利用了这个技巧。
层序遍历(Level-order)
访问规则
层序遍历是唯一一种基于广度优先搜索(BFS)的遍历方式。规则非常直接:从根节点开始,从上到下逐层访问,同一层内从左到右依次读取。
以上图的二叉树为例,层序遍历结果为:A B C D E F G H I J K

Go 语言实现
层序遍历通常使用队列来实现:
// LevelOrderTraversal 层序遍历
func LevelOrderTraversal(root *TreeNode) []string {
if root == nil {
return nil
}
var result []string
queue := []*TreeNode{root}
for len(queue) > 0 {
// 取出队首节点
node := queue[0]
queue = queue[1:]
result = append(result, node.Val)
// 依次将左、右子节点加入队列
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
return result
}层序遍历的实现核心是队列(先进先出)。每次从队列头部取出一个节点,处理后把它的左右子节点加入队尾。这样就自然保证了逐层、从左到右的访问顺序。
4 种遍历方式对比总结
| 对比项 | 前序遍历 | 中序遍历 | 后序遍历 | 层序遍历 |
|---|---|---|---|---|
| 访问顺序 | 根 → 左 → 右 | 左 → 根 → 右 | 左 → 右 → 根 | 逐层从左到右 |
| 搜索策略 | DFS | DFS | DFS | BFS |
| 递归实现难度 | 简单 | 简单 | 简单 | 不适用 |
| 迭代实现难度 | 简单 | 中等 | 中等 | 简单 |
| 辅助数据结构 | 栈 | 栈 | 栈 | 队列 |
| 典型应用场景 | 复制二叉树、序列化 | BST 有序输出 | 释放内存、计算表达式树 | 按层打印、最短路径 |
常见问题
1. 前序、中序、后序遍历名字中的"序"指的是什么?
这里的"序"指的是根节点的访问时机。前序 = 根节点在最前面访问;中序 = 根节点在中间访问;后序 = 根节点在最后访问。左子树始终在右子树之前被访问,不同的只是根节点出现的位置。
2. 递归和迭代写法哪个更好?
递归写法代码简洁,逻辑清晰,非常适合理解遍历的本质。但在实际工程中,如果树的深度非常大,递归可能会导致栈溢出(stack overflow)。迭代写法虽然代码更长,但可以手动控制栈的大小,更加安全。面试中两种写法最好都能熟练掌握。
3. 如何根据遍历结果还原一棵二叉树?
单独的一种遍历结果无法唯一确定一棵二叉树。但有两种经典组合可以还原:
- 前序 + 中序 → 可以唯一确定
- 后序 + 中序 → 可以唯一确定
注意,前序 + 后序的组合在一般情况下不能唯一确定二叉树。
4. 层序遍历有哪些实际应用?
层序遍历在实际开发中使用广泛,例如:按层打印二叉树、计算二叉树的最大/最小深度、判断完全二叉树、寻找二叉树中每层的最大值等。很多 LeetCode 题目都是基于层序遍历的变形。
5. DFS 和 BFS 有什么区别?
DFS(深度优先搜索)会尽可能深地探索分支,直到走不下去才回溯。前序、中序、后序遍历都属于 DFS。BFS(广度优先搜索)则是逐层展开,层序遍历就是典型的 BFS。两者适用场景不同,DFS 适合搜索路径相关问题,BFS 适合最短路径和按层处理的问题。
总结
二叉树的遍历是学习树结构和算法的第一步,也是各类编程面试中绕不开的考点。本文介绍了 4 种遍历方式——前序、中序、后序和层序遍历,每种都给出了直观的图解记忆方法,并提供了 Go 语言的递归与迭代实现代码。
掌握这 4 种遍历的关键在于理解它们的核心区别:
- 前序、中序、后序 的区别仅在于根节点的访问时机
- 层序遍历 使用队列而非栈,体现了广度优先的思路
- 递归写法 帮你理解原理,迭代写法 帮你应对工程实践
建议你先把每种遍历的递归写法彻底理解透,再去攻克迭代写法。遇到相关的算法题时,先判断属于哪种遍历的变形,再套用对应的模板,很多问题就能迎刃而解了。
如果大家对二叉树遍历还有任何疑问,或者遇到了什么棘手的遍历问题,欢迎在评论区交流讨论~~~
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/binary-tree-traversal/
备用原文链接: https://blog.fiveyoboy.com/articles/binary-tree-traversal/