对访问次数和压栈之间关系的思考
- 一个节点如果只用访问一次,那么在一开始访问的时候直接访问即可。
- 如果需要访问两次,那么就要入栈,这样才能返回来再访问一次。站内元素全都已经被访问了一次。
- 如果需要访问三次,那么不仅要入栈,还要标记它已经被访问了一次还是两次 。
后序遍历的三次访问:
1.作为当前节点的左孩子或者右孩子被访问,因为后序遍历此时不能做任何操作,只能入栈等待以后访问。
2.第二次被访问,此时对以该节点为根节点的子树后序遍历,依然不能操作本节点,而是将它的右孩子和左孩子入栈。
3.第三次被访问,此时已经操作完毕它的左子树和右子树,是时候操作它自己了。
// 在本程序中,所谓的操作就是将其输出,print。
后序遍历的优化写法:
- 1.while访问左节点,并入栈。这样栈里的元素都是已经访问过左孩子的。
- 2.第二次访问时,标记本身已经访问了两次,并访问其右节点。
- 3.第三次访问对自身进行操纵。
优化逻辑:
- 右节点可以根据父节点找到 ,因此没有必要提前入栈(这样可以减少栈的最大深度)
- 左节点入栈后立即被访问,因此可以用指针临时保存。维持栈内元素都是已经被访问过左节点的。也正是因为如此,我们不能将右节点提前入栈,因为它的左节点尚未被访问。
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;
}