本文介绍二叉树的遍历方法和具体代码实现。
1 二叉树的遍历方法
二叉树的遍历方法根据访问根结点的先后顺序分为先序、中序、后序遍历。
下面1.1,1.2,1.3节将分别介绍各种遍历方式的递归实现和非递归实现方法。
1.1 二叉树的先序遍历
二叉树先序遍历的过程为:遍历根结点,先序遍历左子树,先序遍历右子树。
1.1.1 二叉树先序遍历的递归实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
preorder(root);
return res;
}
void preorder(TreeNode* root) {
if (root == NULL) {
return;
}
res.push_back(root->val);
preorder(root->left);
preorder(root->right);
}
private:
vector<int> res;
};
|
1.1.2 二叉树先序遍历的非递归实现
先序遍历的非递归实现方式采用DFS的遍历算法,使用栈来保存中间结果。
- 方法1:传统方式,首先将根结点添加至栈中,然后循环添加根结点的左孩子结点。
当左孩子为空时,弹出栈顶结点,并将栈顶的右孩子结点作为根结点重复进行该过程。
因为是先序遍历,因此所有结点在进栈前进行访问。具体代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if (root == NULL) {
return res;
}
stack<TreeNode*> stk;
TreeNode* top = root;
while (!stk.empty() || top) {
while (top) {
res.push_back(top->val);
stk.push(top);
top = top->left;
}
top = stk.top();
stk.pop();
top = top->right;
}
return res;
}
};
|
- 方法2:因为栈的特性是后进先出。入栈和出栈的顺序相反,因此可以利用这一特性简化代码流程:
具体方法为:在根结点出栈前进行访问,然后分别将其右孩子和左孩子进栈。循环进行,直到栈为空为止。
这样就会在访问的时候先访问左孩子再访问右孩子。具体实现方式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if (root == NULL) {
return res;
}
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
TreeNode* top = stk.top();
res.push_back(top->val);
stk.pop();
if (top->right) {
stk.push(top->right);
}
if (top->left) {
stk.push(top->left);
}
}
return res;
}
};
|
1.2 二叉树的中序遍历
二叉树先序遍历的过程为:中序序遍历左子树,遍历根结点,中序遍历右子树。
1.2.1 二叉树的中序遍历的递归实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
inorder(root, res);
return res;
}
void inorder(TreeNode* root, vector<int>& res) {
if (root == nullptr) {
return;
}
if (root->left != nullptr) {
inorder(root->left, res);
}
res.emplace_back(root->val);
if (root->right != nullptr) {
inorder(root->right, res);
}
}
};
|
1.2.2 二叉树的中序遍历的非递归实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
if (root == NULL) {
return res;
}
stack<TreeNode*> stk;
TreeNode* top = root;
while (!stk.empty() || top) {
while (top) {
stk.push(top);
top = top->left;
}
top = stk.top();
res.push_back(top->val);
stk.pop();
top = top->right;
}
return res;
}
};
|
1.3 二叉树的后序遍历
二叉树先序遍历的过程为:中序序遍历左子树,遍历根结点,中序遍历右子树。
1.3.1 二叉树的后序遍历的递归实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
postorder(root, res);
return res;
}
void postorder(TreeNode* root, vector<int>& vec) {
if (root == nullptr) {
return;
}
postorder(root->left, vec);
postorder(root->right, vec);
vec.push_back(root->val);
}
};
|
1.3.2 二叉树的后序遍历的非递归实现
- 方法1:传统方法+访问标记
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if (root == nullptr) {
return res;
}
stack<pair<TreeNode*, bool>> stk;
TreeNode* top = root;
while (!stk.empty() || top) {
while (top) {
auto new_node = std::make_pair(top, false);
stk.push(new_node);
top = top->left;
}
pair<TreeNode*, bool> head = stk.top();
stk.pop();
TreeNode* node = head.first;
int sign = head.second;
if (sign == false) {
head.second = true;
stk.push(head);
top = node->right;
} else {
res.push_back(node->val);
top = nullptr;
}
}
return res;
}
};
|
- 方法2:使用两个栈
该方法类似于先序遍历的非递归方式中的方法2。
只是为了得到中-右-左的序列,因此是先让左孩子入栈,再让右孩子入栈。
最终的得到的序列和目标序列顺序相反,在使用一个栈将顺序调整过来即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if (root == nullptr) {
return res;
}
stack<TreeNode*> stk;
stack<TreeNode*> stk_sec;
stk.push(root);
while (!stk.empty()) {
TreeNode* top = stk.top();
stk.pop();
stk_sec.push(top);
if (top->left) {
stk.push(top->left);
}
if (top->right) {
stk.push(top->right);
}
}
while (!stk_sec.empty()) {
TreeNode* top = stk_sec.top();
res.push_back(top->val);
stk_sec.pop();
}
return res;
}
};
|
- 方法3:使用一个栈
原理同方法2:但是是通过特殊的先序遍历(遍历的方式为中-右-左的方式)的方式得到中-右-左的序列。
然后将得到的序列翻转一下即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if (root == nullptr) {
return res;
}
stack<TreeNode*> stk;
TreeNode* top = root;
while (!stk.empty() || top) {
while (top) {
res.push_back(top->val);
stk.push(top);
top = top->right;
}
top = stk.top();
stk.pop();
top = top->left;
}
reverse(res.begin(), res.end());
return res;
}
};
|
1.4 二叉树的层次遍历
二叉树的层次遍历实际上是对二叉树的广度优先遍历。
原理参考BFS原理的讲解。具体代码实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (root == NULL) {
return res;
}
queue<TreeNode*> que_level;
que_level.push(root);
while (!que_level.empty()) {
int que_size = que_level.size();
vector<int> level_vec;
for (int i = 0; i < que_size; ++i) {
TreeNode* head = que_level.front();
if (head->left) {
que_level.push(head->left);
}
if (head->right) {
que_level.push(head->right);
}
level_vec.push_back(head->val);
que_level.pop();
}
res.push_back(level_vec);
level_vec.clear();
}
return res;
}
};
|
2 二叉树的遍历方法总结
二叉树的前序中序、后序遍历都属于深度优先遍历(DFS),而层次遍历属于广度优先遍历(BFS)。
深度优先遍历需要使用栈作为存储中间过程的数据结构,而广度优先遍历需要使用队列作为中间过程的数据结构。
通过1.1,1.2,1.3的递归实现和非递归实现,我们不难发现。
-
三种递归遍历方式实现代码较为简洁,三种遍历的方式的差异在于访问根结点的时机。迭代是访问根结点的时机和对应的遍历方式对应。
-
三种非递归方式的实现都有可以使用传统遍历框架,区别在于访问结点的差异:
- 先序遍历:先访问结点,再放入栈;
- 中序遍历:先放入栈,出栈时再访问结点;
- 后序遍历:增加标记,结点进栈两次,第二次出栈时再访问结点;
-
先序遍历和后续遍历:可以通过栈后进先出的特性调整顺序,简化操作。