二叉树的遍历算法是二叉树最基础的算法,没有之一。
二叉树的遍历算法主要有4种:先序遍历,中序遍历,后续遍历和层次遍历,其中第1-3个属于深度优先遍历,第4个属于广度优先遍历。
对二叉树的3种深度优先遍历算法的学习有三个层次:
(一)精通遍历算法的递归实现;
(二)精通遍历算法的借助栈结构的迭代实现;
(三)精通遍历算法的不借助栈结构的递归实现。
我们希望读者至少可以掌握前两个层次。
本文讲解借助栈的深度优先遍历算法和借助队列的广度优先遍历算法,使用C++和Python3分别进行演示。
现在假设我们已将0-9的10个数按顺序存储到了一棵完全二叉树中,其结构如下:
现在我们的树结点定义如下:
Python版本:
class TreeNode:
'''二叉树节点的定义'''
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = None
self.right = None
C++版本:
// 二叉树的节点
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
本文二叉树的先序遍历结果是:0 1 3 7 8 4 9 2 5 6
先序遍历的访问顺序是中间节点,左节点,右节点,即中左右。
其每次先处理的是中间节点,那么先将最初的根节点作为中间节点放⼊栈中,
然后迭代时弹出中间节点访问其值,然后将右节点放入栈,再加⼊左节点。
注意:这里是入栈顺序是右左,出栈访问时才是左右。
input:node output:result 初始化结果列表result; 初始化栈stack; 将输入节点node入栈; 当栈stack不为空时: 取出一个栈顶元素命名为cur,将其加入result;(处理节点值) 如果 cur的右子树非空,将右子树入栈; 如果 cur的左子树非空,将左子树入栈。 返回result |
由于Python自带的List数据类型就可以提供栈的功能,所以我们直接使用列表来做为此处的栈。
Python3版本
def pre_order(node):
"""先序遍历"""
result = []
if node == None:
return result
stack = [node]
# 迭代访问
while len(stack) > 0:
cur = stack.pop()
result.append(cur.val)
if cur.right:
stack.append(cur.right)
if cur.left:
stack.append(cur.left)
return result
C++版本
vector preOrder(TreeNode * node) {
vector result;
if (node == NULL) {
return result;
}
stack stack;
stack.push(node);
// 迭代访问
while (!stack.empty()) {
TreeNode* cur = stack.top();
stack.pop();
result.push_back(cur->val);
if (cur->right) {
stack.push(cur->right);
}
if (cur->left) {
stack.push(cur->left);
}
}
return result;
}
本文二叉树的中序遍历结果是:7 3 8 1 9 4 0 5 2 6
中序遍历的访问顺序是左节点,中间节点,右节点,即左中右。
先序遍历:先访问二叉树顶部的中间节点,然后处理中间节点;接着访问左节点,然后处理左节点;最后访问右节点,然后处理右节点。
中序遍历:先访问二叉树顶部的中间节点,然后一层层访问到二叉树最左端的最底部,然后开始处理节点的值
这里需要注意的是,中序遍历的访问顺序和处理顺序已经不一致了。
这里我们就需要用一个指示变量(也可以叫做指针)来帮助我们访问节点,
首先cur存在时,说明cur还没有访问到最底层,则将cur的值存入栈保存下来,然后cur更新为其左节点,这样不断迭代直至找到最底层(cur==null)
当cur为空时,说明cur已经访问到最底层,此时栈中已经保存了cur访问过的节点,其最后放入栈的一定是当前cur的父节点(中间节点),我们将其弹出设置为cur,然后处理其值,然后更新为cur的右节点
过程如下:
input:node output:result 初始化结果列表result; 初始化栈stack; 将输入节点node设置为指示节点变量cur; 当cur存在或者栈stack不为空时: 如果cur存在: 将cur入栈stack; 更新cur为cur的左节点; 如果cur不存在: 取出一个栈顶元素命名为cur,将其加入result;(处理节点值) 更新cur为cur的右节点。 |
由于Python自带的List数据类型就可以提供栈的功能,所以我们直接使用列表来做为此处的栈。
Python3版本
def in_order(self, node):
"""中序遍历"""
result = []
if node == None:
return result
stack = []
cur = node
while cur or len(stack)>0:
if cur:
stack.append(cur)
cur = cur.left
else:
cur = stack.pop()
result.append(cur.val)
cur = cur.right
return result
C++版本
vector inOrder(TreeNode* node) {
vector result;
stackstack;
TreeNode* cur = node;
while (cur || !stack.empty()) {
if (cur) {
stack.push(cur);
cur = cur->left;
}
else
{
cur = stack.top();
stack.pop();
result.push_back(cur->val);
cur = cur->right;
}
}
}
本文二叉树的后序遍历结果是:7 8 3 9 4 1 5 6 2 0
后序遍历的访问顺序是左节点,右节点,中间节点,即左右中。
先序遍历:先访问二叉树顶部的中间节点,然后处理中间节点;接着访问左节点,然后处理左节点;最后访问右节点,然后处理右节点。
后序遍历:先访问二叉树顶部的中间节点,然后一层层访问到二叉树最左端的最底部,然后访问对应的右节点,最后访问中间节点,开始处理节点的值
同样需要注意的是,后序遍历的访问顺序和处理顺序也是已经不一致了。
但是这里我们可以使用一个更加简便的方式:
先序遍历是中左右,后续遍历是左右中,那么我们只需要调整⼀下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了
过程如下:
input:node output:result 初始化结果列表result; 初始化栈stack, 将输入节点node入栈; 当栈stack不为空时: 取出一个栈顶元素命名为cur,将其加入result;(处理节点值) 如果 cur的右子树非空,将右子树入栈; 如果 cur的左子树非空,将左子树入栈; 反向result; 返回result。 |
代码实现如下:
Python3版本
def post_order(self, node):
"""后序遍历"""
stack = [node]
result = []
while len(stack) > 0:
cur = stack.pop() # 根节点
result.append(cur)
if cur.left: # 左节点
stack.append(cur.left)
if cur.right:# 右节点
stack.append(cur.right)
# 反向输出result的结点值
for a in result[::-1]:
print(a.value, end=' ')
C++版本
vector postOrder(TreeNode* node) {
vector result;
if (node == NULL) {
return result;
}
stack stack;
stack.push(node);
while (!stack.empty()) {
TreeNode* node = stack.top();
stack.pop();
result.push_back(node->val);
if (node->left) {
stack.push(node->left);
}
if (node->right) {
stack.push(node->right);
}
}
reverse(result.begin(), result.end()); // 将结果反转
return result;
}
本文二叉树的层次遍历结果是:0 1 2 3 4 5 6 7 8 9
层次遍历就是按树的层次进行遍历。
第一层是根节点,第二层节点是根节点的子节点,访问第二层必须借助根节点,同样第三层节点也必须依次借助第二层节点才能访问,
这种访问下一层节点必须依赖上一层节点的特点要求我们必须在遍历时把下一层的节点存储起来,然后在访问下一层结点时又将它们依次取出
这要求我们的存储容器是先进先出的队列属性
input:node output:result 初始化结果列表result; 初始化队列queue; 将输入节点node入队; 当队列queue不为空时: 取出一个队列元素命名为cur,将其加入result;(处理节点值) 如果 cur的左子树非空,将左子树加入队列; 如果 cur的右子树非空,将右子树加入队列。 返回result |
其实现基本都是借助队列实现的,由于Python自带的List数据类型就可以提供队列的功能,所以我们直接使用列表来做为此处的队列:
Python3版本
def level_order(self,node):
"""层次遍历"""
if not node:
return
queue = [node] # 将第一层根节点加入列表
# 开始遍历
while len(queue)>0:
# 从容器中取出,访问当前层结点
cur = queue.pop(0) # 取出第一个
print(cur.value, end=" ")
# 向容器中添加,存储下一层结点
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
C++版本:
vector levelOrder(TreeNode* node) {
vector result;
if (node == NULL) {
return result;
}
queue queue;
queue.push(node);
while (!queue.empty()) {
TreeNode* cur = queue.front();
result.push_back(cur->val);
queue.pop();
if (cur->left) {
queue.push(cur->left);
}
if (cur->right) {
queue.push(cur->right);
}
}
return result;
}