1.二叉树的前序遍历
1.1 思路分析
前序遍历的顺序是根-左子树-右子树,所以首先从根节点开始,顺着访问左子树:1、2、4。此时,还剩下节点1、节点2、节点3的右子树没有访问。后面倒着访问节点1、2、4的右子树就行了。所以非递归的前序遍历是这样处理的:把一棵二叉树分为两个部分:1、左路节点;2、左路节点的右子树。如下图所示:
对于每一棵右子树,也是同样划分为这两个部分进行处理。
如何倒着取处理左路节点的右子树?我们可以借助栈来处理左路节点的右子树。以上图中的树为例,从根节点1开始,依次访问左路节点1、2、4。在访问的过程中,将左路节点节点放到要返回的vector中,同时把左路节点放到栈里(根据栈的性质,就可以倒着往上依次访问左路节点的右子树)。如下图所示:
接着,依次取栈的栈顶元素,同时访问栈顶元素的右子树。
1、栈顶元素4出栈,再访问节点4的右子树,将节点4的右子树入栈,由于该节点的右子树为空,则结束;
2、接着,栈顶元素2出栈,再访问节点2的右子树,此时节点2的右子树(节点5)入栈,同时将节点5放入到vector中;
3、栈顶元素节点5出栈,再访问节点5的右子树,将节点5的右子树入栈,由于该节点的右子树为空,则结束;
4、栈顶元素1出栈,再访问节点1的右子树,与之前的处理一样,将节点1的右子树分为左路节点和和左路节点的右子树,依次访问节点3、6;左路节点3、节点6入栈,同时节点3、6放入vector中;
5、栈顶元素节点6出栈,再访问节点6的右子树,将节点6的右子树入栈,由于该节点的右子树为空,则结束;
6、栈顶元素节点3出栈,再访问节点3的右子树节点7,节点7入栈并放入vector中;
7、栈顶元素节点7出栈,再访问节点7的右子树,再访问节点7的右子树,将节点7的右子树入栈,由于该节点的右子树为空,则结束。
具体如下图所示:
1.2 代码实现
vector<int> preorderTraversal(TreeNode* root)
{
vector<int> ret;
stack<TreeNode*> st;
TreeNode*cur=root;
while(cur||!st.empty())
{
while(cur)
{
//将二叉树的左路节点入栈,并同时放入数组中
st.push(cur);
ret.push_back(cur->val);
cur=cur->left;
}
//栈顶元素出栈,同时该元素的右子树节点入栈
TreeNode* top=st.top();
st.pop();
cur=top->right;
}
return ret;
}
2.二叉树的中序遍历
2.1 思路分析
二叉树的中序遍历的思路与前序遍历的思路差不多,中序遍历还是将二叉树分为左路节点和右子树,如下图所示。
在前序遍历中,我们将左路节点入栈时,就把他们放入到要返回的vector中了,这样就符合前序遍历的顺序了。中序遍历的顺序是:左子树->根节点->右子树。
1、我们将左路节点入栈,但是不放入到vector中;一直走到节点4的左子树为空,停止入栈,此时可以认为节点4的左子树是空,已经遍历过了;
2、栈中的节点依次出栈(当从栈中取出节点时,就意味着该节点的左子树已经被访问过了),第一个出栈的是栈顶元素节点4,并放入到vector中;访问完节点4的左子树和节点4本身(根节点),此时就该访问节点4的右节点,为空;
3、接着,栈顶元素节点2出栈,并放入到vector中;然后访问节点2的右子树节点5,与第一步一样,节点5入栈(由于节点5左右子树均为空,故只有节点5独自入栈);
4、栈顶元素节点5出栈,并放入到vector中;此时访问节点5的右子树,为空;
5、栈顶元素节点1出栈,并放入到vector中;然后访问节点1的右子树,与第一步一样,将节点1的右子树的左路节点3、6依次入栈;
6、栈顶元素节点6出栈,并放到vector中;访问完节点6的左子树和节点6本身(根节点),此时就该访问节点6的右节点,为空;
7、栈顶元素节点3出栈,并放到vector中;然后访问节点3的右子树,与第一步一样,节点3的右子树节点7入栈;
8、栈顶元素节点7出栈,并放到vector中;此时访问节点7的右子树,为空,则结束。
2.2 代码实现
vector<int> inorderTraversal(TreeNode* root)
{
vector<int> ret;
stack<TreeNode*> st;
TreeNode*cur=root;
while(cur||!st.empty())
{
while(cur)
{
//将二叉树的左路节点入栈
st.push(cur);
cur=cur->left;
}
//栈顶元素出栈,同时将该栈顶元素放入数组中
TreeNode* top=st.top();
st.pop();
ret.push_back(top->val);
cur=top->right;
}
return ret;
}
3.二叉树的后序遍历
3.1 思路分析
后续遍历的顺序是左子树->右子树->根,对于后续遍历依然是将二叉树分为左路节点和右子树,如下图。
对于栈顶元素可以像中序遍历那样直接将栈顶元素放入vector中,然后pop掉吗?答案是不可以,因为对于中序遍历,从栈中取出一个左路节点时,就意味着该节点的左子树已经被访问过了,接着就该访问根节点了,将栈顶元素放入vector中,然后pop掉就相当于访问根节点。后续遍历不能这样做,因为后续遍历要在访问完右子树之后再去访问根节点。
对于后续遍历来说,能不能直接将栈顶元素直接pop掉,然后放入到数组vector中,需要看情况。以下两种情况可以直接将栈顶元素pop掉,然后放入到数组vector中。1、栈顶元素的右子树为空;(如果栈顶元素的右子树不为空,要先访问右子树,再访问根节点。) 2、右子树已经被访问过了。那么问题来了,右子树为空很好判断,但是如何判断某个节点的右子树是否已经被访问过了呢?我们可以定义一个prev指针,每次访问完一个节点并出栈之后,将这个节点的指针赋值为prev指针,就可以通过prev来判断,该被访问过的节点是否为当前栈顶节点的右子树。
具体过程如下图所示。
3.2 代码实现
vector<int> postorderTraversal(TreeNode* root)
{
vector<int> ret;
stack<TreeNode*> st;
TreeNode*prev=nullptr;
TreeNode* cur=root;
while(cur||!st.empty())
{
//遍历二叉树的左路节点并入栈
while(cur)
{
st.push(cur);
cur=cur->left;
}
//取到栈顶元素时,栈顶节点的左子树已经被访问过了
TreeNode*top=st.top();
//如果该栈顶元素的右子树为空,或者该栈顶元素的右子树已经被访问过了,
//则访问该栈顶元素
if(top->right==nullptr||top->right==prev)
{
st.pop();
ret.push_back(top->val);
//记录prev
prev=top;
}
else
{
//否则就先处理右子树
cur=top->right;
}
}
return ret;
}