自用_数据结构与算法——栈

栈与队列

source : 数据结构与算法【北京大学】

    • 定义

      • 后进先出( Last In First Out)
        • 一种限制访问端口的线性表
      • 主要操作
        • 进栈(push) 出栈(pop)
      • 应用
        • 表达式求值
        • 消除递归
        • 深度优先搜索
    • 栈的抽象数据类型

    template <class T>
    class Stack {
    public :                        //栈的运算集
        void clear();               //变为空栈
        bool push(const T item) ;   //item 入栈,成功则返回真,否则假
        bool pop(T& item) ;         //返回栈顶内容并弹出,成功则返回真,否则假
        bool top(T& item) ;         //返回栈顶但不弹出,成功则返回真,否则假
        bool isEmpty() ;            //若栈已空返回真
        bool isFull() ;             //若栈已满返回真
    }
    
    • 火车出站问题: http://poj.org/problem?id=1363

      solution: 运用合法的重构找冲突

    • 实现方式

      • 顺序栈

        • 创建
          template <class T> class arrStack : public Stack <T> {
          private:
              int mSize;                          //栈的顺序存储
              int top;                            //栈顶位置,应小于mSize
              T *st;                              //存放栈的数组
          public:                                 //栈的运算的顺序实现
              arrStack(int size){                 //创建一个给定长度顺序栈的实例
                  mSize = size;
                  top = -1;
                  st = new T[mSize];
                  arrStack(){                     //创建一个顺序栈的实例
                      top = -1;
                  }
                  ~arrStack() { delete [] st; }
                  void clear() { top = -1; }      //清空栈
              }
          }
          
        • (Copilot提示给了我一个奇怪的网站
        • 压入栈顶
          bool arrStack< T >::push(const T item) {
              if(top = mSize){
                  cout << "栈满溢出" << endl;
                  return false;
              }else{
                  st[++top] = item;
                  return true;
              }
          }
          
        • 出栈
          bool arrStack< T >::pop(const T item) {
              if(top = -1){
                  cout << "栈为空,不能执行出栈操作" << endl;
                  return false;
              }else{
                  item = st[top--];
                  return true;
              }
          }
          
      • 链式栈

        • 创建
          template <class T> class lnkStack : public Stack <T> {
          private:                    //栈的链式存储
              Link<T>* top;           //指向栈顶的指针
              int size;               //存放元素的个数
          public:                     //栈运算的链式实现
              lnkStack(int defsize){  //构造函数
                  top = NULL;
                  size = 0;
              }
              ~lnkStack() {           //析构函数
                  clear();
              }
          }
          
        • 压入栈顶
          bool lnksStack<T>::push(const T item) {
              Link<T>* tmp = new Link<T>(item, top);
              top = tmp;
              size++;
              return true;
          }
          Link(const T info, Link* nextValue) {
              data = info;
              next = nextValue;
          }
          
        • 从单栈弹出元素
          bool lnkStack<T>:: pop(T& item) {
              Link <T> *tmp;
              if(size == 0) {
                  cout << "栈为空,不能执行出栈操作"<< endl;
                  return false;
              }
              item = top->data;
              tmp = top->next;
              delete top;
              top = tmp;
              size--;
              return true;
          }
          
    • 链式栈与顺序栈的区别

      • 时间效率
        • 所有操作只需要常数时间
        • 顺序栈与链式栈不相上下
      • 空间效率
        • 顺序栈需要说明一个固定的长度
        • 链式栈长度可变,但需要增加额外的结构性开销
      • 实际应用中顺序栈比链式栈应用更广
        • 顺序栈容易根据栈顶位置进行相对位移,快速定位并读取栈的内部元素
        • 顺序栈读取内部元素的时间为O(1),而链式栈则需要沿着指针链游走,显然慢些,读取第k个元素需要时间为O(k)
      • 一般来说,栈不允许“读取内部元素”,只能在栈顶操作
    • STL中栈相关函数

      STLStandard Template Library)中关于栈的函数主要集中在头文件中。下面是一些常用的栈函数:

      • stack::push():将元素压入栈顶。
      • stack::pop():弹出栈顶元素。
      • stack::top():返回栈顶元素,但不删除。
      • stack::empty():返回栈是否为空。
      • stack::size():返回栈中元素的个数。
      • stack::swap():交换两个栈的内容。

      此外,栈还具有使用构造函数和析构函数进行初始化和销毁的功能。你可以使用stack<T>来定义一个特定类型T的栈对象,例如stack<int>定义一个整数类型的栈

      eg:

      #include <iostream>
      #include <stack>
      
      int main() {
          std::stack<int> myStack;
      
          myStack.push(1);
          myStack.push(2);
          myStack.push(3);
      
          std::cout << "栈中元素个数:" << myStack.size() << std::endl;
          std::cout << "栈顶元素:" << myStack.top() << std::endl;
      
          myStack.pop();
      
          std::cout << "栈顶元素:" << myStack.top() << std::endl;
          std::cout << "栈是否为空:" << (myStack.empty() ? "是" : "否") << std::endl;
      
          return 0;
      }
      

      结果应为:

      栈中元素个数:3
      栈顶元素:3
      栈顶元素:2
      栈是否为空:否
      
    • 栈的应用

      • 栈的特点:后进先出
        • 体现了元素之间的透明性
      • 常用来处理有递归结构的数据
        • 深度优先搜索
        • 表达式求值
        • 子程序/函数调用的管理
        • 消除递归
      • 递归到非递归的转换
        • 递归函数调用原理

          • 函数运行时的动态分配

            • 栈( stack ) 用于分配后进先出的LIFO的数据
              • 如函数调用
            • 堆( heap ) 用于不符合LIFO
              • 如指针所指向空间的分配

          • 函数调用及返回的步骤

            • 调用
              • 保存调用信息
              • 分配数据区
              • 控制转移给被调函数的入口
            • 返回
              • 保存返回信息
              • 释放数据区
              • 控制转移到上即函数(主调用函数)

          图形解释:

          • 所以我们能看出来,先调用后返回,所以用栈
        • 转换方法

          • 机械方法
            • 1、设置一工作栈当前工作记录

              • 在函数中出现的所有参数和局部变量都必须用栈中的相应的数据成员代替
                • 返回语句标号域
                • 函数参数
                • 局部变量
              typedef struct elem {   //栈数据元素类型
                  int rd;             //返回语句的标号
                  Datatypeofpl pl;    //函数参数
                  ...
                  Datatypeofpm pm;
                  Datatypeofql ql;    //局部变量
                  ...
                  Datatypeofqn qn;
              
              }
              

              • 2、设置t+2个语句标号

                • label 0 : 第一个可执行语句
                • label t+1 : 设在函数体结束处
                • label i (1<=i<=t) : 第i个递归返回处
              • 3、增加非递归入口

                //入栈
                S.push(t+1 , p1 , ... , pm , q1 , ...qn);
                
              • 4、替换第i(i=1,...,t)个递归规则

                • 假设函数体中第i(i=1,...,t)个递归调用语句为:recf(a1,a2,...am);
                • 则调用以下语句替换:
                  S.push(i,a1,...,am);    //实参进栈
                  goto label 0 ; 
                  ...
                  label i : x = S.top();S.pop();
                  /*退栈,然后根据需要,将x中某些支付给栈顶的工作记录S.top(),相当于把引用型参数的值传给局部变量*/
                  
              • 5、所有递归出口处增加语句

                    goto label t+1 ;
                
              • 6、标号为t+1的语句

                switch ((x=S。top ()).rd) {
                    case 0 :    goto label 0;
                                break;
                    case 1 :    goto label 1;
                                break;
                    /*以此类推*/
                    case t+1 :  item = S.top(); S.pop(); //返回
                                break;
                    default :   break;
                }
                
              • 7、改写循环和嵌套中的递归

                • 对于循环中的递归,改写成等价的goto型循环

                • 对于嵌套递归调用

                  例如,recf(... recf())
                  改为:

                  exmp1 = recf();
                  exmp2 = recf(exmp1);
                  /*...*/
                  exmpk = recf(exmp k-1);
                  

                  然后应用规则4解决

              • 8、优化处理

                • 进一步优化
                  • 去掉冗余进栈/出栈
                  • 根据流程图找出相应的循环结构,从而消去goto语句
              • 实例演示

                • 递归方法
                  void exmp(int n, int& f) {
                      int u1, u2;
                      if (n<2>){
                          f = n+1;
                      }else{
                          exmp((int)(n/2),u1);
                          exmp((int)(n/4),u2);
                          f = u1*u2;
                      }
                  }
                  
                • 转换为非递归

                  • 机械的递归转换
                    • 数据结构定义
                      typedef struct elem {
                          int rd,pn,pf,q1,q2;
                      }ELEM;
                      class nonrec {
                          private:
                              stack <ELEM> S;
                          public:
                              nonrec(void) {} //constructor
                              void replace(int n,int& f);
                      }
                      
                    • 递归入口
                      void nonrec::replacel(int n,int& f) {
                          ELEM x,tmp;
                          x.rd = 3;   x.pn = n;
                          S.push(x);      //压在栈底作“监视哨”
                      label0: if ((x = S.top()).pn < 2) {
                          S.pop( );
                          x.pf = x.pn + 1;
                          S.push(x);
                          goto label3;
                      }
                      }
                      
                    • 第一个递归语句
                      x.rd = 1;                           //第一处递归
                      x.pn = (int)(x.pn/2);
                      S.push(x);
                      goto label0;
                      label1: tmp = S.top(); S.pop();
                              x = S.top(); S.pop();
                              x.q1 = tmp.pf;             //修改u1=pf
                              S.push(x);
                      
                    • 第二个递归语句
                      x.pn = (int)(x.pn/4);
                      x.rd = 2;
                      S.push(x);
                      goto label0;
                      label2: tmp = S.top(); S.pop();
                              x = S.top(); S.pop();
                              x.q2 = tmp.pf;
                              x.pf = x.q1 * x.q2;
                              S.push(x);
                      label3: x=S.top();
                          switch(x.rd) {
                              case 1 : goto label1;
                                  break;
                              case 2 : goto label2;
                                  break;
                              case 3 : tmp = S.top(); S.pop();
                                  f = tmp.pf;         //计算结束
                                  break;
                              default : cerr<<"error label number in stack";
                                  break;
                          }
                      
                  • 优化后的非递归算法
                    void nonrec::replace2(int n,int& f) {
                        ELEM x, tmp;//入口信息
                        x.rd = 3; x.pn = n; S.push(x);
                        do {
                            //沿左边入栈
                            while ((x=S.top()).pn >= 2){
                                x.rd = 1;
                                x.pn = (int)(x.pn/2);
                                S.push(x);
                            }
                            x = S.top(); S.pop();//原出口
                            x.pf = x.pn + 1;
                            S.push(x);
                            //如果从第二个递归返回则上升
                            while((x = S.top()).rd==2) {
                                tmp = S.top(); S.pop();
                                x = S.top(); S.pop();
                                x.pf = x.q * tmp.pf;
                                S.push(x);
                            }
                            if((x = S.topValue()).rd == 1) {
                                tmp = S.top(); S.pop();
                                x = S.top(); S.pop();
                                x.q = tmp.pf; S.push(x);
                                tmp.rd = 2;//进入第二个递归
                                tmp.pn = (int)(x.pn/4);
                                S.push(tmp);
                            }
                        }while ((x = S.top()).rd != 3);
                        x = S.top(); S.pop;
                        f =x.pf;
                    }
                    
              • 递归转非递归的性能试验

                • 快排时间对照
                • 递归与非递归处理问题的规模
                  • 递归求f(x) :
                    int f(int x) {
                        if (x==0) return 0;
                        return f(x-1) + 1;
                    }
                    
                    • 在默认设置下,当x超过 11772 时会出现堆栈溢出
                  • 非递归求f(x),栈中元素记录当前x值和返回值
                    • 在默认设置下,当x超过 32375567 时会出现错误
posted @ 2023-08-03 21:43  Santerc  阅读(40)  评论(0)    收藏  举报