• 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 运行结果

  • 相关阅读:
    AJAX 方式
    Qt程序设计——txt文本中获取字符串的问题
    二、Cocos2dx中Android部分的c++和java实现相互调用(高级篇)
    Android项目 手机安全卫士(代码最全,注释最详细)之五 splash动画效果
    Navigator 对象
    Dreamweaver中打开CodeSmith文件
    IOS开发:xcode5版本引发的问题
    Ubuntu 13.04 小米2S连接Eclipse真机调试
    Java面试题之四
    c++基础 之 面向对象特征一 : 继承
  • 原文地址:https://www.cnblogs.com/19wangluo-Lishaoqiang/p/12548960.html
Copyright © 2020-2023  润新知