递归算法(前中后序)
零基础小白,考虑问题的思路不是很清晰,希望大佬能指点指点
typedef char BTNodeType;
typedef struct BTNode
{
struct BTNode* left;
struct BTNode* right;
BTNodeType val;
}BTNode;
//前序遍历
void PreOrder(BTNode* root)
{
if(root == NULL)
{
printf("# "); //'#'代表空结点
return;
}
//访问根结点(也可以写一个访问函数进行其他操作,比如线索化二叉树)
printf("%c ", root->val);
PreOrder(root->left);
PreOrder(root->right);
}
//中序遍历
void InOrder(BTNode* root)
{
if(root == NULL)
{
printf("# ");
return;
}
InOrder(root->left);
printf("%c ", root->val);
InOrder(root->right);
}
//后序遍历
void PostOrder(BTNode* root)
{
if(root == NULL)
{
printf("# ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->val);
}
非递归算法(前中后序)
1. 前序遍历
非递归方法需要用到栈,在敲代码之前需要搞清楚几个小问题:
前序遍历的访问顺序是"根-左-右",而每个结点都可看作是根结点,因此遇到结点先访问;
- 从根结点开始访问,沿着其左孩子,依次访问并入栈,直到左孩子为空;
那么我之前就有个疑问,既然已经访问了左孩子,为什么还要入栈呢?
入栈实际上是为了能够访问该结点的右子树,因为左子树遍历结束时,工作指针为空;因此无法回溯到(最近的)根结点访问右子树,而是要通过栈顶元素访问右子树。 - 访问栈顶元素的右孩子并弹出栈顶元素,若栈顶元素的右孩子为空,则继续执行2;若其右孩子不为空,则执行1。
这里的弹出栈顶操作,我总觉得不妥,担心直接出栈会对后面的遍历有影响。
但实际上换个角度思考,该结点的根、左孩子和右孩子都已经访问完了,虽然此时其右子树并没有访问结束,但是对于该结点来说,它的使命已经完成了。
void PreOrder2(BTNode* root)
{
stack<BTNode*> s;
BTNode* cur = root; //工作指针
while(cur || !s.empty())
{
if(cur)
{
cout << cur->val << " ";
s.push(cur);
cur = cur->left;
}
else
{
cur = s.top();
s.pop();
cur = cur->right; //通过栈顶元素访问其右孩子
}
}
2. 中序遍历
中序遍历的非递归算法与前序类似,只是访问的时机不同:
- 从根结点出发,沿着其左孩子,依次入栈(不访问),直到左孩子为空;
- 令工作指针指向当前栈顶元素的右孩子,弹出栈顶元素并访问;
若栈顶元素右孩子为空,则继续执行2;若不为空,则执行1;
void InOrder2(BTNode* root)
{
stack<BTNode*> s;
BTNode* cur = root;
while(cur || !s.empty())
{
if(cur)
{
s.push(cur);
cur = cur->left;
}
else
{
cur = s.top(); //cur回溯到最近根结点,以便继续向右子树遍历
cout << cur->val << " ";
s.pop();
cur = cur->right;
}
}
}
3. 后序遍历
后序遍历的非递归算法就有点麻烦,因为它要求遍历完左子树和右子树之后,才能访问根结点。
可以不过多考虑细节地还原一下这个过程,沿着根结点左孩子一路到底,通过左子树最后一个结点(入口)进入其右子树(从左往右),其右子树遍历结束后要再返回入口(从右侧回),而第二次回到入口结点时需要进行的操作是访问该结点并出栈。
这就存在一个问题,当我走到这个结点时,我是该进入右子树还是访问弹出呢?
我们可以这样想,当我们第一次走到该结点,它的右孩子对我们来说是未知的,也就是未访问过的;而当我们第二次走到该结点时,说明该结点的右孩子刚刚访问完毕(因为是左右根)。因此我们可以设置一个pre指针指向最近访问的结点,即工作指针cur的前一个结点。
因此我们只需要判断 cur->right 是不是等于 pre就可以了。如果cur->right == pre,说明右孩子是访问过的,直接访问cur并弹出;如果cur->right != pre,则进入右子树遍历。
void PostOrder2(BTNode* root)
{
stack<BTNode*> s;
BTNode* cur = root;
BTNode* pre = NULL;
while(cur || !s.empty())
{
if(cur)
{
s.push(cur);
cur = cur->left;
}
else
{
cur = s.top();
if(cur->right && cur->right != pre)
{
cur = cur->right;
}
else
{
cout << cur->val << " ";
s.pop();
pre = cur;
cur = NULL; //将cur置空,是为了确保回到else语句中
}
}
}
}