ZyLiu's Blog

非递归方式遍历二叉树

字数统计: 1.2k阅读时长: 5 min
2020/09/05 Share

非递归遍历二叉树,其基本思想是借助栈实现二叉树的遍历。本文回顾leetcode上给出的巧妙的一种相同算法解决先序、中序、后序的解法。

基础先序遍历

先序遍历二叉树的顺序为“父节点-左孩子-右孩子”,先给出二叉树的数据结构声明:

1
2
3
4
5
6
7
//Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

递归方式先序遍历二叉树:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> rlt;

recursion(root, rlt);
return rlt;
}

void recursion(TreeNode* root, vector<int>& rlt) {
if (!root) {
return;
}
rlt.push_back(root->val);
recursion(root->left, rlt);
recursion(root->right, rlt);
}
};

使用栈来迭代遍历二叉树,其基本思想为,每次迭代,栈顶元素出栈,并进行遍历,然后依次将其右孩子、左孩子入栈。

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
class solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> rlt;

if (!root) {
return rlt;
}

stack<TreeNode*> assist;
assist.push(root);

while (!assist.empty()) {
TreeNode* curNode = assist.top();
assist.pop();
rlt.push_back(curNode->val);

if (curNode->right) {
assist.push(curNode->right);
}

if (curNode->left) {
assist.push(curNode->left);
}
}

return rlt;
}

};

分析如何实现统一的先中后遍历算法

仔细考虑先序、中序、后续遍历的区别,它们的区别仅仅在于何时获取元素。
我们用迭代去模拟递归,来实现二叉树的遍历。
用栈遍历二叉树的整体顺序是确定的,我们先访问左子树,后访问右子树,因此右子树先入栈,左子树后入栈,如果不访问节点的话,最简的遍历方式应该是下面这样的:

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:
void Traversal(TreeNode* root) {
if (!root) {
return;
}

stack<TreeNode*> assist;
assist.push(root);

while (!assist.empty()) {
TreeNode* curNode = assist.top();
assist.pop();

if (curNode->right) {
assist.push(curNode->right);
}

if (curNode->left) {
assist.push(curNode->left);
}
}
}
};

相当于如下递归代码:

1
2
3
4
5
6
7
8
9
10
class solution {
public:
void Traversal(TreeNode* root) {
if (!root) {
return;
}
Traversal(root->left);
Traversal(root->right);
}
};

我们看到该算法中,只要确定何时获取元素,就可以改造为先序、中序、后序遍历。

  • 对于先序遍历而言,先处理父节点,再处理左子树和右子树,其顺序为父节点-左子树-右子树
  • 对于中序遍历而言,处理父节点时,意味着左子树已全部处理完毕,但右子树尚未处理,其顺序为左子树-父节点-右子树
  • 对于后序遍历而言,处理父节点时,意味着左右子树都已全部处理完毕,其顺序为左子树-右子树-父节点

用栈来处理遍历二叉树问题时,栈顶元素先处理,栈底元素后处理,因此三种遍历方式必须保证栈内元素有如下顺序:

  • 先序,父节点在栈顶,然后是左孩子、右孩子。
  • 中序,左孩子在栈顶,然后是父节点、右孩子。
  • 后序,左孩子在栈顶,然后是右孩子、父节点。

基于以上思路,我们使用一个空节点作为父节点的标记,实现了三种遍历算法。
每遍历一个节点,调整栈顶元素的位置,使其始终保持上述顺序,并在父节点上一层添加空节点进行标记。
当栈顶元素为空节点时,意味着次栈顶元素为需要访问的节点。

改进后的先序遍历

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
34
35
36
37
38
class solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> rlt;

if (!root) {
return rlt;
}

stack<TreeNode*> assist;
assist.push(root);

while (!assist.empty()) {
TreeNode* curNode = assist.top();
assist.pop();

if (curNode) {
// 保证父节点在栈顶,然后是左孩子、右孩子
if (curNode->right) {
assist.push(curNode->right);
}

if (curNode->left) {
assist.push(curNode->left);
}

assist.push(curNode);
assist.push(nullptr);
}
else {
rlt.push_back(assist.top()->val);
assist.pop();
}
}
return rlt;
}

};

改进后的中序遍历

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
34
35
36
37
38
class solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> rlt;

if (!root) {
return rlt;
}

stack<TreeNode*> assist;
assist.push(root);

while (!assist.empty()) {
TreeNode* curNode = assist.top();
assist.pop();

if (curNode) {
// 保证左孩子在栈顶,然后是父节点、右孩子
if (curNode->right) {
assist.push(curNode->right);
}

assist.push(curNode);
assist.push(nullptr);

if (curNode->left) {
assist.push(curNode->left);
}
}
else {
rlt.push_back(assist.top()->val);
assist.pop();
}
}
return rlt;
}

};

改进后的后序遍历

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
34
35
36
37
38
class solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> rlt;

if (!root) {
return rlt;
}

stack<TreeNode*> assist;
assist.push(root);

while (!assist.empty()) {
TreeNode* curNode = assist.top();
assist.pop();

if (curNode) {
// 保证左孩子在栈顶,然后是右孩子、父节点
assist.push(curNode);
assist.push(nullptr);

if (curNode->right) {
assist.push(curNode->right);
}

if (curNode->left) {
assist.push(curNode->left);
}
}
else {
rlt.push_back(assist.top()->val);
assist.pop();
}
}
return rlt;
}

};
CATALOG
  1. 1. 基础先序遍历
  2. 2. 分析如何实现统一的先中后遍历算法
  3. 3. 改进后的先序遍历
  4. 4. 改进后的中序遍历
  5. 5. 改进后的后序遍历