今天来做三道有关于树的题目,主要是来深入了解一下实现二叉树前序遍历,中序遍历以及后序遍历的三种方法
理论基础:
前序遍历:是以中左右
的顺序来对二叉树进行遍历的
中序遍历:是以左中右
的顺序来对二叉树进行遍历的
后序遍历:是以左右中
的顺序来对二叉树进行遍历的
树的定义:
struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
第一题
144.二叉树的前序遍历
递归法:
使用递归法来实现前序遍历的逻辑很简单,由于前序遍历是以中左右
的顺序,并且我们是从树的根开始的,所以首先我们得判定此时的node是否为null,如果不为null就将这个node的值放入数组中。然后以前序遍历的方式再继续遍历树的左子树和右子树。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> output;
traversal(root, output);//从根开始
return output;
}
void traversal(TreeNode* cur, vector<int>& vec){
if(cur == NULL) return;
vec.push_back(cur->val);//中
traversal(cur->left, vec);//左
traversal(cur->right,vec);//右
}
};
迭代法:
我们可以利用栈来实现前序遍历,然而由于栈的特性当我们将栈中的元素放到数组里面是以从后往前的顺序来放入的,所以这就会导致放入数组中的元素会反过来那就不是前序遍历了。不过我们可以将元素放入栈时的顺序调换一下,也就是让树的右边子树先进栈,然后再让左边的子树进栈,最后弹出栈中的元素就是正确的顺序了。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> vec;
if(root == NULL) return vec;
st.push(root);
while(!st.empty()){//遍历剩下的树中元素
TreeNode* cur = st.top();
vec.push_back(cur->val);//中
st.pop();//弹出栈中元素
if(cur->right) st.push(cur->right);//右子树进栈
if(cur->left) st.push(cur->left);//左子树进栈
}
return vec;
}
};
统一迭代法:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> vec;
if(root == NULL) return vec;
st.push(root);
while(!st.empty()){
TreeNode* cur = st.top();
if(cur != NULL){
st.pop();//避免重复元素
if(cur->right) st.push(cur->right);//右
if(cur->left) st.push(cur->left);//左
st.push(cur);//中
st.push(NULL);//标记
}else {
st.pop();//弹出NULL元素
vec.push_back(st.top()->val);//将元素加入数组中
st.pop();//弹出该元素
}
}
return vec;
}
第二题
145.二叉树的后序遍历
递归法:
后序遍历的递归法与前序遍历只有一点不同, 就是他的遍历方式是以左右中
的顺序。所以我们只需要改变树的遍历顺序就行也就是从树的左子树开始,再是右子树, 最后才是中间。从前序遍历来看如果我们是以中右左
的顺序放入栈,那么数组中的顺序就是中左右
。要实现左右中
这个顺序,我们可以先以中左右
的顺序放入栈,然后数组中的顺序就是中右左
。最后反转一下这个顺序就变成了左右中
。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> output;
traversal(root, output);//从根开始
return output;
}
void traversal(TreeNode* cur, vector<int>& vec){
if(cur == NULL) return;
traversal(cur->left, vec);//左
traversal(cur->right,vec);//右
vec.push_back(cur->val);//中
}
}
};
迭代法:
后序遍历的迭代法可以利用前序遍历迭代法的逻辑,不过还是有一点不一样。由于后序遍历的顺序是左右中
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> vec;
if(root == NULL)return vec;
st.push(root);
while(!st.empty()){
TreeNode* cur = st.top();
st.pop();
vec.push_back(cur->val);//中
if(cur->left) st.push(cur->left);//左
if(cur->right) st.push(cur->right);//右
//vector中的顺序是中右左
}
reverse(vec.begin(),vec.end());//反转一下vector,中右左的顺序变成了左右中
return vec;
}
};
统一迭代法:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> vec;
if(root == NULL) return vec;
st.push(root);
while(!st.empty()){
TreeNode* cur = st.top();
if(cur != NULL) {
st.pop();//避免重复元素
st.push(cur);//中
st.push(NULL);//标记
if(cur->right) st.push(cur->right);//右
if(cur->left) st.push(cur->left);//左
}else{
st.pop();//弹出元素NULL
vec.push_back(st.top()->val);//将该元素加入数组中
st.pop();//弹出该元素
}
}
return vec;
}
第三题
94.二叉树的中序遍历
递归法:
中序遍历和上面的后序遍历一样,只需要换一下树遍历的顺序就行也就是以左中右
的顺序来遍历
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> output;
traversal(root, output);//从根开始
return output;
}
void traversal(TreeNode* cur, vector<int>& vec){
if(cur == NULL) return;
traversal(cur->left, vec);//左
vec.push_back(cur->val);//中
traversal(cur->right, vec);//右
}
};
迭代法:
中序遍历的迭代法和前序遍历以及后序遍历的方法不太一样,他的栈先是存储左子树的元素,然后再寻找是否有右子树,如果有的话就将右子树放入栈中并在右子树中继续寻找左子树,直到栈弹出了所有元素。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> output;
stack<TreeNode*> st;
TreeNode* cur = root;
while(cur != NULL || !st.empty()){//根不为null直到栈弹出所有元素
if(cur != NULL){//存储左子树以及右子树中的左子树
st.push(cur);
cur = cur->left;
}else {
cur = st.top();
output.push_back(cur->val);//中
st.pop();//弹出栈中元素
cur = cur->right;//右
}
}
return output;
}
};
统一迭代法:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> vec;
if(root == NULL) return vec;
st.push(root);
while(!st.empty()){
TreeNode* cur = st.top();
if(cur != NULL){
st.pop();//避免元素重复
if(cur->right) st.push(cur->right);//右
st.push(cur);//中
st.push(NULL);//标记
if(cur->left) st.push(cur->left);//左
}else {
st.pop();//找到标记NULL,弹出NULL
vec.push_back(st.top()->val);//将元素加入数组中
st.pop(); //弹出元素
}
}
return vec;
}
统一迭代法的总结:
从这三个代码中我们看出除了while循环里面if内部的代码顺序不一样其他的都是一样的。并且每次都是在中间的元素后做一个NULL的标记。
1.前序遍历,原本的顺序是中左右
,在统一迭代法中变成了右左中NULL
2.中序遍历,原本的顺序是左中右
,在统一迭代法中变成了右中NULL左
3.后序遍历,原本的顺序是左右中
,在统一迭代法中变成了中NULL右左
因此可以看到其中的规律就是将原本的顺序倒过来然后在插入中间元素后插入一个NULL进行标记