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

0.PTA得分截图

1.本周学习总结

1.1栈

  • 栈:允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。
  1. 当栈中没有数据元素时称为空栈。
  2. 栈的插入操作称为入栈或者进栈,栈的删除操作称为出栈或者退栈。
  3. 栈的主要特点为后进先出,每次出栈的元素都为当前栈的栈顶元素。

1.1.1顺序栈

  • 顺序栈的结构体定义:
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
    ElemType data[MAXSIZE];//存放元素
    int top = -1; //用于栈顶指针,下标
}SqStack;//顺序栈的类型
  • 初始化栈:
void InitStack(SqStack &s)
{
   s=new SqStack;//动态申请内存
   s->top=-1;
}
  • 是否为空栈:
bool IsEmpty(SqStack s)//如果为空返回true,不为空则返回false;
{
    if(s.top==-1)
        return true;
    else
        return false;
}
  • 进栈:
bool Push(SqStack &S,ElemType e)
{
    if(s->top==MaxSize-1)//顺序栈进栈时要注意栈是否已满
    {
        return false;
    }
    s->top++;//栈顶指针加1
    s->data[s->top]=e; 
    return true;
}
  • 出栈:
bool Pop(SqStack &s,ElemType &e)
{
    if(s->top==-1)//顺序栈出栈时要注意栈是否为空
    {
       return flase
    }
    e=s->data[s->top];
    s->top--;//栈顶指针减一
    return ture;
}
  • 取栈顶元素:
bool GetTop(SqStack *s,ElemType &e)
{
   if(s->top==-1)//判断是否为空
   return false;
   e=s->data[s->top];
   return true;
}
  • 销毁栈:
void DestroyStack(SNode &s)
{
    delete s;
}

1.1.2链栈

  • 链栈的结构体定义:
typedef struct linknode
{
   ElemType data;//数据域
   struct linknode *next;//指针域
}LinkStNode;//链栈类型
  • 初始化栈:
void InitStack(LiStack &s)
{
   s=new LiNode;//动态申请内存
   s->next=NULL;
}
  • 进栈:
void Push(LiStack &s,Elemtype e)
{
   LiStack p;
   p=new LiNode;//新建结点进行插入
   p->data=e;//头插法
   p->next=s->next;
   s->next=p;
}
  • 出栈:
bool Pop(LiStack &s,ElemType &e)
{
   LiStack p;//新建结点临时保存
   if(s->next=NULL)
   {
      return false;
   }
   p=s->next;
   e=p->data;
   s->enxt=p->next;//改变结点指向
   delete p;//删除
   return true;
}
  • 取栈顶元素:
int GetTop(LinkStack S) //返回S的栈顶元素,不修改栈顶指针
{
    if (S != NULL) //栈非空
        return S->data; //返回栈顶元素的值,栈顶指针不变
else
return -1;
}

1.1.3 C++类模板:stack

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

1.2 栈的应用

简单表达式求值

将算术表达式转换成后缀表达式

在将一个中缀表达式转换成后缀表达式时,操作数之间的相对次序是不变的,但运算符的相对次序可能不同,同时还要除去括号。所以在转换时需要从左到右扫描算术表达式,将遇到的操作数直接存放到后缀表达式中,将遇到的每一个运算符或者左括号都暂时保存到运算符栈,而且先执行的运算符先出栈。

  • 伪代码
while (从exp读取字符ch, ch = '\0')
{
ch为数字:将后续的所有数字均依次存放到postexp中,并以字符'#'标识数字串结束;
ch为左括号'(':将此括号进栈到Optr中;
ch为右括号')':将Optr中出栈时遇到的第一个左括号'('以前的运算符依次出栈并存放到postexp中,然后将左括号'('出栈;
ch为其他运算符:
	if (栈空或者栈顶运算符为'(') 直接将ch进栈;
	else if (ch的优先级高于栈顶运算符的优先级)
		直接将ch进栈;
	else
		依次出栈并存入到postexp中,直到ch的优先级高于栈顶运算符,然后将ch进栈;
}
若exp扫描完毕,则将Optr中的所有运算符依次出栈并存放到postexp中。
while (从exp读取字符ch, ch != '\0')
{
ch为数字:将后续的所有数字均依次存放到postexp中,并以字符'#'标识数字串结束;
ch为左括号'(':将此括号进栈到Optr中;
ch为右括号')':将Optr中出栈时遇到的第一个左括号'('以前的运算符依次出栈并存放到postexp中,然后将左括号'('出栈;
ch为'+'或'-':出栈运算符并存放到postexp中,直到栈空或者栈顶为'(',然后将ch进栈; 
ch为'*'或'/':出栈运算符并存放到postexp中,知道栈空或者栈顶运算符为'('、'+'或'-',然后将ch出栈; 
}
若exp扫描完毕,则将Optr中的所有运算符依次出栈并存放到postexp中。
  • 代码
void trans(char* exp, char postexp[])
{
	char e;
	SqStack* Optr;
	InitStack(Optr);
	int i = 0;//i为postexp的下标
	while (*exp != '\0')//exp表达式未扫描完时循环
	{
		switch (*exp)
		{
		case'('://判定为左括号
			Push(Optr, '(');//左括号进栈
			exp++;//继续扫描其他字符
			break;
		case')'://判定为右括号
			Pop(Optr, e);//出栈元素e
			while (e != '(')//不为'('时循环
			{
				postexp[i++] = e;//将e存放到postexp中
				Pop(Optr, e);
			}
			exp++;
			break;
		case'+':
		case'-':
			while (!IsEmpty(*Optr))
			{	GetTop(Optr, e);
				if (e != '(')
				{	postexp[i++] = e;
					Pop(Optr, e);
				}
				else break;
			}

			Push(Optr, *exp);
			exp++;
			break;
		case'*':
		case'/':
			while (!IsEmpty(*Optr))
			{	GetTop(Optr, e);
				if (e == '*' || e == '/')
				{	postexp[i++] = e;
					Pop(Optr, e);
				}
				else break;
			}
			Push(Optr, *exp);
			exp++;
			break;
		default://处理数字字符
			while (*exp >= '0' && *exp <= '9')
			{	postexp[i++] = *exp;
				exp++;
			}
			postexp[i++] = '#';//用'#'标识一个数字串结束
		}
	}
	while (!IsEmpty(*Optr))//扫描完毕,栈不为空时循环
	{	Pop(Optr, e);
		postexp[i++] = e;//将e存放到postexp中
	}
	postexp[i] = '\0';//给postexp表达式添加结束标识
	DestroyStack(*Optr);
}

1.3队列

  • 队列:队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
  1. 插入新元素,新元素进队后为新的队尾元素,元素出队后,其后继的元素称为队头元素。
  2. 队的特点为先进先出

1.3.1顺序队列

  • 顺序队结构体定义:
typedef struct
{
   ElemType data[MaxSize];
   int front,rear;//队首,队尾指针
}Queue;
  • 初始化队列:
void InitQueue(Queue &q)
{
   q=new Queue;//动态申请内存
   q->front=q->rear=-1;
}
  • 是否为空队:
bool IsEmpty(Queue &q)
{
     if(q.rear==q.front)//队空
         return true;
     else//队不空
         return false;
}
  • 进队:
bool enQueue(SqQueue &q,ElemType e)
{
    if(q->rear+1==MaxSize)//判断是否栈满
    return flase;
    q->rear=q->rear+1;//移动指针
    q->data[q->rear]=e;
    return ture;
}
  • 出队:
bool deQueue(SqQueue &q,Elemtype &e)
{
   if(q->front==q->rear)//判断队是否为空
   return flase;
   e=q->data[q->front];
   q->front=q->front+1;//移动指针
   return ture;
}
  • 取队头元素:
bool GetFront(Queue q,Elemtype &e)
{
    if(IsEmpty(q))//取队头是要判断是否为空队;
       return false;
    else
       e = q.data[q.front + 1];
    return true;
}
  • 销毁队列:
void DestroyQueue(Queue &q)
{
    delete q;
}

1.3.2环形队列

  • 初始化队列:
void InitQueue(SqQueue &q)
{
   q=new Queue;//动态申请内存
   q->front=q->rear=0;
}
  • 进环形队列:
bool enQueue(SqQueue &q,Elemtype e)
{
   if((q->rear+1)%MaxSize==q->front)//判断是否队满
   return false;
   q->rear=(q->rear+1)%MaxSize;//移动rear
   q->data[q->rear]=e;
   return true;
}
  • 出环形队列:
bool deQueue(SqQueue &q,Elemtype e)
{
   if(q->front==q->rear)//判断是否队空
   return false
   e=q->data[q->front];
   q->front=(q->front+1)%MaxSize;//移动front
   return true;
}

出队需要判断是否对空,判断q->front==q->rea,后移动front指针q->front=(q->front+1)%MaxSize

1.3.3链队列

  • 初始化链队列:
void InitQueue(LinkQuNode &q)
{
   q=new LinkQuNode;
   q->front=q->rear=NULL;
}
  • 进队列:
bool enQueue(LinkQuNode &q,ElemType e)
{
   p=new QNode;
   p->data=e;//新建结点
   p->next=NULL;//避免后面无结束
   q->rear->next=p;
   q->rear=p;//尾指针移动
}
  • 出队列:
bool deQueue(LinkQuNode &q,ElemType e)
{
    Node 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;//移动front
    }
    e=t->data;
    delete t;//删除
}

1.3.4 C++容器:queue

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

1.3.5 队列运用

PTA:7-5 报数游戏

#include <iostream>
#include <queue>
using namespace std;
int main()
{
int m;//人数
int n;//数字
queue <int>person;
cin >> m >> n;
if (n > m)//错误情况
  {
      printf("error!");
  }
else
  {
      int i;
      for (i = 1; i <= m; i++)//入队
          {
		person.push(i);
          }
	int flag = 1;
	int num;
	while (!person.empty())
	      {
		i++;
		if (i % n == 0)
		    {
			num = person.front();
			if (flag == 1)
			    {
				flag = 0;
				cout << num;
			    }
			else
			    {
				cout << ' ' << num;
			    }
				person.pop();//出队
				i = 0;//重新计数
		    }
		else
		    {
			num = person.front();
			person.push(num);//进队
			person.pop();//出队
		    }
		}
    }
return 0;
}

2.PTA实验作业

2.1 7-3符号配对

符号配对

2.1.1 解题思路及伪代码

  • 解题思路
    首先遍历输入数据遇到左符号就入栈;当遇到右符号时应该考虑是否空栈:如果空栈,直接不匹配,输出缺少左符号;如果不为空栈,判断当前的右符号是否与栈顶的左符号匹配:匹配,出栈栈顶元素;不匹配,输出缺少右符号;当遍历结束后,再次考虑:如果为空栈,表示所有的符号都配对,全配对输出;如果不为空栈,说明不配对,输出缺少右符号。

  • 伪代码

while(遍历至最后一行)
{
getline(str)
总str += str;
}
用string[]建好左右的符号表。
while(遍历总str中)
{
  if(cursign为左符号) 则执行push;
  if (cursign为右符号) 
  {
    if(stack不空)
        {
          if (左右符号匹配)  则执行pop;
          else  则执行输出缺右符号
        }
    else 则执行输出缺左符号
  }
if(stack empty) 则all matched
else 则执行输出缺右符号
}
遍历string[]找相同的string元素。

2.1.2 总结解题所用的知识点

本题需要考虑到左符号剩余,右符号剩余,配对情况,不配对情况,需要多重if来进行约束判断。

2.2 7-6 银行业务队列简单模拟

银行业务队列简单模拟

2.2.1 解题思路及伪代码

  • 伪代码
输入人数k
创建队列q,p
for (i = 0; i < k; i++)
{
    cin >> n;//输入编号
    if (n % 2)  编号为奇数则入奇数的栈;
    else  编号为偶数则入偶数的栈	;
}
for (i = 0; i < k; i++)
{
	if (i == 0)//对第一个数据输出处理空格
	if ((i + 1) % 3)//对a窗口处理
	{
		if (!q1.empty()//判断是否处理完毕
                        未处理完毕打出a窗口的编号;
	        else 处理完毕直接打出b窗口的所有编号;	
	}
	else//对b窗口处理
	{
		if (!q2.empty())//判断是否处理完毕
                        未处理完毕打出b窗口的编号
		else 处理完毕直接打出b窗口的所有编号
	}
}
    return 0;
}

2.2.2 总结解题所用的知识点

学会运用queue容器。

3.阅读代码

3.1 题目及解题代码

剑指 Offer 59 - II. 队列的最大值

3.2 该题的设计思路及伪代码

从队列尾部插入元素时,我们可以提前取出队列中所有比这个元素小的元素,使得队列中只保留对结果有影响的数字。这样的方法等价于要求维持队列单调递减,即要保证每个元素的前面都没有比它小的元素。
伪代码:

多加一个双向队列,保存当前的最大值
双向队列里面保证里面数据递减
当push的数据比deque中的数据大时,则将前面的数据剔除,保持递减状态
当pop的数据就是deque最前面的数据,就把deque前面的数据pop掉

代码:

class MaxQueue {
private:
    queue<int> myQueue;
    deque<int> myDeque;
public:
    MaxQueue() {
        
    }
    
    int max_value() {
        return myQueue.empty()?-1:myDeque.front();
    }
    
    void push_back(int value) {
        
        while((!myDeque.empty())&&(value>myDeque.back()))
        {
            myDeque.pop_back();
        }
        myQueue.push(value);
        myDeque.push_back(value);
    }
    
    int pop_front() {
        if(myQueue.empty()){
            return -1;
        }
        int temp=myQueue.front();
        if(temp==myDeque.front())
        {
            myDeque.pop_front();
        }
        myQueue.pop();
        return temp;
    }
};
  • 时间复杂度:O(1)(插入,删除,求最大值)删除操作于求最大值操作显然只需要 O(1) 的时间

  • 空间复杂度:O(n),需要用队列存储所有插入的元素

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

  1. 从队列尾部取出元素,因此需要使用双端队列来实现。另外我们也需要一个辅助队列来记录所有被插入的值,以确定 pop_front 函数的返回值。
posted @ 2021-04-05 22:14  Lzwx2  阅读(42)  评论(0编辑  收藏  举报