树的三种遍历方式-先序中序后序遍历-优化与思考


对访问次数和压栈之间关系的思考

 

  1. 一个节点如果只用访问一次,那么在一开始访问的时候直接访问即可。
  2. 如果需要访问两次,那么就要入栈,这样才能返回来再访问一次。站内元素全都已经被访问了一次。
  3. 如果需要访问三次,那么不仅要入栈,还要标记它已经被访问了一次还是两次 。 


后序遍历的三次访问:


    1.作为当前节点的左孩子或者右孩子被访问,因为后序遍历此时不能做任何操作,只能入栈等待以后访问。
    2.第二次被访问,此时对以该节点为根节点的子树后序遍历,依然不能操作本节点,而是将它的右孩子和左孩子入栈。
    3.第三次被访问,此时已经操作完毕它的左子树和右子树,是时候操作它自己了。 
    // 在本程序中,所谓的操作就是将其输出,print。 


后序遍历的优化写法:

 

  •     1.while访问左节点,并入栈。这样栈里的元素都是已经访问过左孩子的。
  •     2.第二次访问时,标记本身已经访问了两次,并访问其右节点。
  •     3.第三次访问对自身进行操纵。


优化逻辑:

  1. 右节点可以根据父节点找到 ,因此没有必要提前入栈(这样可以减少栈的最大深度)
  2. 左节点入栈后立即被访问,因此可以用指针临时保存。维持栈内元素都是已经被访问过左节点的。也正是因为如此,我们不能将右节点提前入栈,因为它的左节点尚未被访问。 


PS: 关于栈的最大深度的问题,

常规写法如果想解决,可以让左右孩子不同时压栈。可以把flag变为char或者int类型,记录三种状态。 

  •     1.入栈后flag=0
  •     2.左孩子压栈flag=1
  •     3.右孩子压栈flag=2 

 当然这样flag也会占用空间,但至少不是栈的空间。 

 

PSS:关于初始化时使用push(root)和p=root的区别的问题

 

  • push(root): 初始化时尚未访问root,但已经将root压栈;这代表栈中的元素可能是未访问过的。在使用优化写法时,因为要维持栈中的元素左孩子均被访问过,所以不能用这种写法。
  • p=root 这种初始化时栈为空,可以根据我们的实际需要定义栈中元素所应具有的特点,但是这样 就不能在最外层while循环条件写!empty,因为最开始栈就是空的。 


先序遍历的一次访问:


 对每一个节点,先访问它自身,再将右节点和左节点入栈即可。 


优化写法的逻辑是,左节点入栈后即被访问出栈,所以不必入栈,用指针临时保存即可。 

 

中序遍历常规写法的三次访问:

 

  •     1.作为孩子被访问,入栈。
  •     2.左孩子压栈。给自己一个flag表示自己已访问过两次。 
  •     3.左孩子访问完毕,此时操作自身,出栈,右子树入栈。 


中序遍历优化写法的两次访问:

  •     1.将自己入栈等待以后访问,指针指向左子树,对左子树重复此步骤,直到指针为空 。栈中的元素都是左子树被访问过,但自身和右子树尚未被访问的。 
  •     2. 第二次,出栈,操作自身,并将右子树入栈。 


优化写法的逻辑是

左节点压栈后因为在栈顶,立刻被第二次访问,因此可以用指针临时保存左节点,第二次访问后再压栈;这样栈内元素都是已被访问过两次的,就无需flag了。 

伪代码

// 先序遍历的常规写法 
push(root);
while(!empty){
	p=top();pop();
	print(p);
	if(p->right)push(p->right);
	if(p->left)push(p->left);
} 
// 先序遍历的优化写法,常数更小但更难理解。 
p=root;
while(true){
	while(p!=null){
		print(p);
		if(p->right)push(p->right);
		p=p->left;
	}
	if(empty)break;
	p=top();
	pop();
} 
//中序遍历的常规写法 
push(root)
while(!empty){
	p=top();
	if(p->flag){
		print(p);
		pop(p);
		push(p->right);
	}else{
		p->flag=true;
		push(p->left);
	}
}

// 中序遍历的优化写法 
p=root;
while(true){
	while(p!=null){
		push(p)
		p=p->left;
	}
	if(empty())break;
	p=top();
	pop();
	print(p);
	p=p->right;
} 


// 后序遍历
push(root);
while(!empty){
	p=top();
	if(p->flag){
		print(p);
		pop();
	}else{
		if(p->right)push(p->right);
		if(p->left)push(p->left);
		p->flag=true;
	}
}

// 后序遍历的优化写法
p=root;
while(true){
	while(p){
		push(p);
		p=p->left;
	}
	while(!empty&&(p=top())->flag){
		print(p);
		pop();
	}
	if(empty)break;
	p->flag=true;
	p=p->right;
}
//后序遍历的进一步优化写法 
p=root;
while(true){
	while(p){
		push(p);
		p=p->left;
	}
	while(!empty&&(p=top())->flag){
		print(p);
		pop();
	}
	if(empty)break;
	p->flag=true;
	p=p->right;
}


 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值