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

| 这个作业属于哪个班级 |


| ---- | ---- | ---- |
| 这个作业的地址 |
| 这个作业的目标 | 学习栈和队列的结构设计及运算操作 |
| 姓名 | 黄静 |

0.PTA得分截图

1.本周学习总结

1.1.1 栈的介绍

  • 定义:栈是一种只能在一端进行插入或删除操作的线性表。表中允许进行插入,删除的一端称为栈顶。
  • 进栈:插入操作
  • 出栈:删除操作
  • 出栈顺序:先进后出(如下图出栈顺序为:an,an-1,···a2,a1)
  • 存储结构:顺序栈,链栈

1.1.2 顺序栈

顺序栈介绍

采用顺序存储结构的栈称为顺序栈

顺序栈四要素:

  • 栈空条件:top = -1
  • 栈满条件:top = MaxSize - 1
  • 进栈e操作:top++;st->data[top] = e
  • 退栈操作:e = st->data[top];top--

定义结构体

假设栈的元素个数最大不超过正整数MaxSize,所有元素都具有同一元素数据类型ElemType,则栈类型SqStack定义为

typedef struct
{
	ElemType data[MaxSize];
	int top;//栈顶指针
}Stack;
typedef Stack* SqStack;

初始化栈

void InitStack(SqStack& s)
{
	s = new Stack;
	s->top = -1;
}

销毁栈

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

判断栈是否为空

bool StackEmpty(SqStack s)
{
	return (s->top == -1);
}

进栈

在栈不满的情况下,先将栈指针加一,然后在该位置上插入元素e

bool Push(SqStack& s, ElemType e)
{
	if (s->top == MaxSize - 1)//栈满
		return false;
	s->top++;//栈顶指针加一
	s->data[s->top] = e;//赋值
	return true;
}

出栈

在栈不为空的情况下,先将栈顶元素赋给e,然后将栈指针减一

bool Pop(SqStack& s, ElemType& e)
{
	if (s->top == -1)//栈空
		return false;
	e = s->data[s->top];//取栈顶指针元素
	s->top--;//栈顶指针减一
	return true;
}

取栈顶元素

bool GetTop(SqStack s, ElemType& e)
{
	if (s->top == -1)//栈空
		return false;
	e = s->data[s->top];//取栈顶指针元素
	return true;
}

1.1.3 共享栈

顺序栈采用一个数组存放栈中的元素。如果需要用到两个相同类型的栈时,若各自开辟一个数组空间,可能会出现这样的情况:第一个栈已满,再进栈就溢出了,而另一个栈还有很多空闲空间,解决这个问题的方法是将两个栈合起来,用一个数组来实现这两个栈,这称为共享栈。

在设计共享栈时,由于一个数组(大小为MaxSize)有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下端为0处,另一个栈的栈底为数组的末端,即下标为MaxSize-1,这样在两个栈中进栈元素时栈顶向中间伸展。
共享栈四要素:

  • 栈空条件:栈1空为top1 == -1;栈2空为top2 == MaxSize
  • 栈满条件:top1 == top2 - 1
  • 元素x进栈操作:进栈1操作为top++;data[top1] = x;进栈2操作为top2--;data[top2] = x;
  • 出栈x操作:出栈1操作为x = data[top1];top1--;出栈2操作为x = data[top2];top2++;

定义结构体

data数组表示共享栈的存储空间,top1和top2分别为两个栈的栈顶指针,这样改共享栈通过data,top1和top2来标识,也可以将它们设计为一个结构体类型:

typedef struct
{
	ElemType data[MaxSize];//存放共享栈中的元素
	int top1, top2;//两个栈的栈顶指针
}DStack;

1.1.4 链栈

采用链式存储结构的栈称为链栈,这里采用带头节点的单链表来实现链栈

链栈四要素:

  • 栈空的条件:s->next == NULL
  • 栈满的条件:由于只有内存溢出时才出现栈满,通常不考虑,所以看作不存在栈满
  • 元素e的进栈操作:新建一个结点存放元素e(由p指向它),将结点p插入到头节点之后,头插法
  • 出栈操作:取出首结点的data值并将其删除

定义结构体

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

初始化栈

该运算创建一个空链表栈,实际上是创建链栈的头节点,并将其next域置为NULL

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

销毁栈

释放链栈占用的全部空间结点,和单链表的销毁算法相同

void DestroyStack(LiStack& s)
{
	LiStack node;
	while (s != NULL)
	{
		node = s;
		s = s->next;
		delete node;
	}
}
![](https://img2020.cnblogs.com/blog/2161689/202104/2161689-20210404163846837-49269583.png)

判断栈是否为空

判断s->next == NULL的条件是否成立

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

进栈

新建一个结点,用于存放元素e(由p指向它),然后将其插入头节点之和作为新的首结点

void Push(LiStack& s, ElemType e)
{
	liStack p;
	p = new Linode;
	p->data = e;
	p->next = s->next;
	s->next = p;
}

出栈

在栈不为空的情况下提取首结点的值赋给参数e,然后将其删除

void 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结点
	delete p;//释放p结点
	return true;
}

取栈顶元素

在栈不为空的条件下提取首结点的数据域赋给参数e

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

1.1.5 栈c++模板

#include <stack>///头文件

stack<int>s;//初始化栈,参数表示元素类型
s.push(t);//入栈元素t
s.top();//返回栈顶元素
s.pop();//出栈,物理删除栈顶数据,不返回该元素
s.empty();//栈空时,返回true
s.size();//访问栈中的元素个数

1.2 栈的应用

  • 符号配对
  • 中缀表达式转后缀表达式
    后缀表达式可以将复杂表达式转换为可以依靠简单的操作得到计算结果的表达式。所以对中缀表达式的求值过程是先将中缀表达式转换成后缀表达式,然后对其求值。
在扫描exp遇到一个运算符op时,如果栈为空,直接将其进栈;如果栈不空,只有当op的优先级高于栈顶运算符的优先级时才直接将op进栈(以后op先出栈表示先执行它);否则依次出栈运算符并存人postexp(出栈的运算符都比op先执行),直到栈顶运算符的优先级小于op的优先级为止,然后再将op进栈。
//有括号的情况
如果op为'(',表示一个子表达式的开始,直接将其进栈;如果op为')',表示一个子表达式的结束,需要出栈运算符并存人postexp,直到栈顶为'(',再将'('出栈;如果op是其他运算符,而栈顶为'(',直接将其进栈。

例如:
书上例题的转换过程

  • 走迷宫

1.3 队列

1.3.1 队列的介绍

队列简称队,它也是一种操作受限的线性表,其限制为仅允许在表的一端进行插入操作,而在表的另一端进行删除操作。把进行插入的一端称为队尾(rear),把进行删除的一端称为队头或队首(front),向队列中插入新元素称为进队或入队,新元素进队后就成为新的队尾元素;从队列中删除元素称为出队或离队,元素出队后,其直接后继元素就成为队首元素。
由于队列插入与删除操作分别是在各自的一端进行的,每个元素必然按照进入的次序出队,所以又把队列称为先进先出表。

只允许在表的一端进行插入,而在表的另一端进行删除的线性表
队尾(rear):允许插入的一端
队首,队头(front):允许删除的一端
进队,入队:插入操作
出队,离队:删除操作
特点:先进先出
存储:顺序表,链表

1.3.2 顺序队列

采用顺序存储结构的队列称为顺序队,即分配一块连续的存储空间来存放队列中的元素,并用两个整型变量来反映队列中元素的变化,它们分别储存队首元素和队尾元素的下标位置,分别称为队首指针(队头指针)和队尾指针

定义结构体

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

初始化队列

void InitQueue(SqQueue& q)
{
	q = new Queue;
	q->front = q->rear = -1;
}

销毁队列

void DestroyQueue(SqQueue& q)
{
	delete q;
}

判断队列是否为空

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

进队列

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

出队列

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

1.3.3 循环队列

在顺序队操作中,队列可能出现假溢出的情况,当出现假溢出时,队尾指针指向data最大下标,而另一端可能还有若干空位置,解决方法是将data数组前端和后端连接起来,形成一个环形数组,即把存储队列元素的数组从逻辑上看成一个环,称为环形队列或循环队列。

操作使用求余运算来实现

  • 队头指针front循环增一:front=(front+1)%MaxSize
  • 队尾指针rear循环增一:rear=(rear+1)%MaxSize
  • 队空条件:q->rear==q->front
  • 队满条件:(q->rear+1)%MaxSize==q->front

定义结构体

typedef struct
{
	ElemType data[MaxSize];
	int front, rear;
}Queue;
typedef Queue* SqQueue;

初始化队列

void InitQueue(SqQueue& q)
{
	q = new Queue;
	q->front = q->rear = 0;
}

销毁队列

void DestroyQueue(SqQueue& q)
{
	delete q;
}

判断队列是否为空

若队列为空,返回真,否则返回假;

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

进队列

bool enQueue(SqQueue& q, ElemType e)
{
	if ((q->rear + 1) % MaxSize == q->front)//队满
		return false;
	q->rear = (q->rear + 1) % MaxSize;
	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;
	return true;
}

1.2.4 队列c++模板

#include <queue>///头文件

q.push(x);//x插入队列的末端
q.pop();//弹出队列的第一个元素,物理删除,不返回该元素
q.front();//取队头元素
q.back();//取队尾元素
q.empty();//队列为空时,返回true
q.size();//访问队列中的元素个数

1.2.5 链队列

采用链式存储结构的队列称为链队,这里采用单链表来实现链队;在这样的链表中只允许在单链表的表头进行删除操作(出队)和在单链表的表尾进行插入操作(入队),因此需要使用队头指针front和队尾指针rear,用front指向队首结点,rear指向队尾结点,和链栈一样,链队也不存在队满上溢出的情况。

定义结构体

//单链表中数据节点类型QNode定义如下:
typedef struct qnode
{
	ElemType data;//数据元素
	struct qnode* next;
}QNode;
//链队中头尾指针类型LinkQueue定义如下:
typedef struct
{
	QNode* front;
	QNode* rear;
}LinkQueue;

初始化链队

构造一个空队,即创建一个链队结点,其front和rear域均置为NULL;

Status InitQueue(LinkQueue& Q)
{
	Q.front = Q.rear = new QNode;
	if (!Q.front) exit(OVERFLOW);
	Q.front->next = NULL;
	return OK;
}

判断队列是否为空

若队列为空,返回真,否则返回假;

Status QueueEmpty(LinkQueue Q)
{
	return(Q.front == Q.rear);
}

求链队列的队头元素

Status GetHead(LinkQueue Q, ElemType& e)
{
	if (Q.front == Q.rear)//空队列
		return ERROR;
	e = Q.front->next->data;
	return OK;
}

链队列入队

创建一个新节点用于存放e,(由p指向它),若原队列为空,则链队结点的两个域均指向p,否则将p连接到单链表表尾,并让链队结点的rear指向它;

Status EnQueue(LinkQueue& Q, ElemType e)
{
	p = new QNode;
	if (!p)exit(OVERFLOW);
	p->data = e;
	p->next = NULL;
	Q.rear->next = p;
	Q.rear = p;
	return OK;
}

链队列出队

若原队列为空,返回假;若原队列不空,则将首结点data值赋给e,并删除之;
若原队列只有一个结点,则需将链队结点的两个域均置为NULL,表示队列已为空;

Status DeQueue(LinkQueue& Q, ElemType& e)
{
	if (Q.front == Q.rear)//空队列
		return ERROR;
	p = Q.front->next;
	e = p->data;
	Q.front->next = p->next;
	if (Q.rear == p) Q.rear = Q.front;//最后一个元素被删,改队尾
	delete p;
	return OK;
}

2.PTA实验作业

2.1 符号配对

7-2 jmu-ds-符号配对 (15 分)
假设表达式中允许包含3种括号:圆括号、方括号和大括号。即(,[,'{'。编写一个算法判断表达式中的括号是否正确配对, 要求利用栈的结构实现。 ###输入要求 输入一行带上述括号字符串 ###输出要求 若匹配,输出yes。若不匹配,输出当前栈顶元素再换行输出no,并。若栈顶为空,则输出no。

2.1.1 解题思路及伪代码

解题思路

先使用一个字符串数组str存储输入的一行字符串,再依次遍历该字符串,若遇到左边括号'(','[','{'则入栈,若发现为右边括号')',']','}'则判断其与栈顶的元素是否匹配。
若匹配,则栈顶元素出栈,继续遍历字符串,
若不匹配或栈此时为空,则为符号不匹配,
最后如果字符串遍历完成,且栈为空,说明符号完全匹配;

伪代码

定义字符数组str存储输入的字符串,栈s为存储左括号'(', '[', '{'的栈,flag表示此时的状态,flag = 1表示匹配,flag = 0表示不匹配;
for (i = 0;str[i] != '\0';i++)//遍历字符串
	if str[i]为左括号'(', '[', '{'
		进栈;
	else if str[i]为右括号')', ']', '}'
		if 此时为空栈
			不匹配,flag = 0;break;
		else if str[i]与栈顶元素s.top()为相匹配的左右括号
			匹配,栈顶元素出栈;
		else
			不匹配,flag = 0;break;
end for;

if 栈为空和flag == 1	匹配;
else 不匹配;

2.1.2 知识点总结

  • stack模板的应用
  • 扫描遍历字符串
  • 判断字符为左括号,右括号,以及符号匹配的条件

2.2 银行业务队列简单模拟

7-6 银行业务队列简单模拟 (25 分)

设某银行有A、B两个业务窗口,且处理业务的速度不一样,其中A窗口处理速度是B窗口的2倍 —— 即当A窗口每处理完2个顾客时,B窗口处理完1个顾客。给定到达银行的顾客序列,请按业务完成的顺序输出顾客序列。假定不考虑顾客先后到达的时间间隔,并且当不同窗口同时处理完2个顾客时,A窗口顾客优先输出。

2.2.1 解题思路及伪代码

解题思路

建立两个队列分别对应两个窗口,分为奇偶数输入数据,对2取余不为0的存入qa队列,对2取余为0的存入qb队列。当A,B窗口都有人时,每当A窗口输出两个顾客,B窗口输出一位顾客,使用临时变量count=0计数,A窗口输出一位,count加一,当count为2的倍数时,B窗口也输出,且A窗口先输出,当只有一个窗口有人时,依次输出其队列中剩余的顾客编号。

伪代码

定义两个队列qa,qb,分别输入数据,对2取余不为0的存入qa队列,对2取余为0的存入qb队列,定义count变量计数;
while qb和qb都不为空
{
	count++;
	输出一个qa队列元素;
	qa元素出队;

	if count对2取余等于0
		输出一个qb队列元素;
	    qb元素出队;
		count = 0;
	end if;
}
while qa不为空
输出qa元素;qa元素出队;
while qb不为空
输出qb元素,qb元素出队;

2.2.2 知识点总结

  • queue模板的应用
  • 注意输出的格式(使用flag来表示输出格式,每一环节都可能是第一个输出数据的地方)
  • 间隔人数出队的处理办法(类似于报数问题)
  • 需要考虑队列是否为空(若有不为空的还要继续输出)

3.阅读代码

3.1 题目及解题代码

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

————来源:力扣(LeetCode)

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        const int n = T.size();
        vector<int> ans(n, 0);
        stack<int> stk;
        // 正向遍历温度列表
        for(int i = 0; i < n; i++)
        {
            // 当栈不为空且待比较元素大于栈顶元素时,弹栈且更新ans
            while(!stk.empty() && T[i] > T[stk.top()])
            {
                ans[stk.top()] = i - stk.top();
                stk.pop();
            }
            stk.push(i);
        }
        return ans;
    }
};

作者:bei-zhi-hu
来源:力扣(LeetCode)

3.2 设计思路

维护一个存储下标的单调栈,从栈底到栈顶的下标对应的温度列表中的温度依次递减。如果一个下标在单调栈里,则表示尚未找到下一次温度更高的下标。正向遍历温度列表。对于温度列表中的每个元素 T[i],如果栈为空,则直接将 i 进栈,如果栈不为空,则比较栈顶元素 prevIndex 对应的温度 T[prevIndex] 和当前温度 T[i],如果 T[i] > T[prevIndex],则将 prevIndex 移除,并将 prevIndex 对应的等待天数赋为 i - prevIndex,重复上述操作直到栈为空或者栈顶元素对应的温度小于等于当前温度,然后将 i 进栈。由于单调栈满足从栈底到栈顶元素对应的温度递减,因此每次有元素进栈时,会将温度更低的元素全部移除,并更新出栈元素对应的等待天数,这样可以确保等待天数一定是最小的。 ————力扣

新建立一个单调栈,使栈中的下标对应的温度从栈底到栈顶是依次递减的。遍历整个温度数组,如果栈不空,且当前数字大于栈顶元素,所以需要取出栈顶元素,由于当前数字大于栈顶元素的数字,而且一定是第一个大于栈顶元素的数,直接求出下标差就是二者的距离。继续比较新的栈顶元素,直到当前数字小于等于栈顶元素停止,然后将数字入栈,这样就可以一直保持递减栈,且每个数字和第一个大于它的数的距离也可以算出来。

正向遍历温度列表,使用递减栈。

  • 如果栈为空,则直接将当前温度下标i入栈;
  • 栈不空时,将栈顶下标的温度T[top]与待入栈下标的温度T[i]对比。
  • 如果T[i]更小就入栈(满足递减栈属性),i后移一位;
  • 如果T[i]更大则说明此待入栈元素是相距最近的更大元素,直接更新答案(下标差),然后将栈顶top弹出栈。直到栈空或者t[i] <= T[top]时直接入栈,i后移一位。
  • 持续上述循环,直到正向遍历整个温度列表。

3.3 伪代码

新建一个栈stk存储温度所对应的下标,新建一个存储温度的数组T,新建一个存储需要等待的天数的数组ans;
for i = 0 to T.length - 1
    while 栈不为空且待比较元素大于栈顶元素时// 当栈不为空且待比较元素大于栈顶元素时,弹栈且更新ans
              得到该栈顶下标所对应的温度的等待天数:当前元素下标 - 栈顶元素下标;
	      栈顶元素出栈stk.Pop();
		
    end while;
        入栈stk.Push(i);
end for;

3.4 优势及难点。

优势
使用栈来解该题减少了大量遍历次数,大大提高了程序的计算效率,利用栈一端进出与先进后出的特性,使用单调栈的方法,保存各个温度所对应的下标并使它按照温度单调递减排列,只需要比较栈顶元素与当前元素的大小情况,就可找到距离最近的温度升高的一天。
难点

  • 单调栈的分析与使用
  • 该题中每个温度都需要计算等待天数
  • 等待天数的计算方法(利用当前下标与栈顶所存储的下标,计算二者的差)
posted @ 2021-04-05 22:53  栀夏~  阅读(184)  评论(2编辑  收藏  举报