zoukankan      html  css  js  c++  java
  • 邓俊辉数据结构学习-3-栈

    栈的学习

    栈的应用场合

    1. 逆序输出

      • 输出次序与处理过程颠倒,递归深度和输出长度不易预知
      • 不是很理解
      • 实例:进制转换
      • 大致思路:对于进制转换,我们一般使用的都是长除法,因此要保存每次得到的余数,但是最后算下来
        新的进制的数,刚好和存储的时候是逆序的。因此利用栈输出即可。原因是栈是FiLo后进先出。
    2. 递归嵌套

      • 具有自相似性的问题可递归描述,但分支位置和嵌套深度不固定
      • 实例:括号匹配,
      • 实例:栈混洗
      • 联系,n个括号构成的合理种类和n个元素栈混洗的数目是一致的。
    3. 延迟缓存

      • 线性扫描算法模式中,在预读足够长之后,方能确定可处理的前缀。
      • 表达式求值
      • 求RPN, 中缀转后缀表达式
      • 这里核心的概念是优先级表的构造, 对于表达式求值来说,比如说输入10个数,我们并不能确定其全部的
        计算顺序,只能确定其部分比如, (1+2)*3-5/6....我们知道可以优先计算1+2是,然后可以计算乘法,接下来
        除法该不该计算取决于后面的符号。这种知道谁该先计算就是优先级表的概念。因为我们直到遇到)才可以去
        计算+法,此时我们必须将+先存储起来,这里就是使用栈进行村粗。
      • 求后缀表达式的核心和表达式求值是一个道理。
    4. 栈式计算(完全不了解)

      • 基于栈结构的特定计算

    解题思路: 应用充分性而不是必要性。

    https://www.zhihu.com/question/30469121

    举例: 我们家人很高,充分条件,我们家人,即当人是我们家人的时候,结论身高很高。

    当满足条件A,就可以推出结论B

    结婚需要买房, 买房即使结婚的必要条件。
    根据结论B,可以推出条件A,同时也可以推出C,结婚只是一个条件之一。

    栈的小结

    在很多的算法中,我们往往需要构造一个辅助栈来帮帮助我们延迟缓存和逆序输出。对于逆序输出来说,这个没什么好说的,
    就是要记得有这个逆序的思想。最为经典的我认为莫过于二叉树的逆层次遍历。完全利用倒置的思想。还有就是回朔法,也是
    利用栈的这个逆序的思想,深度优先遍历算是回朔法的一种吧。

    递归嵌套,理解的不深

    栈的习题

    N皇后问题

    struct Queen 
    { 
    public: 
        Queen () = default;
        Queen (int xx, int yy)
        : x(xx)
        , y(yy) {}
         
        bool operator==(const Queen &rvalue)
        {
            if(x == rvalue.x || 
               y == rvalue.y ||
               x + y == rvalue.x + rvalue.y ||
               y - x == rvalue.y - rvalue.x)
                return true;
            return false;
        }
    
        int x;
        int y;
    }; 
    std::ostream & operator<<(std::ostream  &os, const Queen q)
    {
        os << q.x << "," << q.y ;
        return os;
    } 
    
    const int N = 10;
    int chessboard[N][N];
    
    // 整体思路
    // 1 如果皇后和已放置皇后序列不冲突,则加入皇后序列, 行号+1 goto 2, 否则改变当前皇后列的位置 goto 1
    // 2 每行放置一个皇后
    // 3 如果在皇后没有放完的情况下,无法放置。则重新改变上一个皇后的列 goto 1。
    
    void placeQueen()
    {
        vector<Queen> s;
        Queen q(0, 0);
        while(s.size() < N)
        {
            while(count(s.begin(), s.end(), q) && q.y < N)
            {
                ++q.y;
            }
            if( q.y < N)
            {
                s.push_back(q);
                ++q.x;
                q.y = 0;
            }
            else{
                if(!s.size())
                {
                    cout << "no solution" << endl ;return ;
                }
                q = s[s.size() - 1];
                s.pop_back();
                ++q.y;
            }
        }
        for(auto &a : s)
        {
            cout << a << endl;
        }
    }
    

    迷宫路径问题

    using std::stack; 
    // 迷宫初始可用状态, 形成路径状态, 回朔状态, 墙
    typedef enum{Available, Route, BaskTrack, Wall} Status;
    
    // unknown 为未定方向,即在没有进行方向搜索之前的状态 
    // 这个设置很棒,这样在转向下一个方向的时候,只要加一下就可以。
    typedef enum{Unkonwn, East, South, West, North, Noway} ESWN; // 定义的方向
    
    // 返回下一个方向
    inline ESWN nextESWN (ESWN eswn) { return ESWN(eswn + 1);}
    
    struct Cell{
        int x, y;
        Status status;
        ESWN ingoing, outgoing; 
        Cell() = default;
        Cell(int xx, int yy, Status s = Available, ESWN i = Unkonwn, ESWN o = Unkonwn )
        : x(xx), y(yy), status(s), ingoing(i), outgoing(o) {}
        bool operator==(const Cell &rvalue)
        {
            if(x == rvalue.x && y == rvalue.y)
                return true;
            return false;
        }
    };
    
    #define Maxsize 24
    
    Cell laby[Maxsize][Maxsize];
    
    int randLaby(Cell *&startCell, Cell *&goalCell)
    {
        srand(time(NULL));
        int ret;
        int size = Maxsize/2 + rand()%(Maxsize/2);
        for(int i = 0; i < size; ++i)
        {
            for(int j = 0; j < size; ++ j)
            {
                laby[i][j] = Cell(i, j, Wall);
            }
        }
        for(int i = 1; i < size - 1; ++i)
        {
            for(int j = 1; j < size - 1; ++j)
            {
                ret = rand() % 4;
                if(ret)
                {
                    // 3/4 设置为可用
                    laby[i][j] = Cell(i,j, Available);
                }
            }
        }
        // 设置起始点 和 结束点
        startCell = &laby[rand() % (size - 2) + 1][rand() % (size - 2) + 1];
        startCell->status = Available;
        goalCell = &laby[rand() % (size - 2) + 1][rand() % (size - 2) + 1];
        goalCell->status = Available;
        return size;
    }
    
    inline Cell* neighbour(Cell *c)
    {
        switch(c->outgoing)
        {
            case East :
                return &laby[c->x][c->y + 1];
            case South :
                return &laby[c->x + 1][c->y];
            case West :
                return &laby[c->x][c->y - 1];
            case North :
                return &laby[c->x - 1][c->y];
            default:
                exit(-1);
        }
        return nullptr;
    }
    inline Cell* advance(Cell *c)
    {
        switch(c->outgoing)
        {
            case East:
                c = &laby[c->x][c->y+1];
                c->ingoing = East;
                break;
            case South:
                c = &laby[c->x+1][c-> y];
                c-> ingoing = South;
                break;
            case West:
                c = &laby[c->x][c->y-1];
                c-> ingoing = West;
                break;
            case North:
                c = &laby[c->x-1][c->y];
                c-> ingoing = North;
                break;
            default:
                exit(-1);
        }
        return c;
    }
    
    void display(int size, Cell start, Cell goal)
    {
        system("clear");
        for(int i = 0; i < size; ++i)
        {
            for(int j = 0; j < size; ++j)
            {
                switch(laby[i][j].status)
                {
                    case Route : 
                        printf("*");
                        break;
                    case Wall:
                        printf("#");
                        break;
                    case BaskTrack:
                        printf("x");
                        break;
                    case Available:
                        printf(" ");
                        break;
                    default :
                        printf("-");
                }
                if(laby[i][j] == start)
                   printf("S");
                else if(laby[i][j] == goal)
                   printf("E");
            }
            printf("
    ");
        }
    }
    bool findPath(Cell *start, Cell *goal, int size)
    {
        if(start->status != Available || goal->status != Available)
        {
           return true;
        }
        stack<Cell*> s;
        start->status = Route;
        s.push(start);     
        do{
            // 展示迷宫
            display(size, *start, *goal);
            getchar();
            Cell *c = s.top();
            // 开始试探
            if(c == goal)
                return true;        
            // 试探所有方向
            while((c-> outgoing = nextESWN(c-> outgoing)) < Noway)
            {
                // 判断这个方向是否有路
                if(Available == neighbour(c)-> status) break;
            }
            // 如果所有方向都不行,就回朔
            if(c-> outgoing >= Noway && !s.empty())
            {
                c-> status = BaskTrack;
                c = s.top();
                s.pop();
            }
            else if( c->outgoing >= Noway && s.size() <= 1)
            {
                printf("failed
    ");
                exit(-1);
            } 
            else{
                s.push( c = advance(c));
                c-> outgoing = Unkonwn;
                c-> status = Route;
            }
        }while(!s.empty());
        return false;
    }
    
    

    哇,这里不得不佩服邓俊辉老师这个题的思路,非常容易理解,而且非常好阅读

    其主要思路为: 而且关键的一点是要明白我们是对这个迷宫地图做文章

    1. 将start节点加入Path中。
    2. 检查Path.top() 的四个方向,寻找一个可以走的方向。
      1. 如果没有,将Path.top()的状态设置为BackTrack(这样下一次检查就会pass这个方向),然后Path.pop()
      2. 如果有,走向这个方向,并将当前节点的状态设置为Route,然后将当前节点加入到Path中
    3. 如果Path不为空 goto 2。

    因此根据这个思路可以改写成我自己的方式。首先是迷宫的初始化,墙设置为'#',可以走的路设置为' ',
    已经走过的路设置为'',回朔过不能走的路标记为'!'。那么我判断上下左右能不能走只要去判断这个二维
    向量的值是否为' '就可以了。走到这一步,就将其加入路径设置为'
    '带表我走过。如果刚才的上下左右都
    不能走,将其标记为'!',然后pop掉Path.top(),再重新取Path中的top来进行下一轮判断。代码如下。

    const int Maxsize = 24;
    char laby[Maxsize][Maxsize];
    
    using Point = pair<int, int>;
    class Laby 
    { 
    public:
    public: 
        Laby () {   init();     }
        void init()
        {
            srand(time(NULL));
            size = Maxsize / 2 + rand() % (Maxsize / 2);
            start.first = rand() % (size - 2) + 1;
            start.second = rand() % (size - 2) + 1;
            goal.first = rand() % (size - 2) + 1;
            goal.second = rand() % (size - 2) + 1;
            for(int i = 0; i < size; ++i)
            {
                for(int j = 0; j < size; ++j)
                {
                    laby[i][j] = '#';
                }
            }
            for(int i = 1; i < size - 1; ++i)
            {
                for(int j = 1; j < size - 1; ++j)
                {
                    if(rand() %4)
                        laby[i][j] = ' ';
                }
            }
            laby[start.first][start.second] = ' ';
            laby[goal.first][goal.second] = ' ';
        }
    
        void display()
        {
            system("clear");
            for(int i = 0; i < size; ++i)
            {
                for(int j = 0; j < size; ++j)
                {
                    if(i == goal.first && j == goal.second)
                        printf("G");
                    else
                        printf("%c", laby[i][j]);
                }
                printf("
    ");
            }
        }
    
        Point findAvailableDirection(Point p){
            for(int i = 0; i < 4; ++i)
            {
                if(laby[p.first + 1][p.second] == ' ')
                    return std::make_pair(p.first + 1, p.second);
                if(laby[p.first - 1][p.second] == ' ')
                    return std::make_pair(p.first - 1, p.second);
                if(laby[p.first][p.second + 1] == ' ')
                    return std::make_pair(p.first , p.second + 1);
                if(laby[p.first][p.second - 1] == ' ')
                    return std::make_pair(p.first , p.second - 1);
            }
            return std::make_pair(-1, -1);
        }
    
        bool findPath()
        {
            stack<Point> path;
            path.push(start);
            laby[start.first][start.second] = '*';
            do{
                display();
                getchar();
                Point tmp = path.top();
                if(tmp == goal)
                    return true;
                // 首先要找当前点中一个可以的方向, 如果四个方向都不行就返回!
                auto ret = findAvailableDirection(tmp);
                if(ret == Point(-1,-1))
                {
                    // 需要回朔
                    laby[tmp.first][tmp.second] = '!';
                    path.pop();
                }
                else{
                    laby[tmp.first][tmp.second] = '*';
                    path.push(ret);
                }
            }while(!path.empty());
            return false;
        }
         
    private: 
        Point start;
        Point goal;
        int size;
    }; 
    
    bool operator==(Point a, Point b)
    {
        if(a.first == b.first && a.second == b.second)
            return true;
        return false;
    }
    

    表达式求值

    表达式求值基本上说只有一个核心问题,就是优先级表的问题。我们维护俩个栈,一个位操作符栈,
    一个位操作数栈,在操作符栈的进栈过程中。我们要为了进行优先计算,让低优先级的操作符可以触发
    一些列高优先级的操作符,从而进行运算。达到我们想要的优先级高先进行计算的目的

    #define N_OPTR 9
    using Operator = enum {Add, Sub, Mul, Div, Pow, Fac, Lp, Rp, Eoe};
    
    const char pri[N_OPTR][N_OPTR] = {
             /*add  sub  mul  div  pow  fac  lp   rp   eoe*/
    /*add*/    {'>', '>', '<', '<', '<', '<', '<', '>', '>'},
    /*sub*/    {'>', '>', '<', '<', '<', '<', '<', '>', '>'},
    /*mul*/    {'>', '>', '>', '>', '<', '<', '<', '>', '>'},
    /*div*/    {'>', '>', '>', '>', '<', '<', '<', '>', '>'},
    /*pow*/    {'>', '>', '>', '>', '>', '<', '<', '>', '>'},
    /*fac*/    {'>', '>', '>', '>', '>', '>', ' ', '>', '>'},
    /*lp*/     {'<', '<', '<', '<', '<', '<', '<', '=', ' '},
    /*rp*/     {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
    /*eoe*/    {'<', '<', '<', '<', '<', '<', '<', ' ', '='},
     };
    
    using namespace std;
    
    void readNumber(char *&s, Stack<float> &opnd)
    {
        opnd.push(static_cast<float>(*s - '0'));
        while(isdigit(*(++s)))
        {
            opnd.push(opnd.pop() * 10 + *s - '0');
        }
        if(*s != '.')
            return ;
        float fraction = 1;
        while(isdigit(*++s))
        {
            fraction = fraction / 10;
            opnd.push(opnd.pop() + (*s - '0') * fraction);
        }
    }
    
    int convert(char c)
    {
        Operator op;
        switch(c)
        {
            case '+' : op = Add; break;
            case '-' : op = Sub; break;
            case '*' : op = Mul; break;
            case '/' : op = Div; break;
            case '^' : op = Pow; break;
            case '!' : op = Fac; break;
            case '(' : op = Lp;  break;
            case ')' : op = Rp;  break;
            case '$' : op = Eoe; break;
            case '#' : op = Eoe; break;
        }
        return op;
    }
    
    float eval(float op1, char op, float op2)
    {
        switch(op)
        {
            case '+' :
                return op1 + op2;
            case '-' :
                return op1 - op2;
            case '*' :
                return op1 * op2;
            case '/' : 
                return op1 / op2;
            case '^' :
                return pow(op1, op2);
        }
        return -1;
    }
    
    float fac(float num) // 懒得写了,递归了
    {
        if(num == 0)
            return 1;
        return num * fac(num - 1);
    }
    float eval(char op, float op1)
    {
        if(op == '!')
            return fac(op1);
        return -1;
    }
    
    void process(char *&s, Stack<char> &optr, Stack<float> &opnd)
    {
        int rop = convert(*s);
        int lop; 
        lop = convert(optr.top());
        switch(pri[lop][rop])
        {
            case '<' :
                optr.push(*s);
                ++s;
                break;
            case '>' :
                if(optr.top() == '!')
                {
                    float op1 = opnd.pop();
                    char opchar = optr.pop();
                    opnd.push(eval(opchar, op1));
                }
                else{
                    float op2 = opnd.pop(); float op1 = opnd.pop();
                    char opchar = optr.pop();
                    opnd.push(eval(op1, opchar, op2));
                }
                break;
            case '=' :
                optr.pop(); 
                ++s;
                break;
            case ' ' ://非法情况
                cout << "illegal expression" << endl;
                exit(-1);
        }
    }
    
    float evaluate(char * S)
    {
        Stack<float> opnd; 
        Stack<char> optr;
        optr.push('#');
        char *idx = S;
        while(!optr.empty())
        {
            if(isdigit(*idx))
            {
                readNumber(idx, opnd);// 这个操作会导致idx恒定每次递增
            }
            else{
                process(idx, optr, opnd);// 这个操作不一定导致idx每次递增
            }
        }
    
        return opnd.top();
    }
    
    void testReadNumber()
    {
        char str[100];
        scanf("%s", str);
        char *ss = str;
        Stack<float> opnd;
        readNumber(ss, opnd);
        cout << opnd.top() << endl;
    }
    int main()
    {
        char str[1000];
        while(scanf("%s", str))
        {
            printf("%f
    ", evaluate(str));
            memset(str, 0, sizeof(str));
        }
        return 0;
    }
    
  • 相关阅读:
    LOJ 3055 「HNOI2019」JOJO—— kmp自动机+主席树
    LOJ 2586 「APIO2018」选圆圈——KD树
    bzoj 3600 没有人的算术——二叉查找树动态标号
    bzoj 1257 余数之和 —— 数论分块
    bzoj 3998 弦论 —— 后缀自动机
    bzoj 2946 公共串 —— 后缀自动机
    bzoj 4032 [ HEOI 2015 ] 最短不公共子串 —— 后缀自动机+序列自动机
    bzoj 2555 SubString —— 后缀自动机+LCT
    洛谷 P3804 [模板] 后缀自动机
    洛谷 P4106 / bzoj 3614 [ HEOI 2014 ] 逻辑翻译 —— 思路+递归
  • 原文地址:https://www.cnblogs.com/patientcat/p/9720284.html
Copyright © 2011-2022 走看看