二叉树的遍历

递归算法(前中后序)

零基础小白,考虑问题的思路不是很清晰,希望大佬能指点指点

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. 前序遍历

非递归方法需要用到栈,在敲代码之前需要搞清楚几个小问题:
前序遍历的访问顺序是"根-左-右",而每个结点都可看作是根结点,因此遇到结点先访问;

  1. 从根结点开始访问,沿着其左孩子,依次访问并入栈,直到左孩子为空
    那么我之前就有个疑问,既然已经访问了左孩子,为什么还要入栈呢?
    入栈实际上是为了能够访问该结点的右子树,因为左子树遍历结束时,工作指针为空;因此无法回溯到(最近的)根结点访问右子树,而是要通过栈顶元素访问右子树。
  2. 访问栈顶元素的右孩子并弹出栈顶元素,若栈顶元素的右孩子为空,则继续执行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. 中序遍历

中序遍历的非递归算法与前序类似,只是访问的时机不同:

  1. 从根结点出发,沿着其左孩子,依次入栈(不访问),直到左孩子为空;
  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语句中
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值