目录
- 以下是整理了一晚上的二叉树三种非递归遍历方式,关于非递归遍历,有一个大的特点就是代码简单,但是难以理解。面试中常常考察我们的非递归迭代实现,而主要以后序遍历为重点!!
一、二叉树的前中后序遍历概念
要遍历,首先得有一棵树,直接上图!
(一)前序遍历
依次遍历 根->左子树->右子树,根在头部,故也称为前序遍历。一个原则,当你在某一颗子树中不知道怎么走的时候,永远按照 根->左子树->右子树顺序走就不会错。
上图前序遍历顺序为:8 3 1 6 4 7 10 14 13
(二)中序遍历
依次遍历 左子树->根->右子树,根在中部,故也称为中序遍历。
上图前序遍历顺序为:1 3 4 6 7 8 10 13 14
(三)后序遍历
依次遍历 左子树->右子树->根,根在尾部,故也称为后序遍历。
上图前序遍历顺序为:1 4 7 6 3 13 14 10 8
(四)层序遍历
依次遍历 每一层,故也称为层序遍历。
上图前序遍历顺序为:8 3 10 1 6 14 4 7 13
二、二叉树前序遍历
(一)题目描述
(二)思路
1.我们还是拿上边的二叉树进行讲解,列出该树前序遍历为8 3 1 6 4 7 10 14 13。
2.在非递归中我们需要定义一个栈来存储入栈的结点,在定义一个vector容器来保存遍历的值。而后先访问该树的所有左路节点,将其入栈并将值存储vector中。如下图:
3.接下来需要访问所有的子路节点的右树了,如图将1出栈,判断该结点有没有右树,没有继续将3出栈继续判断,3在走右树,则继续将6入栈,4入栈,也就是再去找子树的每一个左路结点,然后在入栈,每次入之前都存入该结点的数值到vector中,当栈为空并且结点为空时,证明前序遍历已经结束了,直接打印vector的值就是前序遍历的结果了。
(三)程序实现
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> v;
stack<TreeNode*> st;
TreeNode* cur=root;
//cur不为空,表示还有树没有开始访问
//st不为空,表示还有节点得右子树没有访问
while(cur||!st.empty())
{
//将该树中所有的左路结点入栈并将值存储到vector中
while(cur)
{
v.push_back(cur->val);
st.push(cur);
cur=cur->left;
}
//出栈,并走将该结点的右树
cur=st.top();
st.pop();
cur=cur->right;
}
return v;
}
};
三、二叉树中序遍历
(一)题目描述
(二)思路
1.理解上一个前序遍历,中序也就好理解了,列出该树中序遍历为1 3 4 6 7 8 10 13 14。
2.定义一个vector和栈,依次让所有左路结点入栈,但是先不存入vector中。
3.出栈并访问右子树时,将出栈的值存入vector中,如一开始访问到1时,1没有右树了,所以将1所在的结点出栈并存入1的值。在将3出栈并存值,3有右树则继续访问该右树,将该右树所有左结点入栈,如此循环往复。
4.直至所有结点访问完并且栈为空,遍历就完成了。
(三)程序实现
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
stack<TreeNode*> st;
TreeNode* cur=root;
//cur不为空,表示还有树没有开始访问
//st不为空,表示还有节点得右子树没有访问
while(cur||!st.empty())
{
//将该树中所有的左路结点入栈
while(cur)
{
st.push(cur);
cur=cur->left;
}
//出栈,将值存储到vector中,并走该结点的右树
cur=st.top();
v.push_back(cur->val);
st.pop();
cur=cur->right;
}
return v;
}
};
四、二叉树后序遍历
(一)题目描述
该题的非递归解法面试过程中常考,是重中之重,因为掌握了后序相当于前序中序全部掌握了。
(二)思路
1.列出该树后序遍历为1 4 7 6 3 13 14 10 8,依次将所有子树左路径入栈,
2.当走到1时,1没有左右树,所以将1出栈并存储,接着判断3是否有右子树,有则转换为子树问题继续访问,接着将4和6入栈。判断4是否有右子树,没有直接出栈并存储,接着我们判断6是否有右子树,有转入子树问题进行入栈,在判断7是否有左右子树,没有则出栈,再次回到6这个节点上。这时我们发现一个问题,我们两次经过6,第一次经过的时候,6的右树还没有访问,第二次在经过6的时候6的右树已经访问了,这时将6出栈并存储。所以,凡是右不为空,都会两次经过这个节点,那么我们要想办法区分。
3.区分两次经过的结点是否出栈存储,如结点6结点3,如果一个节点的右不为空。
- 如果他的右孩子不等于上一个访问的节点,那么他的右树还没有访问,应该子问题访问。
- 如果他的右孩子等于上一个访问的节点,那么他的右树已经访问过了,应该放他本身。
- 相当于当第一次经过6时我们是刚访问完4,并没有访问7,判断条件就是看上一次访问的4是否等于6的右子树结点。所以我们不需要将6出栈并存储,当访问完7时,证明已经访问完6的所有子树了,这时我们就让6出栈并存储,判断条件就是看上一次访问的7是否等于6的右子树结点。
4.直至所有结点访问完并且栈为空,遍历就完成了。
(三)程序实现
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> v;
stack<TreeNode*> st;
TreeNode* cur=root;
TreeNode* prev=nullptr;
//cur不为空,表示还有树没有开始访问
//st不为空,表示还有节点得右子树没有访问
while(cur||!st.empty())
{
//将该树中所有的左路结点入栈
while(cur)
{
st.push(cur);
cur=cur->left;
}
cur=st.top();
//判断该结点右子树是否为空,为空则出栈并存储数据
//判断该结点右子树是否与上一次出栈数据相同,相同则则出栈并存储数据
if(cur->right==nullptr||cur->right==prev)
{
v.push_back(cur->val);
st.pop();
prev=cur;
cur=nullptr;
}
//转入子树问题求解
else
{
cur=cur->right;
}
}
return v;
}
};