2.用基础知识实现递归转栈式访问

基于以上几点,我们怎么把所有的递归都用栈这个数据结构实现呢?

想必聪明的你已经有想法了吧!

例题 : (请先自己思考尝试一下怎么实现)

先给出二叉树节点定义 :

typedef struct BiTNode{
    char data;
    BiTNode* lchild,*rchild;
} *BiTree;

访问二叉树使用的函数 :

void visit(BiTNode * node)

 

如果觉得上述节点定义眼熟,那我们俩应该都是供液大学的。

题1.难度等级: C

使用栈实现二叉树的先序遍历

函数头:

void preOrderRead(BiTree tree);

 

题2.难度等级: B

使用栈实现二叉树的后续遍历

void postOrderRead(BiTree tree);

 

题3.难度等级: A

二叉树的节点含有成员变量M

找出二叉树中成员变量M为a 和 b 的节点的最小祖先节点(假设a b 只出现一次)

BiTNode* findNearestAncestor(BiTree tree, char a, char b);

题目1为例,思路开导与解析 :

首先我们需要写出大概的使用递归实现功能的代码。

题1:

void preOrderRead(BiTree tree){

  if(tree == NULL){

    return;

  }

  visit(tree);//3

  preOrderRead(tree -> lchild); //1

  preOrderRead(tree -> rchild);//2

}

怎么转换成栈实现呢?

从之前我们分析中知道,对函数的调用实际上是创建栈帧的过程,那么上图1,2处我们调用了两次函数,那么在这两处我们应该都要

用创建栈帧来代替。

问题是创建的栈帧里面应该包含什么内容呢?

应该包含这个栈帧创建到销毁过程中所有需要的信息。而这里的信息可能不是直接获得的,例如可能我们的栈帧中包含了一个指向父栈帧的指针,那么我们就可以和父栈帧

通信,而无需要把父栈帧中的某些变量之类的信息冗杂地包含到栈帧里来。

严谨一点地说,栈帧应该包含本栈帧创建到销毁过程中需要的所有信息的 “来源或者信息本身”。

还有更重要的一点,递归函数的方法体只有一个,也就是说,对说有的栈帧都要进行同一个操作,无论这个栈帧包含的信息有多么不一样!

所以,方法中对栈帧的处理至关重要,他将作用于所有栈帧。

要能够根据栈帧内包含的信息对信息不同的栈帧做出合理的操作。

返回我们的题目1。除去1和2这两个创建栈帧的过程。如果把当前方法的调用想成一个栈帧,那么我们在栈帧里需要执行的操作只是判断本栈帧的节点是否为空,不空就读取,仅此而已。

对应的,设计我们的函数实现.

在这里,我们把栈的元素直接设计为节点,因为节点的信息已经够我们完成所有操作(只有visit操作而已);

1.如果把栈帧的入栈想成函数调用,出栈想成函数返回,那么当栈为空的时候,函数调用就结束了。于是有了下面1处的判断栈是否是空的

2.你可能会问:子函数都没调用完,2处怎么就把父函数的栈帧出栈了呢?因为如果我们在把子函数栈帧入栈(调用子函数)前将父函数的所有操作都做了,并且子函数的栈帧不需要和父函数栈帧通信的话,那么父函数的栈帧没有存在在栈中的意义了,因为该执行的都执行完了,子函数也不需要他,子函数在栈中的顺序也不会变,好一可怜的老父亲。

在下面需要对栈帧做的所有操作只有visit,也就是访问他的节点,子函数栈帧入栈前(调用子函数)就可以把父函数的所有操作在3处完成了,没有其他操作要等待子函数栈帧出栈(返回)接着做,而且子函数的栈帧已经包含所有操作需要的信息了(BiTree),所以2处父栈帧直接出栈。

4,5两处子函数栈帧入栈,表示父函数递归调用子函数。注意要放右孩子 rchild先,因为栈是先入后访问,而且左孩子总是先于右孩子访问

void preOrderRead(BiTree tree){

    Stack stack;

       init(stack);

  stack.push(tree);

  while( ! empty(stack)){  // 1

    BiTNode* node = stack.pop();  // 2

          if(node == NULL){

      continue;

    }

     visit(node); // 3

       stack.push(tree -> rchild); // 4

    stack.push(tree -> lchild); // 5  

  }

}
12-29 11:00