zoukankan      html  css  js  c++  java
  • 栈论 : 递归与栈式访问,如何用栈实现所有递归操作(幼儿园题目篇,题目2)

    上一篇 :

    栈论 : 递归与栈式访问,如何用栈实现所有递归操作(幼儿园题目篇)

    题目2

    题目2和题目1最大的不同点是访问顺序变了。

    我们对应的伪代码应该如下:

      1,2,3表示的是先递归读取左子树,再是右子树,最后读取自己

      void postOrderRead(BiTree tree){
    
        if(tree == NULL){
    
          return;
    
        }
    
        preOrderRead(tree -> lchild); //1
    
        preOrderRead(tree -> rchild);//2
    
        visit(tree);//3
    
      }

    看完上一道题的解析,应该熟能生巧了吧~

    首先,我们列出每个栈帧应该具有的信息 :

    1.当前节点

    其次,我们理一下逻辑思路

    下面的左子函数 = 左节点的子函数

    首先,因为父函数中对节点的读取是在子函数退出之后的(3在1和2之后),所以父函数的栈帧在子函数栈帧入栈时不能出栈(不能退出),要等待子函数出栈,

    操作完3之后才能出栈。如果我们现在我们从栈中访问了一个节点(注意是访问,不是弹出,因为父栈帧不能随意弹出),因为是后序遍历,所以要访问左子树先

    也就是执行1处,所以总是要把包含左子节点的栈帧入栈。只有等到左子树是空才停止。

    但是现在有一个问题,当我们访问到一个节点,我们怎么知道他的子函数栈帧该不该创建呢(子函数调用),因为此时可能是子函数调用过并退出,当前栈帧才露出来给我们获取到。另一种是子函数还没有调用,现在的栈帧是刚创建的,需要马上调用子函数。总结来说就是我们在当前节点不知是该调用子函数还是自己退出。造成这种情况的原因是,因为函数是顺序执行的,即使在同一个栈帧中,这段栈帧对应的程序是可以知道当前程序执行到的行号的。也就是说知道是否该调用子函数。而我们从栈帧得到栈帧,如果不带有类似行号的信息,根本不知道是否该调用子函数。

    所以,栈帧里的信息,我们需要修改一下。

    每个栈帧应该具有的信息 :

    1.当前节点

    2.当前节点是否已经调用过左/右子函数

     

    第二点和行号有差不多的功能,但我们只需要是否调用过子函数的信息,行号太细了,可以但是没必要。

    那么用什么来存储这个信息呢?

    我的想法是用一个int型的变量,一个int型的变量一般是32位,也就是说他可以存储32个“是与否的信息”。

    我用最低位为1表示还需要将左子函数栈帧入栈(还没调用过),为0表示已经把左子函数栈帧入栈了。

    依次类推,第二位来对应右子函数。

    你可能会问我这样选是否合理,我个人觉得还是相对合理的。

    原因如下:

    1.首先存储信息的载体体积较小,只用一个变量,充分利用每一位的信息。

    2.其次是虽然每次获取信息都需要进行与掩码的操作(例如 A | (00000000 00000000 00000000 00000001) = 是否左子函数栈帧入栈),但是这样的操作耗时还是相对较少的。相比之下,如果我们用了很多个变量,频繁读取这些变量的时候,高速缓存的cache line 可能就会被提前填满,导致我们缓存的优势发挥效能降低,CPU运行速度下降。而且 | 操作在硬件层面讲是时间复杂度为O(1)的操作,因为每一位的信号都可以并行通过与门。而移位则需要等待下一位的触发器接受到上一位的触发器信息,上一位的触发器才能接受上上一位的触发器信息,存在等待问题,所以硬件层面的时间复杂度是O(n)。选与操作还是比较好的。当然,这只是从我有限的硬件知识推理分析的,并没有考虑操作系统之类的因素,如果有说错的地方请赐教。当然你也可以不运算,直接将这个int的不同值对应不同的情况,比如0表示调用左子函数,1不是不要,2表示调用右子函数,3表示不要......但是这样没有了0和1这样相反的思维逻辑条理性,而且情况一多处理麻烦。

     实现代码如下 :

    栈帧定义 :

    typedef struct FunctionFrame {
        BiTNode * node;
        int tag; // 标志是应该往右走还是往左走
    };

    具体实现

       Stack stack;
        FunctionFrame frame;
        int initial;
        
        initial = 0b0011; //初始值 表示两边都还要调用
        init(stack);
        frame = {&node, initial};
        push(stack, &frame);
    
        while (!stackEmpty(stack)) { //栈不空表示还有函数在调用中
            FunctionFrame* frame = getTop(stack); //不是弹出的访问
    
            if (frame -> tag & 0b0001) { //如果最低位是1
                if (frame ->node ->lchild != NULL) { //左孩子节点不为空
                    FunctionFrame* lc = (FunctionFrame*) malloc(sizeof(FunctionFrame)); //创建左子节点的函数栈帧
                    lc->node = frame->node->lchild;
                    lc->tag = initial; 
                    push(stack, lc); // 左子函数栈帧入栈
                    frame->tag =  frame->tag & 0b0010; //将最低位(调用左子函数的标志)抹除掉
                    continue; //左子函数栈帧入栈 右子函数就不要入了,因为要等待左子函数调用完右边才能调用
                }
                
            }
    
         //如上,以此类推
            if (frame ->tag & 0b0010) {
                if (frame->node->rchild != NULL) {
                    FunctionFrame* rc = (FunctionFrame*)malloc(sizeof(FunctionFrame));
                    rc->node = frame->node->rchild;
                    rc->tag = initial;
                    push(stack, rc);
                    frame->tag = frame->tag & 0b0001;
                    continue;
                }
                
            }
    
            visit(frame -> node);
            pop(stack);
        }

     下一篇 :

    栈论 : 递归与栈式访问,如何用栈实现所有递归操作(幼儿园题目篇,题目3)

    护眼绿:

    没人看的结语:

    首先很感谢你看到这里,辛苦了。

    文章中某些地方可能不正确或不准确,代码也可能不够高效可读,希望读者能够帮忙指正,共同学习进步。

  • 相关阅读:
    Python正则表达式re模块
    time,datetime,calendar模块
    Python的特殊属性和魔法函数
    Django环境搭建
    第十二篇 os模块
    第十一篇 logging模块
    Page Object设计模式
    实现自动发邮件功能
    cs61a Mutable Data 2 学习笔记和补充
    Lambda Expressions and Higher-Order Functions 学习笔记和习题解答
  • 原文地址:https://www.cnblogs.com/lqlqlq/p/11790486.html
Copyright © 2011-2022 走看看