目录
💓1.栈与队列的特点
逻辑结构与线性表相同,但运算受到了限制。
栈:限制在表的一端(表尾)进行插入和删除的线性表
队列:限制在表的一端(表尾)进行插入,另一端(表头)进行删除
- 栈:先进后出、后进先出
- 队列:先进先出、后进后出
⚽2.思考题
了解了栈和队列的插入删除逻辑,我们不妨来思考一道题。
将数字1、2、3依次分别插入到栈与队列中(在插入123的过程中也可以删除元素),问:123出栈或出队的顺序?
我们先来考虑栈?
注:大家可以盯着格子里看,出现一个元素就是入栈,没了就是在出栈,可以把出栈顺序记住嗷~
123
132
213
231
321
那么出栈顺序一共有5种,312这种情况不可能出现。
那队列呢?
显然只有123这一种情况。
❄️3.栈与队列的作用
3.1物理结构中的栈
在CPU当中,有⼀个非常核心的模块,叫做ALU(算术逻辑单元),也就是执行各种计算和逻辑运算操作的⼀个部件,是我们CPU的执行单元。
比如1 + 1 = 2。如果说,有多个运算参与,比如 111 + 222 + 333,这个时候,他会先计算 111 + 222 ,然后得到⼀个临时结果,再将我们的临时结果和剩下的数字相加。这个时候,我们就需要将临时结果找个地方存⼀下,这个地方,我们叫寄存器,他们的名字就是AX,BX,CX,DX等等。临时结果就保存在寄存器中。
为了实现更复杂的计算,能不能做特别多的寄存器呀?答案是不能,如果做特别多的寄存器,只会增加我们的CPU设计上的成本和复杂性。这个时候,就需要从外面找帮⼿。
这个帮手需要什么条件呢?那就是速度要快。然后,计算机的设计者就将目光放在了内存条上面。 接下来,就要在内存条中划出⼀片专门的区域,用来临时存储数据。既然是专用的,那就需要有个名字。叫做栈。栈其实只是⼀个乳名,实际上这个区域叫做堆栈。
要注意,内存里面还有⼀个区域,叫做堆。和栈的特性很不⼀样。所以,栈的本质就是内存中的⼀个区域。他的特殊之处就在于,CPU从中存取数据的方式。就好像弹夹装子弹,就是先⼊后出,后⼊先出。 而CPU中,很多对于数据的操作都要遵循这个规律。在内存中,有⼀个个的存储单元,在存储单元中,就有⼀片区域,就是堆栈。
——摘自《大话数据结构》
3.2数据结构中的栈
数据结构中的栈只是模拟了物理结构中栈的操作方式,是一种抽象的数据结构。
3.3队列的应用
- 解决主机与外部设备速度不匹配
- 多⽤户引起的资源竞争问题
📌4.栈的操作
栈也同样分为顺序栈与链栈
而栈其实只有两个简单的操作,个人认为用链栈实现的意义不大,还更繁琐,故后文的实现都为顺序栈。
我们借用栈底指针与栈顶指针,来控制入栈与出栈的操作。(实际上,只用栈底指针即可实现,需要移动的指针为栈底指针,栈顶指针一直指向栈顶,用于判断栈满,也可以不要它)
4.1变量定义
我采用了全局变量来定义
int *stack; 指针数组
int top; 栈顶指针,不移动,用于判断栈满;也不可不要它
int end; 栈底指针,随着出入栈而移动
4.2初始化
stack = (int*)malloc(Size * sizeof(stack));
top = Size - 1;
end = -1;
- 此种方法:end是指向栈底元素的。若没有元素,则指向-1,若栈满,则指向top
- 另一种方法:end指向栈底元素上一个位置。若没有元素,则指向0,若栈满,则指向Size(即top+1)
4.3入栈
若栈未满:
- 栈底指针上移一个位置
- 插入元素
void insert(int n)
{
if (end != top)
{
end++;
stack[end] = n;
//也可以stack[++end]=n;
}
else
{
printf("栈已满,入栈失败\n");
}
}
4.4出栈
若栈不空:栈底指针下移一个位置(释放出栈结点)
void del()
{
if (end != -1)
{
//顺序栈,这里就先只移动了指针
end--;
//链栈需要删除结点(free)
}
else
{
printf("栈空,出栈失败\n");
}
}
📌5.队列的操作
分类1-存储结构
- 顺序队列
- 链队列
注:以下操作的实现为顺序队列。
分类2-实现方式
- 单向队列
- 循环队列
- 双端队列
- 循环队列是我们为了解决单向队列出现的一个问题(假溢出)而优化得到的;
- 双端队列是将栈和队列的操作融合在一起实现的,STL中的deque就是采用双端队列的思想,我们下一节再讲~
假溢出
- 队头(front)设置在最近⼀个离开队列元素所占的位置。
- 队尾(rear)设置在最近⼀个进行入队列的元素位置。
- 队头和队尾随着插⼊和删除的变化而变化。
当队列为空时,front = rear;但是,这个时候,会有⼀个问题。当我们的队头指针指向size - 1 时,代表长度已满。但是根据队列的规则,就实际情况来说,他还有空闲的空间。那么这个时候,我们就将其称之为假溢出。
为了解决假溢出的问题,我们有两种办法。
- 每删除一个数据元素,余下的所有数据元素顺次移动一个位置;
- 将我们的顺序队列看成是首位相接的循环结构。
显然第一种方法操作过于繁琐,于是我们来研究第二种方法。
5.1初始化
两指针都指向0
5.2入队
- 队尾指针+1%MAXSIZE
- 元素插入
void insert_queue(int key)
{
if (isFull())
{
printf("队满\n");
}
else
{
rear = (rear + 1) % maxSize;
queue[rear] = key;
}
}
5.3出队
队首指针+1%MAXSIZE
队列中的元素就是从q.f指向元素到q.r-1指向元素[q.f,q.r)
void delete_queue()
{
if (isEmpty())
{
//队空 提示
}
else
{
front = (front + 1) % maxSize;
}
}
此时仍然存在一个问题?
队空和队满时两指针都是q.f==q.r, 无法根据队首和队尾指针的相对位置判断队列是处于“空” 还是处于“满”的状态。
解决办法
- 牺牲一个存储位置,让队空:q.r==q.f ;队满:(q.r+1)%MAXSIZE==q.f
- 设一计数器,初始化时计数器清0,入队时,计数器+1,出队时计数器-1
int isFull()
{
if ((rear+1)%maxSize == front)
{
return 1;
}
else
{
return 0;
}
}
int isEmpty()
{
if (front == rear)//指针相遇 队空
{
return 1;
}
else {
return 0;
}
}
🌟6.完整代码
6.1顺序栈
#include<stdio.h>
#include<stdlib.h>
#define Size 4
//直接采用全局变量
int *stack;//指针数组
int top;//栈顶指针,不移动,用于判断栈满;也不可不要它
int end;//栈底指针,随着出入栈而移动
void Init();
void insert(int n);
void del();
//栈没有其他的操作了
void show();
int main()
{
Init();
insert(1);
insert(2);
insert(3);
insert(4);
insert(5);
show();//在此处栈满 从栈顶到栈底依次为4321
printf("\n");
del();//321
show();
return 0;
}
void Init()
{
stack = (int*)malloc(Size * sizeof(stack));
top = Size - 1;
end = -1;
//此种方法:end是指向栈底元素的。若没有元素,则指向-1,若栈满,则指向top
//另一种方法:end指向栈底元素上一个位置。若没有元素,则指向0,若栈满,则指向Size(top+1)
}
void insert(int n)
{
if (end != top)
{
end++;
stack[end] = n;
//也可以stack[++end]=n;
}
else
{
printf("栈已满,入栈失败\n");
}
}
//删除不能指定元素,只能删除栈顶元素
void del()
{
if (end != -1)
{
//顺序栈,这里就先只移动了指针
end--;
//链栈需要删除结点(free)
}
else
{
printf("栈空,出栈失败\n");
}
}
void show()
{
for (int i = 0; i <= end; i++)
{
printf("%d", stack[i]);
}
}
6.2顺序循环队列
#include<stdio.h>
#include<stdlib.h>
int isEmpty();//队列是否为空
int isFull();//队列是否满
void insert_queue(int);//插入操作
void delete_queue();//删除操作
/*全局变量*/
int* queue;//用数组实现队列
int front;//头指针 因为是数组 这里用下标代表指针
int rear;//尾指针 因为是数组 这里用下标代表指针
int maxSize=5;//当前的容量
int main()
{
insert_queue(3);
return 0;
}
int isEmpty()
{
if (front == rear)//指针相遇 队空
{
return 1;
}
else {
return 0;
}
}
int isFull()
{
if ((rear+1)%maxSize == front)
{
return 1;
}
else
{
return 0;
}
}
/*
插入操作
int key : 待插入的关键字
*/
void insert_queue(int key)
{
if (isFull())
{
printf("队满\n");// 要么提示 要么扩容
}
else
{
rear = (rear + 1) % maxSize;
queue[rear] = key;
}
}
void delete_queue()
{
if (isEmpty())
{
//队空 提示
}
else
{
front = (front + 1) % maxSize;
}
}