二叉树的先序遍历
方法一:递归
思路与算法:
首先我们需要了解什么是二叉树的前序遍历:按照访问根节点——左子树——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。
代码实现(c++):
class Solution {
public:
void preorder(TreeNode *root, vector<int> &res) {
if (root == nullptr) {
return;
}
res.push_back(root->val);
preorder(root->left, res);
preorder(root->right, res);
}
vector<int> preorderTraversal(TreeNode *root) {
vector<int> res;
preorder(root, res);
return res;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度: O ( n ) O(n) O(n),为递归过程中栈的开销,平均情况下为 O ( l o g n ) O(log\ n) O(log n),最坏情况下树呈现链状,为 O ( n ) O(n) O(n)。
方法二:迭代
思路与算法:
我们也可以用迭代的方式实现方法一的递归函数,两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同。
代码实现(c++):
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if (root == nullptr) {
return res;
}
stack<TreeNode*> stk;
TreeNode* node = root;
while (!stk.empty() || node != nullptr) {
while (node != nullptr) {
res.emplace_back(node->val);
stk.emplace(node);
node = node->left;
}
node = stk.top();
stk.pop();
node = node->right;
}
return res;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度: O ( n ) O(n) O(n),为迭代过程中显式栈的开销,平均情况下为 O ( l o g n ) O(log\ n) O(log n),最坏情况下树呈现链状,为 O ( n ) O(n) O(n)。
二叉树的中序遍历
方法一:递归
思路与算法:
首先我们需要了解什么是二叉树的中序遍历:按照访问左子树——根节点——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。
代码实现(c++):
class Solution {
public:
void inorder(TreeNode* root, vector<int>& res) {
if (!root) {
return;
}
inorder(root->left, res);
res.push_back(root->val);
inorder(root->right, res);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
inorder(root, res);
return res;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度: O ( n ) O(n) O(n),为递归过程中栈的开销,平均情况下为 O ( l o g n ) O(log\ n) O(log n),最坏情况下树呈现链状,为 O ( n ) O(n) O(n)。
方法二:迭代
思路与算法:
方法一的递归函数我们也可以用迭代的方式实现,两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其他都相同,具体实现可以看下面的代码。
代码实现(c++):
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk;
while (root != nullptr || !stk.empty()) {
while (root != nullptr) {
stk.push(root);
root = root->left;
}
root = stk.top();
stk.pop();
res.push_back(root->val);
root = root->right;
}
return res;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度: O ( n ) O(n) O(n),为迭代过程中显式栈的开销,平均情况下为 O ( l o g n ) O(log\ n) O(log n),最坏情况下树呈现链状,为 O ( n ) O(n) O(n)。
二叉树的后序遍历
方法一:递归
思路与算法:
首先我们需要了解什么是二叉树的后序遍历:按照访问左子树——右子树——根节点的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。
代码实现(c++):
class Solution {
public:
void postorder(TreeNode *root, vector<int> &res) {
if (root == nullptr) {
return;
}
postorder(root->left, res);
postorder(root->right, res);
res.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode *root) {
vector<int> res;
postorder(root, res);
return res;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度: O ( n ) O(n) O(n),为迭代过程中显式栈的开销,平均情况下为 O ( l o g n ) O(log\ n) O(log n),最坏情况下树呈现链状,为 O ( n ) O(n) O(n)。
方法二:迭代
思路与算法:
我们也可以用迭代的方式实现方法一的递归函数,两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同,具体可以参考下面的代码。
代码实现(c++):
class Solution {
public:
vector<int> postorderTraversal(TreeNode *root) {
vector<int> res;
if (root == nullptr) {
return res;
}
stack<TreeNode *> stk;
TreeNode *prev = nullptr;
while (root != nullptr || !stk.empty()) {
while (root != nullptr) {
stk.emplace(root);
root = root->left;
}
root = stk.top();
stk.pop();
if (root->right == nullptr || root->right == prev) {
res.emplace_back(root->val);
prev = root;
root = nullptr;
} else {
stk.emplace(root);
root = root->right;
}
}
return res;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度: O ( n ) O(n) O(n),为迭代过程中显式栈的开销,平均情况下为 O ( l o g n ) O(log\ n) O(log n),最坏情况下树呈现链状,为 O ( n ) O(n) O(n)。