• DS博客作业02--栈和队列


    0.PTA得分截图

    1.本周学习总结(0-4分)

    1.1 总结栈和队列内容

    一.栈

    • 栈的定义

    栈是一种只能在一端进行插入或删除操作的线性表,俗称:后进先出。表中允许进行插入、删除操作的一端称为栈顶。

    • 栈的进栈出栈规则:

    1.栈顶出栈->栈底最后出栈;

    2.时进时出->元素未完全进栈时,即可出栈。

    • 栈的分类:

    1.顺序栈

    利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针 top 指示栈顶元素在顺序栈中的位置,附设指针 base 指示栈底的位置。
    
    同样,应该采用可以动态增长存储容量的结构。如果栈已经空了,再继续出栈操作,则发生元素下溢,如果栈满了,再继续入栈操作,则发生元素上溢。
    
    栈底指针 base 初始为空,说明栈不存在,栈顶指针 top 初始指向 base,则说明栈空,元素入栈,则 top++,元素出栈,则 top--,
    
    故,栈顶指针指示的位置其实是栈顶元素的下一位(不是栈顶元素的位置)。
    

    2.链栈

    其实就是链表的特殊情形,一个链表,带头结点,栈顶在表头,插入和删除(出栈和入栈)都在表头进行,也就是头插法建表和头删除元素的算法。
    
    显然,链栈插入删除的效率较高,且能共享存储空间。
    
    • 栈的基本运算

      • InitStack(&s):初始化栈。构造一个空栈s。

      • DestroyStack(&s):销毁栈。释放栈s占用的存储空间。

      • StackEmpty(s):判断栈是否为空:若栈s为空,则返回真;否则返回假。

      • Push(&S,e):进栈。将元素e插入到栈s中作为栈顶元素。

      • Pop(&s,&e):出栈。从栈s中退出栈顶元素,并将其值赋给e。

      • GetTop(s,&e):取栈顶元素。返回当前的栈顶元素,并将其值赋给e。

    • 顺序栈的功能操作代码实现

    1.图像表示

    2.结构体定义

    typedef struct 
    {  ElemType data[MaxSize]; 
       int top;		//栈顶指针
    } Stack;
    typedef Stack *SqStack;
    

    3.基本运算

    <1>初始化栈initStack(&s)

    void InitStack(SqStack &s)
    {  s=new Stack;     
        s->top=-1;
      } 
    

    <2>销毁栈ClearStack(&s)

    void DestroyStack(SqStack &s)
    {
      delete s;
    }
    

    <3>判断栈是否为空StackEmpty(s)

    bool StackEmpty(SqStack s)
    {
      return(s->top==-1);
    }
    

    <4>进栈Push(&s,e)

    bool Push(SqStack &s,ElemType e)
    {
      if (s->top==MaxSize-1)  
    	return false;
       s->top++;		   //栈顶指针增1
       s->data[s->top]=e;	  
       return true;
    }
    

    <5>出栈Pop(&s,&e)

    bool Pop(SqStack &s,ElemType &e)
    {
       if (s->top==-1)	//栈为空的情况,栈下溢出
    	return false;
       e=s->data[s->top];//取栈顶指针元素     
       s->top--;		//栈顶指针减1
       return true;
    }
    

    <6>取栈顶元素GetTop(s)

    bool GetTop(SqStack *s,ElemType &e)
    {	
       if (s->top==-1)	//栈为空的情况    
        return false;
        e=s->data[s->top];	    
        return true;
    }
    

    4.顺序栈的四要素

    栈空条件:top=-1

    栈满条件:top=MaxSize-1

    进栈e操作:top++; st->data[top]=e

    退栈操作:e=st->data[top]; top--;

    • 链栈的功能操作代码实现

    1.图像表示

    2.结构体定义

    typedef int ElemType;
    typedef struct linknode
    {  ElemType data;			//数据域
       struct linknode *next;	//指针域
    } LiNode,*LiStack
    

    3.基本运算

    <1>初始化栈initStack(&s)

    void InitStack(LiStack &s)
    {  s=new LiNode;
       s->next=NULL;
    }
    

    <2>销毁栈ClearStack(&s)

    void DestroyStack(LiStack &s)
    { LiStack p;
       while (s!=NULL)
       {	  p=s; 	
           s=s->next;
           free(p); 
       }
     }
    

    <3>判断栈是否为空StackEmpty(s)

    bool StackEmpty(LiStack s)
    {
       return(s->next==NULL);
    }
    

    <4>进栈Push(&s,e)

    void Push(LiStack &s,ElemType e)
    {  LiStack p;
       p=new LiNode;
       p->data=e;		//新建元素e对应的节点*p
       p->next=s->next;	//插入*p节点作为开始节点
       s->next=p;
    }
    

    <5>出栈Pop(&s,&e)

    bool Pop(LiStack &s,ElemType &e)
    {  LiStack p;
       if (s->next==NULL)		//栈空的情况
    	return false;
       p=s->next;			//p指向开始节点
       e=p->data;
       s->next=p->next;		//删除*p节点
       free(p);				//释放*p节点
       return true;
    }
    

    <6>取栈顶元素GetTop(s,e)

    bool GetTop(LiStack s,ElemType &e)
    {  if (s->next==NULL)	//栈空的情况
    	return false;
       e=s->next->data;
       return true;
    }
    

    4.链栈的四要素

    栈空条件:s->next=NULL

    栈满条件:不考虑

    进栈e操作:结点插入到头结点后,链表头插法

    退栈操作:取出头结点之后结点的元素并删除之

    • 对于栈的C++模板类:stack
    #include<stack>
    
    1.stack<int>  s:初始化栈,参数表示元素类型
    
    2.s.push(t):入栈元素t
    
    3.s.top():返回栈顶元素
    
    4.s.pop():出栈操作只是删除栈顶元素,并不返回该元素
    。
    5.s1.empty(),当栈空时,返回true。
    
    6.s1.size():访问栈中的元素个数
    
    • 栈的应用

      • 网络浏览器多会将用户最近访问过的网址组织为一个栈。

        • 用户访问一个新页面,其地址会被存放至栈顶;而“后退”按钮,即可沿相反的次序访问此前刚访问过的页面。
      • 递归算法

      • 表达式求值--中缀表达式转后缀表达式

        • 中缀表达式:运算符号位于两个运算数之间。

        • 后缀表达式:运算符号位于两个运算数之后。

    二.队列

    • 队列的定义

    只允许在表的一端进行插入,而在表的另一端进行删除的线性表。

    • 队列的出队入队规则

    1.限定在表的一端插入、另一端删除。 插入的那头就是队尾,删除的那头就是队头。也就是说只能在线性表的表头删除元素,在表尾插入元素。

    2.先进先出。我们不能在表(队列)的中间操作元素,只能是在尾部进,在头部出去。

    • 队列的分类

    1.顺序队列

    用一组连续的存储单元依次存放队列中的元素。 
    

    2.链队列

    用链表表示的队列,限制仅在表头删除和表尾插入的单链表。
    
    一个链队列由一个头指针和一个尾指针唯一确定。(因为仅有头指针不便于在表尾做插入操作)。
    
    为了操作的方便,也给链队列添加一个头结点,因此,空队列的判定条件是:头指针和尾指针都指向头结点。
    

    3.环形队列(循环队列)

    把顺序队列首尾相连,把存储队列元素的表从逻辑上看成一个环,成为循环队列。
    
    • 队列的基本运算

      • InitQueue(&Q):构造一个空队列Q。

      • DestroyQueue(&Q):销毁队列Q。

      • QueueEmpty(Q):判断队列Q是否为空。

      • QueueLength(Q):返回Q的元素个数,即队列的长度。

      • GetHead(Q, &e):用e返回Q的队头元素。

      • ClearQueue(&Q):将Q清为空队列。

      • EnQueue(&Q, e):插入元素e为Q的新的队尾元素。

      • DeQueue(&Q, &e):删除Q的队头元素,并用e返回其值。

    • 顺序队列的功能操作代码实现

    1.结构体定义

    #define MAXQSIZE  100 //最大队列长度 
    typedef struct 
    { 
        QElemType *base; //初始化动态分配存储空间 
        int front, rear ;     /*队首、队尾*/ 
    }SqQueue; 
    SqQueue Q;
    

    2.基本运算

    <1>初始化队列InitQueue(&q)

    void InitQueue(SqQueue *&q){  
        q=(SqQueue *)malloc(sizeof(SqQueue));  
        q->front=q->rear=-1;  
    }  
    

    <2>销毁队列DestroyQueue(&q)

    void DestroyQueue(SqQueue *&q){  
        free(q);  
    }  
    

    <3>判断队列是否为空QueueEmpty(q)

    void QueueEmpty(SqQueue *q){  
        return (q->front==q->rear);  
    }  
    

    <4>enQueue(&q,e)

    bool enQueue(SqQueue *&q,ElemType e){    
        if(q->rear==MaxSize) return false;   //队满上溢   
        q->rear++;    
        q->data[q->rear]=e;    
        return true;    
    }   
    

    <5>出队列deQueue(&q,&e)

    bool deQueue(SqQueue *&q,ElemType &e){  
        if(q->front==q->rear) return false;  
        q->front++;  
        e=q->data[q->front];  
        return true;  
    }  
    

    3.顺序队列的四要素

    空队列条件:Q.front==Q.rear

    队列满:Q.rear-Q.front=m

    入队: Q.base[rear++]=x;

    出队:x=Q.base[front++];

    • 链队列的功能操作代码实现

    1.图像表示

    2.结构体定义

    typedef int QElemType;
    typedef struct QNode {// 结点类型
        QElemType data; 
        struct QNode *next;
    } QNode, *QueuePtr;
    
    typedef struct { // 链队列类 
        QueuePtr front;  // 队头指针 
        QueuePtr rear;   // 队尾指针 
    } LinkQueue;
    

    3.基本运算

    <1>创建结点

    QueuePtr createNode(int data){
        struct Node* newNode=(struct Node*)malloc(sizeof(struct Node));
        newNode->next=NULL;
        newNode->data=data;
        return newNode;    
    }; 
    

    <2>队列的初始化

    QueuePtr createQueue(){
        QueuePtr queue=new QNode;//分配内存空间 
        queue->front=queue->rear=NULL;//头指针和尾指针在一起为空 
        queue->queueSize=0;//队列大小为0 
        return queue;
    } 
    

    <3>入队操作

    void push(QueuePtr queue,int data){
        QueuePtr newNode=createNode(data);
        if(queue->queueSize==0)
            queue->front=newNode;
            else
            queue->rear->next=newNode;
            queue->rear=newNode;
            queue->queueSize++;
    } 
    

    <4>获取对头元素

    int queryFront(QueuePtr queue) {
        if(queue->queueSize==0){
            printf("队列为空无法获取对头元素");
            printf("
    "); 
            return -1; 
        }
        return queue->front->data;
    }
    

    <5>判断队列是否为空

    int empty(QueuePtr queue){
        if(queue->queueSize==0)
        return 0;
        else
        return 1;
    } 
    

    <6>出队操作

    void pop (QueuePtr queue){
        if(queue->queueSize==0){
        printf("队列为空不能出队");
        exit(0);
         }else{
             QueuePtr newFrontNode=queue->front->next;
             free(queue->front); 
             queue->front=newFrontNode;
             queue->queueSize--;
         }
    }
    
    • 循环队列

    1.图像表示

    2.循环队列的产生

    当使用顺序队列时,会遇到明明队中还有位置却不能入队的情况

    这是因为采用rear==MaxSize-1作为队满条件的缺陷。

    当队满条件为真时,队中可能还有若干空位置。这种溢出并不是真的溢出,称为假溢出。

    为了解决这种情况下造成的假溢出

    把数组的前端和后端连接起来,形成一个环形的顺序表,即把存储队列元素的表从逻辑上看成一个环,称为环形队列或循环队列。

    3.循环队列的入/出队列运算(利用“模运算”)

    • 入队:Q.rear=(Q.rear+1)%m

    • 出队:Q.front=(Q.front+1)%m

    4.循环队列的四要素

    队空条件:front=rear

    队满条件:(rear+1)%MaxSize=front

    进队e操作:rear=(rear+1)%MaxSize;将e放在rear处;

    出队操作:front=(front+1)%MaxSize;取出front处元素e;

    • 对于队列的C++模板类:queue
    1.push():将x入队,时间复杂度为O(1)
    
    2.front()back():分别可以获得队首元素和队尾元素,时间复杂度为O(1)
    
    3.pop():令队首元素出队,时间复杂度为O(1)
    
    4.empty():检查queue是否为空,返回true则空,返回false则非空,时间复杂度为O(1)
    
    5.size():返回queue内元素的个数,时间复杂度为O(1)
    
    • 队列的应用

      • 重复密钥

      • 凯撒加密

        • 通过将字母按顺序推后3位起到加密作用
      • 图的宽度优先搜索法

      • 操作系统的作业调度,用于缓冲区,网络中的路由缓冲包区。

    1.2.谈谈你对栈和队列的认识及学习体会。

    栈(Stack)是限定仅在表尾进行插入和删除操作的线性表。

    队列(Queue)是允许在一端进行插入,一端进行删除操作的线性表。

    它们都可以用线性表的顺序存储结构来实现,但都存在着顺序存储的一些弊端。

    对于栈来说,如果是两个相同数据类型的栈,则可以用数组的两端作栈底的方法来让两个栈共享数据,这就可以最大化利用数组的空间。

    对于队列来说,为了避免插入删除需要移动数据,于是就引入了循环队列,使得队头和队尾可以在数组中循环变化。

    解决了移动数据的时间损耗,使得本来插入和删除是O(n)的时间复杂度变成O(1)。

    在学习了栈和队列的相关内容之后,在场景的应用方面又得到了一定的拓展,面对之前无法解决的情况也得到了新的方法。
    在接下来对存储结构操作的不断学习中,应付不同问题的手法也将越来越熟练。

    2.PTA实验作业(0-2分)

    2.1.题目1:7-5 表达式转换 (25分)

    2.1.1代码截图




    2.1.2本题PTA提交列表说明。

    Q1:没有考虑到当表达式头或左括号后为+-符号时对应的操作,应加上该情况下对应的操作

    Q2:在当经过在表达式头或左括号后为+-号后,没有将flag1、2的值赋为1,从而导致非指定条件下进入错误的判断

    2.2 题目2:7-7 银行业务队列简单模拟 (25分)

    2.2.1代码截图


    2.2.2本题PTA提交列表说明。

    Q1:没有判断输出空格的条件,导致末尾留有空格而格式错误

    Q2:在输出空格的判断条件中忽略了A、B至少有一者非空就要输出空格的整体性

    3.阅读代码(0--4分)

    3.1 题目及解题代码

    题目

    解题代码

    class Solution {
    public:
        bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
            stack<int> st;
            int n = popped.size();
            int j = 0;
            for (int i = 0; i < pushed.size(); ++i){
                st.push(pushed[i]);
                while(!st.empty() && j < n && st.top() == popped[j]){
                    st.pop();
                    ++j;
                }
            }
            return st.empty();
        }
    };
    

    3.1.1 该题的设计思路

    时间复杂度:O(N)

    空间复杂度:O(N)

    3.1.2 该题的伪代码

    初始化栈 stack,j = 0;
    
    遍历 pushed 中的元素 x;
    
    当 j < popped.size() 且栈顶元素等于 popped[j]:
    
    弹出栈顶元素;
    
    j += 1;
    
    如果栈为空,返回 True,否则返回 False。
    

    3.1.3 运行结果

    符合情况

    不符情况

    3.1.4分析该题目解题优势及难点。

    解题优势:

    思路很简单,尝试按照 popped 中的顺序模拟一下出栈操作,如果符合则返回 true,否则返回 false。
    这里用到的贪心法则是如果栈顶元素等于 popped 序列中下一个要 pop 的值,则应立刻将该值 pop 出来。

    难点:

    使用一个栈 st 来模拟该操作。
    将 pushed 数组中的每个数依次入栈,在入栈的同时需要判断这个数是不是 popped 数组中下一个要 pop 的值。
    如果是就把它 pop 出来。
    最后检查栈是否为空。

    3.2 题目及解题代码

    题目

    解题代码

    using PII = pair<int, int>;
    class Solution {
    public:
        bool canMeasureWater(int x, int y, int z) {
            stack<PII> stk;
            stk.emplace(0, 0);
            auto hash_function = [](const PII& o) {return hash<int>()(o.first) ^ hash<int>()(o.second);};
            unordered_set<PII, decltype(hash_function)> seen(0, hash_function);
            while (!stk.empty()) {
                if (seen.count(stk.top())) {
                    stk.pop();
                    continue;
                }
                seen.emplace(stk.top());            
                auto [remain_x, remain_y] = stk.top();
                stk.pop();
                if (remain_x == z || remain_y == z || remain_x + remain_y == z) {
                    return true;
                }           
                stk.emplace(x, remain_y);            
                stk.emplace(remain_x, y);   
                stk.emplace(0, remain_y);        
                stk.emplace(remain_x, 0);           
                stk.emplace(remain_x - min(remain_x, y - remain_y), remain_y + min(remain_x, y - remain_y));           
                stk.emplace(remain_x + min(remain_y, x - remain_x), remain_y - min(remain_y, x - remain_x));
            }
            return false;
        }
    };
    

    3.2.1 该题的设计思路

    时间复杂度:O(xy)

    空间复杂度:O(xy)

    3.2.2 该题的伪代码

    stack<PII> stk;
    stk.emplace(0, 0);
    以 remain_x, remain_y 作为状态,表示 X 壶和 Y 壶中的水量。
    HashSet存储所有已经搜索过的 remain_x, remain_y 状态,
    保证每个状态至多只被搜索一次。
    while (!stk.empty())
        把 X 壶灌满。
        把 Y 壶灌满。
        把 X 壶倒空。
        把 Y 壶倒空。
        把 X 壶的水灌进 Y 壶,直至灌满或倒空。
        把 Y 壶的水灌进 X 壶,直至灌满或倒空。
    end while
    返回flase
    

    3.2.3 运行结果

    符合情况:

    不符情况:

    3.2.4分析该题目解题优势及难点。

    解题优势:

    在实际的代码编写中,由于深度优先搜索导致的递归远远超过了 Python 的默认递归层数,
    因此下面的代码使用栈来模拟递归,避免了真正使用递归而导致的问题。

    难点:

    在每一步搜索时,会依次尝试所有的操作,递归地搜索下去。这可能会导致陷入无止境的递归。

  • 相关阅读:
    Mysql 修改默认端口
    通过.pro文件生成C++工程
    内联函数知识点
    DICOM文件添加私有Tag(DCMTK Private Tag)
    poj 1185 炮兵阵地 状压dp
    cf #216 C
    cdoj1365 木杆上的蚂蚁
    cf #214 Dima and Salad
    cf #213 Matrix
    hdu 2222 Keywords Search(AC自动机入门题)
  • 原文地址:https://www.cnblogs.com/yushanbaiyi/p/12538436.html
Copyright © 2020-2023  润新知