zoukankan      html  css  js  c++  java
  • DS博客作业02--栈和队列

    0.PTA得分截图

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

    1.1 总结栈和队列内容

    顺序栈

    · 结构体定义:

    
    typedef struct
    {
        int data[MAXSIZE];
        int top;//栈顶元素位置
    }SqStack,*Stack;
    
    

    · 注意事项:栈只能在栈顶进行出栈(pop)和入栈(push)操作,并且是先进先出的。
    · 顺序栈的图示:

    · 顺序栈的三种状态:

    · 初始状态时,栈顶指针的值为-1(因为栈空条件),当top=MAXSIZE-1时栈满,进栈top+1,出栈top-1。
    · 顺序栈的几种操作:
    (1)初始化栈:

    void InitStack(Stack &S)
    {
         S=(SqStack*)malloc(sizeof(SqStack));   
         S->top=-1;
    }
    
    

    (2)进栈:

    bool PushStack(Stack &S,int e)
    {
        if(s->top==MAXSIZE)return false;//当栈达到最大容量
        S->top++;
        S->data[S->top]=e;
        return true;
    }
    
    

    (3)出栈:

    
    bool PopStack(Stack &S,int e)
    {
        if(s->top==-1)return false;//当栈空时,栈下溢
        e=S->data[S->top];
        S->top--;
        return true;
    }
    
    

    (4)销毁栈:

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

    链栈:

    · 结构体定义:

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

    · 链栈图示(带头节点):

    · 链栈的四要素:
    栈空条件:S->next==NULL;
    进栈操作:使用头插法,将节点插到头结点后。
    出栈操作:取出头结点后的第一个节点,并释放内存。
    栈空条件:由于链栈采用链结构,故不用考虑栈满。

    · 链栈的几种操作:
    (1)链栈的初始化:

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

    对应的C++模板操作:stack<类型> 名称;例如:stack S;

    (2)进栈:

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

    对应的C++模板操作:s.push(e);//入栈元素e。
    图示:

    (3)出栈:

    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;
    }
    
    

    对应的C++模板操作:S.pop();这里要和S.top区分一下,前者做的是物理删除,并不返回栈顶元素的值,后者返回栈顶元素的值。
    图示:

    (4)取栈顶元素:

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

    对应的C++模板操作:3.s.top();//返回栈顶元素

    (5)判断栈是否为空:

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

    对应的C++模板操作:s.empty();//当栈空时,返回true。

    (6)销毁栈:

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

    栈的应用

    1.栈和递归:

    递归过程退回的顺序是它前行顺序的逆序。显然这符合栈的存储方式。简单的说,就是在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压如栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。

    2.进制转换

    在计算机中存储的数据都是二进制,所以往往需要把十进制数据转换成二进制,转换的过程实际就是除2取余数,这其中我们可以看到最先求得余数实际是个位数,书写一个数据的时候都是先书写高位的数据,而后依次到个位。这正好和栈后进先出的特性吻合,因此可以使用栈来存储。

    3.表达式求值(中缀转后缀)

    首先置操作数数组postexp为空,表达式的起始符'='为运算符栈OP的栈底元素;if(是数字)postexp[]=OP.top(),OP.pop();if(是运算符) 与OPTR栈顶元素进行比较,按优先级进行操作;
    (1)if栈顶元素<输入算符,则算符压入OP栈,并接收下一字符
    (2)if栈顶元素=运算符但≠‘=’,则脱括号(弹出左括号)并收下一字;
    (3)if栈顶元素>运算符,则退栈、按栈顶计算,将结果压入OP栈。
    (4)且该未入栈的运算符要保留,继续与下一个栈顶元素比较!
    另附上map容器详解网址:https://blog.csdn.net/Dawn_sf/article/details/78456747

    4.迷宫

    迷宫中走不通的路要原路返回,很适合栈
    其中栈的数据结构为:

    typedef struct
    {  Box data[MaxSize];
       int top;//栈顶指针
    } StType;//顺序栈类型
    
    

    方块的数据结构为:

    typedef struct
    {  int i;//当前方块的行号
      int j;//当前方块的列号
      int nd;//nd是下一可走相邻的方位号
    } Box;//定义方块类型
    
    

    1.2队列的存储结构及操作

    顺序队:

    结构体定义:

    typedef struct 
    {     ElemType data[MaxSize]; 
          int front,rear;      //队首和队尾指针
    }   SqQueue;
    
    
    

    图示(附几种操作):

    注意事项:1.rear总是指向队尾元素。 2.元素进队,rear增1。 3.front指向当前队中队头元素的前一位置。 4.元素出队,front增1
    四要素:
    · 队空条件:front = rear
    · 队满条件:rear = MaxSize-1
    · 元素e进队:rear++; data[rear]=e;
    · 元素e出队:front++; e=data[front];

    ···

    顺序队的几种操作:
    (1)初始化队列:

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

    注意此时rear和front都为-1。

    (2)进队列:

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

    注意:1.首先要判断队列是不是满的。 2.先将队尾指针rear自增,再将元素添加到该位置。

    (3)出队列:

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

    注意:1.首先要判断队列是否是空的。 2.队首指针自增,然后将front位置的值给e。

    链队列

    链队列的结构体定义:

    
    typedef struct qnode
    {      ElemType data;	//数据元素
            struct qnode *next;
    }  DataNode;
    typedef struct
    {      DataNode *front;	//指向单链表队头结点
            DataNode *rear; 	//指向单链表队尾结点
    }  LinkQuNode,*LinkList; 
    
    
    

    图解:

    (1)初始化队列InitQueue(q)

    
    void InitQueue(LinkQuNode *&q)
    {     q=new LinkQuNode;
          q->front=q->rear=NULL;
    }
    
    
    

    对应的C++模板操作为:queue<类型>队列名称;如:queueQ;(需要用到头文件#include)

    (2)判断队列是否为空QueueEmpty(q)
    若队列q满足q->front==q->rear条件,则返回true;否则返回false。

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

    对应的C++模板操作为:Q.empty();//队空时返回true。

    (3)进队列:

    
    void enQueue(LinkQuNode *&q,ElemType e)
    {     DataNode *p;
           p=(DataNode *)malloc(sizeof(DataNode));
           p->data=e;
           p->next=NULL;
           if (q->rear==NULL)   
    	q->front=q->rear=p;
          else
          {       q->rear->next=p;   
    	q->rear=p;
          }
    }
    
    

    对应的C++模板操作为:Q.push(e);
    注意:链队不需要考虑队满的情况。

    (4)出队列:

    
    bool deQueue(LinkQuNode *&q,ElemType &e)
    {     DataNode *t;
          if (q->rear==NULL) return false;	//队列为空
          t=q->front;		   		
          if (q->front==q->rear)  		
    	q->front=q->rear=NULL;
          else			   		
    	q->front=q->front->next;
          e=t->data;
          free(t);
          return true;
    }
    
    
    

    对应的C++模板操作为:Q.pop();//弹出队列的第一个元素,但并不会返回被弹出元素的值。
    注意:无论是顺序队还是链队在出队时都要考虑队空的情况。
    还有一些其他的C++模板操作:
    Q.front():即最早被压入队列的元素。
    Q.back():即最后被压入队列的元素。
    Q.size():返回队列中元素的个数。
    队列应用

    队列在日常生活中有很多应用,如银行排队系统,餐厅取餐系统,公园售票系统,还有手机短信的发送等等。
    1.2.谈谈你对栈和队列的认识及学习体会。
    栈和队列都有

    2.PTA实验作业

    2.1.1 6-5 jmu-ds-舞伴问题

    2.1.2代码截图:

    2.1.3 PTA提交列表及说明

    编译错误:在VS上用的是C编译,在PTA上用的是C++编译,而且题目的头文件中没有,导致复制字符串函数出错
    格式错误:他这个数据之间是两个空格,我第一次提交时只有一个,改过来就可

    2.1.1 7-5 表达式转换

    2.1.2代码截图


    2.1.3本题PTA提交列表说明。

    多种错误:第一次没有考虑到有负数的情况,且控制空格输出也写错了
    部分正确:更改了输出格式,并加上了负数的情况,但是有嵌套括号的情况还是不对
    正确:使用了map容器使得判断条件更为简洁,并修改了一下栈内左括号与栈外括号之间的优先级关系

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

    3.1 题目:

    代码:

    
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <stack>
    #include <string>
    #include <cctype>
    using namespace std;
    stack <double> st;
    int main()
    {
    	string s;
    	getline(cin, s);
    	for (int i = s.size() - 1; i >= 0; i--)//取数字
    	{
    		if (isdigit(s[i]))
    		{
    			double mul = 10, num = s[i] - '0';
    			for (i--; i >= 0; i--)//再往前一个字符
    			{
    				if (isdigit(s[i]))
    				{
    					num += (s[i] - '0') * mul;
    					mul *= 10;//因为是倒着遍历的,这样做使字符串变为数值
    				}
    				else if (s[i] == '.')//mul是为了控制小数的
    				{
    					num /= mul;
    					mul = 1;
    				}
    				else if (s[i] == '-')//遇到负数直接取相反数
    					num = -num;
    				else
    					break;
    			}
    			st.push(num);
    		}
    		else if (s[i] != ' ')   //else
    		{
    			double a, b, sum;
    			a = st.top();
    			st.pop();
    			b = st.top();
    			st.pop();
    			switch (s[i])
    			{
    			case '+':
    				sum = a + b;
    				break;
    			case '-':
    				sum = a - b;
    				break;
    			case '*':
    				sum = a * b;
    				break;
    			case '/':
    			{
    				if (b == 0)
    				{
    					cout << "ERROR";
    					return 0;
    				}
    				sum = a / b;
    			}
    			}
    			st.push(sum);
    		}
    	}
    	printf("%.1lf", st.top());
    }
    
    
    

    3.1.1 该题的设计思路

    首先从右向左开始入栈,当遇到第一个操作符的时候出栈两次计算结果,再将结果入栈,最后输出总结果
    由于是前缀表达式,所以必然是先有运算符,再有两个数字的,所以我们从后往前遍历,24-45行代码是读取一个数字的,每读完一个数字就压入栈中,当读到运算符时,就将栈顶的两个元素取出st.top()并删除这两个元素st.pop(),然后计算值并将值压入栈中以便下次计算st.push(sum),注意一个小问题,46行写成else if,原因自己想想就知道了。
    函数说明:
    isdigit(s[i]);//判断s[i]是否是0-9的阿拉伯数字,在头文件#include 有定义
    getline(cin, s);//将字符串读入到s中,在头文件#include 有定义
    扩展:cin.get()获取一个字符;getline(cin,s)是C的,获取一行字符串;cin.getline() 获取一行字符串,可以接收空格并输出

    3.1.2 该题的伪代码

    
    int main()
    {
        定义类型为double的栈
        定义数组s
        将数据读入到数组s中
        for(倒着遍历字符数组)
        {
             if(为数字)
             {
                 for(遍历接下去的一个字符)
                 {
                      根据情况将字符串转化为数值
                      直到一个"数字"结束,break;
                 }
                 数值入栈;
             }
             else if(为运算符)
             {
                 取出栈最上面的两个数字 
                 根据运算符计算
                 在将结果压入栈中
             }
        }
        输出栈顶元素
    }
    
    

    3.1.3 运行结果

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

    1、题目中没有明确说明是整数,而且也没说是正数,所以我们必须考虑 小数 和 负数 的情况 !
    2、因为是按字符输入,例如小数3.2 的格式为3.2,这里‘.’的前后是没有空格的!我们读取从右至左,所以会先读到2然后是‘.’,最后是3;我们只需要让2变成0.2;加上3就搞定了!!!
    3、负数:例如-1 格式是就是-1 中间也没有空格;有空格一定是-。我们遍历时如果遇到数字和‘-’挨着,那么就一定是负数!!!

    3.2题目:

    代码:

    
    #include<iostream>
    #include<set>
    using namespace std;
    int main()
    {
    	int num, n;
    	cin >> n;
    	set<int> s;
    	for (int i = 0; i < n; i++)
    	{
    		cin >> num;
    		if (s.upper_bound(num) != s.end())
    			s.erase(s.upper_bound(num));
    		s.insert(num);
    	}
    	cout << s.size() << endl;
    	return 0;
    }
    
    

    3.2.1 该题的设计思路

    先将一个数插入进set容器中,set容器默认从小到大(自动排序),在依次进行每个数的输入,如果输入的数比当前set容器中的最后一个数小,删除set容器中第一个大于输入数的值,在将输入数进行插入,重新排序后,输入的值就代替了删除的值,依次循环往复,进行到结尾 。
    函数说明:
    rbegin():
    c.begin() 返回一个迭代器,它指向容器c的第一个元素
    c.end() 返回一个迭代器,它指向容器c的最后一个元素的下一个位置
    c.rbegin() 返回一个逆序迭代器,它指向容器c的最后一个元素
    c.rend() 返回一个逆序迭代器,它指向容器c的第一个元素前面的位置
    upper_bound():
    upper_bound是找到大于t的最小地址,如果没有就指向末尾
    lower_bound是找到大于等于t的最小地址
    set::erase():
    erase() 迭代器的参数必须是一个指向容器中元素的、有效的、可解引用的迭代器,因此需要确保它不是容器的结束迭代器。这个版本的 erase() 函数会返回一个指向被删除元素的下一个位置的迭代器,如果删除的是最后一个元素,那么它就是结束迭代器。
    调用unordered_set容器的成员函数clear()可以删除它的全部元素。成员函数erase()可以删除容器中和传入参数的哈希值相同的元素。另一个版本的erase()函数可以删除迭代器参数指向的元素。

    3.2.2 伪代码

    
    int main()
    {
         读取列车的序列数
         set一个容器s
         在依次进行每个数的输入
         if(输入的数比当前set容器中的最后一个数小)
              删除set容器中第一个大于输入数的值,
              再将输入数进行插入
         输出s的长度
    }
    
    

    3.2.3 运行结果

  • 相关阅读:
    POJ 2175 Evacuation Plan 费用流 负圈定理
    POJ 2983 Is the Information Reliable? 差分约束
    codeforces 420B Online Meeting
    POJ 3181 Dollar Dayz DP
    POJ Ant Counting DP
    POJ 1742 Coins DP 01背包
    中国儒学史
    产品思维30讲
    Java多线程编程核心技术
    编写高质量代码:改善Java程序的151个建议
  • 原文地址:https://www.cnblogs.com/19wangluo-Lishaoqiang/p/12548960.html
Copyright © 2011-2022 走看看