DS02-栈和队列

0.展示PTA总分

1.本章学习总结

1.1 总结栈内容

  • 栈是一种是一种线性表,我们只能在栈的一端进行插入和删除操作,是先进后出的一种结构。允许插入删除的一端我们称为栈顶,而另一端不允许插入删除的我们称为栈底。
  • 因为栈是一种线性表,所以栈可以采用和线性表相同的存储结构:顺序存储和链式存储。顺序存储结构的栈称为顺序栈,链式存储的栈称为链栈。
  • 当栈中没有数据元素时,称为空栈。栈的插入操作通常称为进栈或入栈。栈的删除操作通常称为退栈或出栈。

C++类模板:stack

头文件 #include <stack>
stack <Elemtype> s;初始化栈,保存Elemtype类型的数据
s.push(x);入栈元素t
s.top();返回栈顶指针
s.pop();出栈操作,只做删除栈顶元素的操作,不返回该删除元素
s.empty();判断是否栈空,如果为空返回true
s.size();返回栈中元素个数

栈结构定义:

/*顺序栈*/
typedef struct 
{
    Elemtype data[MaxSize];
    int top;//栈顶指针;
}SNode;

/*链栈*/
typedef struct stack
{
     Elemtype  data;
     struct stack* next;
}SNode,*Stack;
  • 栈初始化:
/*顺序栈*/
void InitStack(SNode &s)
{
     s.top=-1;//一般的顺序表栈顶指针的初始化为-1,
}

/*链栈*/
void InitStack(Stack &s)
{
     s = new SNode;//申请空间;
     s->next=NULL;
}
  • 判断是否为空栈:
  • 我们如果要访问某个地址时,一定要先判断该地址中是否已经保存了数据,否则访问就会出现错误。所以我们要先判断该栈是否是一个空栈,以保证后续访问操作的进行。
/*顺序栈*/
bool IsEmpty(SNode s)//如果为空返回true,不为空则返回false;
{
    if(s.top==-1)
        return true;
    else
        return false;
}


/*链栈*/
bool IsEmpty(Stack s)//如果为空返回true,不为空则返回false;
{
    if(s->next==NULL)
        return true;
    else 
        return false;
}
  • 判断是否栈满(链栈无需考虑)
  • 对于顺序栈来说,是用数组来保存数据,因为数组在被定义时就需要预先申请一段连续的空间,所以如果我们要往里面进行插入的操作,必须要先判断该栈是否已经满了,否则就会出现数组溢出,访问错误的情况。
bool IsFull(sNode s)
{
    if(s.top==MaxSize-1)
       return true;
    else
       return false;
}

进栈:

/*顺序栈*/
bool Push(SNode &s,Elemtype e)
{
    if(IsFull(s))//在进栈之前一定要先判断是否栈满!
        return false;//返回错误;
    else
        s.data[++s.top] = e;
    return true;//说明进栈成功;
}


/*链栈*/
void Push(Stack &s,Elemtype e)//使用头插法;
{
    Stack str;
    str=new Stack;
    str->data = e;
    str->next= s->next;//先将原来的链栈连接到str后面;
    s->next = str;//再将str连接到头结点后面成为新的栈顶;
}

取栈顶元素

/*顺序栈*/
bool Gettop(SNode &s, Elemtype &e)
{
     if(IsEmpty(s))//取栈顶时要判断是否为空栈!
       return false;
     else 
       e = s.data[s.top];
     return true ;
}


/*链栈*/
bool Gettop(Stack &s,Elemtype &e)
{
   if(IsEmpty(s))//取栈顶时要判断是否为空栈!
      return false;
   else
      e = s->next->data;
   return true;
}

出栈:

/*顺序栈*/
bool Pop(SNode &s,Elemtype &e)//出栈并返回该栈顶元素;
{
    if(IsEmpty(s))//在出栈之前一定要判断是否为空栈!
       return false ;//出栈错误;
    else
      e = s.data[s.top--];//这里只是移动了栈顶指针,并没有真正的删除数据; 
    return true;//出栈成功;
}


/*链栈*/
void Pop(Stack & s,Elemtype &e)
{
    Stack str;
    if(IsEmpty(s))//出栈之前要判断是否为空栈;
         return false;
    else
    {
         e = s->next->data;
         str = s->next;
         s->next = str->next;//让头结点连接栈顶的下一个结点,使s->next->next成为新的栈顶;
         delete str;//释放空间,这里数据是真的被删除
    }
}

销毁栈:

/*顺序栈*/
void DestroyStack(SNode &s)
{
    delete s;
}


/*链栈*/
void DestroyStack(Stack &s)
{ 
    ListStack str;
    while(s!=NULL)
    {
          str = s;//str保存当前删除的结点
          s = s->next;//s指向下一个需要删除的结点;
          delete str;
    }
}

栈的应用(符号配对、表达式转换、迷宫求解(回溯法))

1.以判断字符串是否是对称串为例

  • 判断对称的方法,可以进行首位对比,那么就可以利用栈的特点,后进先出,将后面的元素与开始的元素进行对比
#include <iostream>
#include<stack>
using namespace std;
typedef char ElemType;
int main()
{
	char str[100];
	cin >> str;
	char c;
	stack<char>s;
	int i, len;
	for (i = 0; str[i] != '\n' && str[i] != '\0'; i++)
	{
		s.push(str[i]);
	}
	len = s.size();
	if (len % 2 != 0)
	{
		cout << "no";
	}
	else
	{
		for (i = 0; i < (len / 2); i++)
		{
			c = s.top();
			s.pop();
			if (str[i] == c)
			{
				continue;
			}
			else
			{
				cout << "no";
				return 0;
			}
		}
		if (i == (len / 2) )
		{
			cout << "yes";
		}
	}
}

1.2 总结队列内容

  • 队列只能选取一端进行插入操作,另一端做删除操作,是先进先出的一种结构。我们把进行删除的一段叫做队头,进行插入的一端叫做队尾。
  • 分为顺序存储结构和链式存储结构,顺序存储结构的队列叫做顺序队,链式存储结构的队列叫做链队。链队中,队头指针和队尾指针是单独放在一个结构体当中。

队列的定义:

/*顺序队*/
typedf struct
{
   Elemtype data[MaxSize];
   int front;//队头指针;
   int rear;//队尾指针;
}QNode;

/*链队*/
typedef struct qnode//用于保存每个结点;
{
    Elemtype data;
    struct qnode *next;
}Node,*LinkNode;

typedef struct 
{
   LinkNode front;//队头指针;
   LinkNode rear;//队尾指针;
}Queue;

队列初始化:

/*顺序队*/
void InitQueue(Queue &q)
{
    q.front=q.rear=-1;
}


/*链队*/
void InitQueue(Queue &q)
{
    q.front->next = NULL;
    q.rear->next = NULL;
}

判断是否空

/*顺序队*/
bool IsEmpty(Queue &q)
{
     if(q.rear==q.front)//队空
         return true;
     else//队不空
         return false;
}


/*链队*/
bool IsEmpty(Queue &q)
{
      if(q.front->next==NULL)//队空
        return true;
      else
        return false;
}

队是否满(链队不需考虑)

bool IsFull(Queue &q)
{
      if(q.rear==MaxSize-1)//队满
          return true;
      else
          return false;
}

进队

/*顺序队*/
bool Push(Queue &q,Elemtype e)
{
    if(IsFull(q))//在进队之前一定要先判断是否队满;
        return false;//表示入队失败;
    else
        q.data[++q.rear] = e;
    return true;//表示入队成功;
}


/*链队*/
void Push(Queue &q,Elemtype e)
{
    LinkNode qtr;
    qtr->data=e;  
    qtr->next=NULL;
    if(IsEmpty(q))//先判断是否为空栈,如果为空栈要对队头指针一起修改;
       q.front->next = qtr;
    q.rear->next = qtr;
    q.rear =  qtr;
}

出队

bool Pop(Queue &q,Elemtype &e)
{
    if(IsEmpty())//出栈是要先判断是否为空栈;
        return false;
    else
        e = q.data[++q.front];
    return true;//表示出队成功;
}


/*链队*/
bool Pop(Queue &q,Elentype &e)
{
    LinkNode qtr;
    if(IsEmpty())
        return false;
    else
    {
        qtr=q.front->next;//先用qtr保存要出队的结点;
        q.front->next=qtr->next;//修改队头指针;
        e = qtr->data;
        delete qtr;//删除结点;
    }    
}

销毁队列

/*顺序栈*/
void DestroyQueue(Queue &q)
{
    delete q;
}


/*链栈*/
void DestroyQueue(Queue &q)
{ 
    LinkNode qtr;
    while(q.front!=NULL)
    {
          qtr = q.front;//str保存当前删除的结点
          q.front = q.front->next;//s指向下一个需要删除的结点;
          delete qtr;
    }
}

循环队列

  • 当采用rear==MaxSize-1作为队满条件时,当其为真,队中可能还有若干空位置,这种溢出并不是真正的溢出,称为假溢出。
队空条件:front==rear

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

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

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

  • 解决假溢出的问题,就需要把数组的前端和后端连接起来,形成一个环形的顺序表,即把存储队列元素的表从逻辑上看成一个环,称为环形队列或循环队列。

c++容器:queue

头文件:#include <queue>
q.push(x);将x插入到队列末端,成为新的队尾元素
q.pop();弹出队列的第一个元素,注意!!这里不返回被弹出元素
q.front();返回队头元素
q.back();返回队尾元素
q.empty();当队空是,返回true
q.size();返回队列的元素个数

队列应用(报数游戏、迷宫(实现最短路径))

以报数游戏为例

  • 有n个人围成一圈,按顺序从1到n编好号。从第一个人开始报数,报到m(m<n)的人退出圈子;下一个人从1开始报数,报到m的人退出圈子。如此下去,直到留下最后一个人。其中n是初始人数;m是游戏规定的退出位次(保证为小于n的正整数)
  • 代码:
#include<iostream>
#include<string>
#include<queue>
#include<map>
using namespace std;
int main()
{
	int i,n,m,e,flag=1;
	cin >> n;
	cin >> m;
    if(m>n)
    {
        cout<<"error!";
        return 0;
    }
	queue<int>q;
	for (i = 1; i <= n; i++)
	{
		q.push(i);
	}
	i = 1;
	while (!q.empty())
	{
		if (i == m)
		{
			if (flag == 1)
			{
				cout << q.front();
				flag++;
			}
			else
				cout << ' ' << q.front();
			q.pop();
			i = 1;
		}
		else
		{
			e = q.front();
			q.pop();
			q.push(e);
			i++;
		}
		
	}
}

1.2.对线性表的认识及学习体会

  • 刚开始初步理解栈和队列时,感觉挺简单的,特别是能够使用c++容器后,代码更是简洁了很多。但是但迷宫问题出现时,我就趴下了,面对银行三兄弟更是望而却步。不过林智凯同学将这些问题简化了,等接有空还是要好好研究一下这些题目。

2.PTA实验作业

2.1 jmu-报数游戏

==========

  • 报数游戏是这样的:有n个人围成一圈,按顺序从1到n编好号。从第一个人开始报数,报到m(m<n)的人退出圈子;下一个人从1开始报数,报到m的人退出圈子。如此下去,直到留下最后一个人。其中n是初始人数;m是游戏规定的退出位次(保证为小于n的正整数)。要求用队列结构完成。输出数字间以空格分隔,但结尾不能有多余空格。
  • 输入样例:
5 3
  • 输出样例:
3 1 5 2 4
  • 输入样例:
5 6
  • 输出样例:
error!

2.1.1 代码截图

2.1.2 PTA提交列表及说明

  • 先将1到n放入一个队列中,然后通过奇偶数来决定是出列或者是出列再入列
  • 答案错误:没有考虑m>n的情况

2.2 表达式转换

  • 算术表达式有前缀表示法、中缀表示法和后缀表示法等形式。日常使用的算术表达式是采用中缀表示法,即二元运算符位于两个运算数中间。请设计程序将中缀表达式转换为后缀表达式。

  • 输入数据:

2+3*(7-4)+8/4
  • 输出数据:
2 3 7 4 - * + 8 4 / +

2.2.1 代码截图

2.2.2 PTA提交列表及说明

  • 多种错误,答案错误:没有考虑两位数的情况,修改完之后,得18分。
  • 多种错误:还没修改小数的情况。
  • 对于符号的处理,正号本是不用输出的,但是负号是要输出的,而正负号在的位置只能是左括号的右边和数字的前面,所以加个判断就好。

3.阅读代码

3.1 题目及解题代码

  • 验证栈序列
    给定 pushed 和 popped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。
    示例一:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

示例二:

输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
class Solution {
public:
    bool validateStackSequences(const vector<int>& pushed, const vector<int>& popped) {
        stack<int> sk;
        auto first = pushed.cbegin();
        for (const auto ch : popped) {
            if (!sk.empty() && sk.top() == ch) {
                sk.pop();
            } else {
                while (first != pushed.cend() && *first != ch)
                    sk.push(*first++);
                if (first == pushed.cend()) return false;
                else first++;
            }
        }
        return first == pushed.cend();
    }
};

3.1.1 该题的设计思路

遍历出栈序列,如果出栈序列合法,遍历到的符号X应该在栈的顶端,或者在入栈前的队列中。
如果在栈顶,出栈这个符号X。
不在栈顶时,遍历队列,寻找这个符号X,并将X前的符号入栈。
队列已经为空还没找到X,则出栈序列不合法。
验证完出栈序列后,判断入栈前的队列是否为空。

3.1.2 该题的伪代码

获取popped序列的大小n;
k用于遍历popped序列;
i用于遍历pushed序列;
for(i=0;i<n;i++)
{
   先按照给出的进栈序列pushed[k] 按顺序进栈s;
   while(!s.empty() && k<n && s.top==poped[k])//如果相等,s中的栈顶元素就出栈;
   {
       s.pop();
       k++;//继续遍历popped的下一个元素;
   }
}
判断栈是否为空,如果栈不为空,说明该出栈序列不正确;

3.1.3 运行结果

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

  • 时间复杂度为:O(n)(n为序列的元素个数),最复杂的情况就是当结果为"true"时,每个元素都进栈一次又出栈一次,n个元素都进栈一次出栈一次,所以时间复杂度为O(n);
  • 空间复杂度为: O(n),最坏情况是所有元素都进栈,最后再一个一个出栈,此时在栈中开辟了n个空间保存元素,空间复杂度为O(n);

3.2 题目及解题代码

  • 用队列实现栈
    push(x) -- 元素 x 入栈
    pop() -- 移除栈顶元素
    top() -- 获取栈顶元素
    empty() -- 返回栈是否为空
    注意:

你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。

class MyStack {
public:
    /** Initialize your data structure here. */
    MyStack() {

    }
    
    /** Push element x onto stack. */
    void push(int x) {
        que.push(x);
        for (int i = 0; i + 1 < que.size(); i++) {
            que.push(que.front());
            que.pop();
        }
    }
    
    /** Removes the element on top of the stack and returns that element. */
    int pop() {
        int val = top();
        que.pop();
        return val;
    }
    
    /** Get the top element. */
    int top() {
        return que.front();
    }
    
    /** Returns whether the stack is empty. */
    bool empty() {
        return que.empty();
    }

private:
    queue<int> que;
};

3.2.3 运行结果

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

  • 入栈O(N) 出栈O(1)
  • 用两个队列q1和q2,总是保持一个队列为空。
  • 入栈时向非空的一个队列push;
  • 出栈时,假设q1非空,则从q1队头取元素入队到q2中,直到q1中剩下一个元素,这个元素就是栈顶元素;
  • 求top直接返回非空队列的队尾即可。
posted on 2020-03-22 23:11  洪志鸿  阅读(287)  评论(0编辑  收藏  举报