例題
- 中序遍歷94. Binary Tree Inorder Traversal
- 先序遍歷144. Binary Tree Preorder Traversal
- 后序遍歷145. Binary Tree Postorder Traversal
遞歸棧
遞歸函數棧的方法很基礎,寫法也很簡單,三種遍歷方式之間只需要改變一行代碼的位置即可
中序遍歷
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void inorder(TreeNode* root, vector<int>& v){
if(root != nullptr) {
inorder(root->left, v);
v.push_back(root->val); // 改變位置的代碼
inorder(root->right, v);
}
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
inorder(root, v);
return v;
}
};
先序遍歷
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void inorder(TreeNode* root, vector<int>& v){
if(root != nullptr) {
v.push_back(root->val);
inorder(root->left, v);
inorder(root->right, v);
}
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
inorder(root, v);
return v;
}
};
后序遍歷
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void inorder(TreeNode* root, vector<int>& v){
if(root != nullptr) {
inorder(root->left, v);
inorder(root->right, v);
v.push_back(root->val);
}
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
inorder(root, v);
return v;
}
};
非遞歸棧
當樹的深度過大時,函數棧可能會溢出,這時候需要我們使用數據結構中的棧,來模擬節點在棧中的壓入和彈出
中序遍歷
cur指針時刻指向需要處理的節點
如果當前節點不為空,則壓入棧中,並改變cur指針指向其左節點
如果當前節點為空,也代表空節點的中序遍歷自動完成,無需壓棧,這時候需要彈出棧頂的節點,保存棧頂節點的值,並更改cur指向其右子樹,以完成右子樹的中序遍歷
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
stack<TreeNode*> s;
TreeNode* cur = root;
while(cur || !s.empty()){
if(cur){
s.push(cur);
cur = cur->left;
} else {
cur = s.top();
s.pop();
v.push_back(cur->val);
cur = cur->right;
}
}
return v;
}
};
先序遍歷
先序遍歷與中序遍歷代碼相比只改變了保存節點的值的代碼的位置,當先訪問根的時候就記錄節點,而不是等左子樹遍歷完,彈出根節點的時候再記錄
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> v;
stack<TreeNode*> s;
TreeNode* cur = root;
while(cur || !s.empty()){
if(cur){
v.push_back(cur->val);
s.push(cur);
cur = cur->left;
} else {
cur = s.top();
s.pop();
cur = cur->right;
}
}
return v;
}
};
后序遍歷
因為后序遍歷的壓棧順序是左-右-根,由於先遍歷完左子樹,然后遍歷完右子樹,然后才能處理當前節點,為了和之前的代碼的結構保持一致,我們可以反向處理,也就是按根-右-左的順序壓棧,
結果反向輸出即可
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> v;
stack<TreeNode*> s;
TreeNode* cur = root;
while(cur || !s.empty()){
if(cur){
v.push_back(cur->val);
s.push(cur);
cur = cur->right;
} else {
cur = s.top();
s.pop();
cur = cur->left;
}
}
reverse(v.begin(), v.end()); // 反向輸出結果
return v;
}
};
Morris Traversal
線索二叉樹,O(n)時間,常數空間
Morris Traversal方法遍歷二叉樹(非遞歸,不用棧,O(1)空間)
就是當前節點的中序遍歷前驅節點,如果此前驅節點的右指針為空,則將此前驅節點的右指針指向當前節點
當尋找當前節點的中序遍歷前驅節點時,發現能循環到自己,說明左子樹已經遍歷完,需要遍歷右子樹
中序遍歷
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
TreeNode* cur, *prev;
vector<int> v;
cur = root;
prev = nullptr;
while(cur){
if(cur->left == nullptr){
v.push_back(cur->val);
cur = cur->right;
} else {
prev = cur->left;
while(prev->right != nullptr && prev->right != cur)
prev = prev->right;
if(prev->right == nullptr){
prev->right = cur;
cur = cur->left;
} else {
v.push_back(cur->val);
prev->right = nullptr;
cur = cur->right;
}
}
}
return v;
}
};
先序遍歷
同樣先序遍歷與中序遍歷相比也只需要改變一行代碼的位置
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
TreeNode* cur, *prev;
vector<int> v;
cur = root;
prev = nullptr;
while(cur){
if(cur->left == nullptr){
v.push_back(cur->val);
cur = cur->right;
} else {
prev = cur->left;
while(prev->right != nullptr && prev->right != cur)
prev = prev->right;
if(prev->right == nullptr){
v.push_back(cur->val);
prev->right = cur;
cur = cur->left;
} else {
prev->right = nullptr;
cur = cur->right;
}
}
}
return v;
}
};
后序遍歷
后序遍歷需要反向輸出cur->left到cur的中序前驅結點之間的路徑
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void reverse_traverse_reverse(TreeNode* cur, vector<int>& v){
TreeNode* prev = nullptr;
while(cur){
auto right = cur->right;
cur->right = prev;
prev = cur;
cur = right;
}
cur = prev;
prev = nullptr;
while(cur){
auto right = cur->right;
cur->right = prev;
v.push_back(cur->val);
prev = cur;
cur = right;
}
}
vector<int> postorderTraversal(TreeNode* root) {
TreeNode* cur, *prev;
vector<int> v;
cur = new TreeNode(-1);
prev = nullptr;
cur->left = root;
while(cur){
if(cur->left == nullptr){
cur = cur->right;
} else {
prev = cur->left;
while(prev->right != nullptr && prev->right != cur)
prev = prev->right;
if(prev->right == nullptr){
prev->right = cur;
cur = cur->left;
} else {
prev->right = nullptr;
reverse_traverse_reverse(cur->left, v);
cur = cur->right;
}
}
}
return v;
}
};
總結
三種遍歷方式中,后序遍歷的處理比較麻煩,但是無論是使用遞歸棧,非遞歸棧還是Morris Traversal,代碼的結構都是一樣的,中序和前序甚至只有一行代碼位置的差別
使用棧的版本中,最壞空間復雜度為O(n)鏈型,平均空間復雜度為O(lgn)
Morris Traversal空間復雜度為O(1)