zoukankan      html  css  js  c++  java
  • 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 的默认递归层数,
    因此下面的代码使用栈来模拟递归,避免了真正使用递归而导致的问题。

    难点:

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

  • 相关阅读:
    [saiku] 系统登录成功后查询Cubes
    216. Combination Sum III
    215. Kth Largest Element in an Array
    214. Shortest Palindrome
    213. House Robber II
    212. Word Search II
    211. Add and Search Word
    210. Course Schedule II
    分硬币问题
    开始学习Python
  • 原文地址:https://www.cnblogs.com/yushanbaiyi/p/12538436.html
Copyright © 2011-2022 走看看