中序遍历是十分重要的遍历算法。针对BST而言,中序遍历给定了一个次序,将二叉树变成了一种线性结构,而且正好是升序排列(在BST中,语义规范不存在关键值相同的节点)。
- 仿照先序遍历的递归版
template<typename T,typename VST>void travIn_R(BinNodePosi(T) x,VST& visit){
if(!x) return;
travIn_R(x-lc,visit);
visit(x->data);
travIn_R(x-rc,visit);
}
将递归版算法改写为迭代版,右子树遍历属于尾递归,但是左子树却严格不是。
考察中序遍历二叉树的左侧通路,不难发现一个规律,遇到的左侧节点总不是最先访问的,而是会检查有没有右子树,如果有转向右子树,直到当前节点没有右兄弟,才访问该节点。则依照此思路,从根节点沿左侧通路一趟下行,沿右子树逐一遍历。这样,原问题就分解若干个右子树遍历的问题。
template<typename T,typename VST>void travIn_R(BinNodePosi(T) x,VST& visit){
Stack<BinNodePosi(T)> S;
while(true){
goAlongLeftBranch(x,S);
if(S.empty()) break;
x = S.pop(); visit(x-data);
x = x->rc;
}
}
template<typename T,typename vst>void goAlongLeftBranch(BinNodePosi(T) *x,Stack<BinNodePosi(T)&> S){
while(x){S.push(x);x=x->lc}
}
同理,可以借助辅助栈,重写上述算法。
template<typename T,typename VST>void travIn_R2(BinNodePosi(T) x,VST& visit){
Stack<BinNodePosi(T)> S;
while(true){
if(x) S.push(x);
x = x->lc;
else if(!S.empty()){
x = S.pop();
visit(x->data);
x = x->rc;
}
else break;
}
}
- 直接后继
中序遍历与其他遍历策略一样,将二叉树定义了一个次序的关系,则可以通过succ()接口来查找某个节点的直接后继
template<typename T>BinNodePosi(T) BinNode<T>::succ(){
BinNodePosi(T) x = this;
if(rc){
x = rc;
if(HasLChild(*x)) x = x-lc;
}
else{
while(IsRChild(*x)){
x = x->parent;
}
x = x->parent;
}
return x;
}
再有了直接后继之后,可以不再使用辅助栈来实现中序遍历。
template<typename T,typename VST>void travIn_R3(BinNodePosi(T) x,VST& visit){
bool backtrack = false;
while(true){
if(HasLChild(*x) && !backtrack){//如果有左孩子,而且没有回溯,深入左子树。
x = x-lc;
}
else{//刚刚发生回溯或无左子树。访问
visit(x->data);
if(HasRChild(*x)){//如果有右子树,按照一开始给定的策略,遍历右子树。
x = x->rc;
backtrack = false;
}
}
else{//如果右子树为空,遇到了最左侧的叶节点,回溯。
if(!x=x->succ()) break;
backtrack = true;//开启回溯标识。
}
}
}