你是否想过,微信中的“撤销消息”如何实现回到当初?12306如今被大众使用而不崩溃?这些看似神奇的场景,背后竟然可以“逆转时空,操作时间”的数据结构——栈(Stack)与队列(Queue)
当你深入理解栈与队列,会发现它们不仅是数据结构,更深一种哲学!有的问题需要面向过去(栈),而有的问题需要面向未来(队列)!下面随我一起解剖栈与队列!
目录
栈
一.栈的基本概念
栈(Stack):只允许在一端进行插入或者删除的线性表。因此上图上的为何说操作受限大家理解了吧!栈只支持在一端进行插入和删除操作。
栈顶(Top):线性表允许进行插入和删除的那一端
栈底(Bottom):固定的,不允许进行插入和删除的另一端
空栈:不含任何元素的空表
压栈(Push):数据的插入也叫压栈(压栈、进栈、入栈都是一个意思)
存储特点:栈中的数据遵循先进后出、后进先出的UIFO(Last In First Out)原则(理解为“有先有后”),如下图理解:比如一个容器,先放入的物体就先被拿出来!它的存储顺序与拿出顺序相反
在这里需要一个注意的地方:这里所说的存储特点指的是栈内数据元素的先进后出,下面我们看一个例子,加深理解这个存储规则:现在有一组数据(1,2,3,4,5)
入栈的顺序为 1 2 3 4 5
而出栈的顺序可以有很多种,比如:2 3 4 5 1 , 1 2 4 5 3 , 1 2 3 5 4 , 2 3 1 4 5........
解释:比如2 3 1 4 5 ,先将1 2压栈,然后2出栈,再把3压栈,3出栈,接着1出栈,4压栈,4出栈,5再压栈,5再出栈 ,那么总的出栈顺序就是 2 3 1 4 5
二.栈的基本操作
与之前的实现链表功能其实差不多,但是受栈存储原则的影响,比如:每次只能访问栈顶元素,那么栈的功能操作也就被简化了!我们一起来看看吧!
初始化栈:在使用栈之前,于情于理都需要先给它开辟一块空间、设置栈顶指针、设置容量
判断栈空:检查是否是空栈
入栈:初始化之后我们肯定需要放入元素
读取栈元素:严格意义来说是读取的栈顶元素,因为受存储规则限制,只能优先拿出栈顶的元素
出栈:因为“先进后出,先出后进”的规则,我们无法在在一组数据里面去突然插入某个数据,因此功能受限!
销毁栈:我们在初始化时已经开辟好了一处栈空间,那么我们使用完应该及时销毁!
三.栈的顺序存储结构
首先我们来理解概念:顺序存储结构就是通过使用一个连续的存储空间,来存放栈中的元素,这个存储空间的起始位置是固定的,而栈顶的位置是随着入栈和出栈的操作发生变化。为了指示栈顶的位置,我们需要使用一个栈顶指针,来实时告诉我们栈顶的位置,这个指针我们一般命名为top。这种利用内存中的一片起始位置确定的连续的存储区域来存放栈中的元素就叫顺序栈!
这里有一个注意点:就是栈顶指针top,上面所说的顺序栈的空间是连续的,比如数组,它是通过下标来找到元素的,但是我们的栈顶指针在这里并不是从0下标开始,而是从-1下标开始,如图:
栈空:顺序栈的数组下标如果从0开始,那么栈顶指针从top=-1开始
栈满:顺序栈的数组下标如果从0开始,那么栈顶指针此时top=maxsize-1
栈的顺序实现方式我们可以采用两种:数组形式 指针形式,接下来我会分别实现!
四.顺序栈—数组方式实现
四.1栈的结构创建
#define _CRT_SECURE_NO_WARNINGS 1
#define MAX 20
typedef struct Olderly
{
int data[MAX];//栈空间的最大存储大小
int top;//栈顶指针
}Stack;
四.2栈的初始化
//顺序栈—初始化
void Perliminery(Stack* s)
{
s->top = -1;//初始化栈顶指针
}
四.2判断栈空
栈空的条件就是栈顶指针为-1,咱直接判断值即可!
//顺序栈—判断栈空
bool Vacant(Stack s)
{
if (s.top == -1)
{
return true;
}
else
return false;
}
四.3入栈
这里需要注意的点有两个:
1:存元素到栈时,必须判断是否是满栈
2:按照逻辑,栈的第一个元素应该满足 top==0,所以需要先改变栈顶指针top,再存储元素
//顺序栈—入栈
void Enter(Stack* s,int n)
{
//判断是否满栈
if (s->top == MAX - 1)
{
return;
}
//进行元素存储
s->top++;
s->data[s->top] = n;
}
四.4读取栈顶元素
这里咱们只需要正常的读栈顶元素就行了,并不需要改变栈顶指针
//顺序栈—读取栈顶元素
void Read(Stack s)
{
//判断是否是空栈
if (s.top == -1)
{
return;
}
printf("%d\n", s.data[s.top]);
}
四.5出栈
注意是元素从栈中先被拿出来,然后栈顶指针下移一位,这是逻辑关系
//顺序栈—出栈
void Out(Stack* s)
{
//判断是否是空栈
if (s->top == -1)
{
return;
}
s->data[s->top] = 0;
s->top--;
}
四.6栈销毁
因为我们这里并没有给栈开辟动态空间,因此我们只需要清楚栈中的元素即可,同时改变栈顶指针
//顺序栈—栈销毁
void Undermine(Stack* s)
{
//判断空栈
if (s->top == -1)
{
return;
}
while (s->top != -1)
{
s->data[s->top] = 0;
s->top--;
}
printf("销毁成功\n");
}
五.合并代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"taxt.h"
int main()
{
Stack s;
//需要存入的元素
int n = 1;
//顺序栈—初始化
Perliminery(&s);
//顺序栈—判断栈空
Vacant(s);
//顺序栈—入栈
Enter(&s,n);
//顺序栈—读取栈顶元素
Read(s);
//顺序栈—出栈
Out(&s);
//顺序栈—栈销毁
Undermine(&s);
return 0;
}
头文件:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdbool.h>
#include<stdio.h>
#define MAX 20
typedef struct Olderly
{
int data[MAX];//栈空间的最大存储大小
int top;//栈顶指针
}Stack;
//顺序栈—初始化
void Perliminery(Stack* s);
//顺序栈—判断栈空
bool Vacant(Stack s);
//顺序栈—入栈
void Enter(Stack* s,int n);
//顺序栈—读取栈顶元素
void Read(Stack s);
//顺序栈—出栈
void Out(Stack* s);
//顺序栈—栈销毁
void Undermine(Stack* s);
实现函数:
#define _CRT_SECURE_NO_WARNINGS 1
#include"taxt.h"
//顺序栈—初始化
void Perliminery(Stack* s)
{
s->top = -1;//初始化栈顶指针
}
//顺序栈—判断栈空
bool Vacant(Stack s)
{
if (s.top == -1)
{
return true;
}
else
return false;
}
//顺序栈—入栈
void Enter(Stack* s,int n)
{
//判断是否满栈
if (s->top == MAX - 1)
{
return;
}
//进行元素存储
s->top++;
s->data[s->top] = n;
}
//顺序栈—读取栈顶元素
void Read(Stack s)
{
//判断是否是空栈
if (s.top == -1)
{
return;
}
printf("%d\n", s.data[s.top]);
}
//顺序栈—出栈
void Out(Stack* s)
{
//判断是否是空栈
if (s->top == -1)
{
return;
}
s->data[s->top] = 0;
s->top--;
}
//顺序栈—栈销毁
void Undermine(Stack* s)
{
//判断空栈
if (s->top == -1)
{
return;
}
while (s->top != -1)
{
s->data[s->top] = 0;
s->top--;
}
printf("销毁成功\n");
}
六.顺序栈—指针方式实现
六.1栈的创建
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdbool.h>
#include<stdio.h>
#define MAX 20
typedef struct Orderly
{
int *data;//存放数据
int top;//栈顶指针
}Stack;
六.2栈的初始化
//顺序栈—初始化
void Perliminery(Stack* s)
{
//给栈开辟空间
s->data = (int*)malloc(MAX * sizeof(int));
if (s->data == NULL)
{
perror("malloc");
return;
}
//初始化栈顶指针
s->top = -1;
}
六.3判断栈空
//顺序栈—判断栈空
bool Vacant(Stack s)
{
if (s.top == -1)
{
return false;
}
else
return true;
}
六.4入栈
//顺序栈—入栈
void Enter(Stack* s,int n)
{
//判断栈满
if (s->top == MAX - 1)
{
return;
}
s->top++;
s->data[s->top] = n;
}
六.5读取栈元素
//顺序栈—读取栈顶元素
void Read(Stack s)
{
//判断空栈
if (s.top == -1)
{
printf("读取失败\n");
return;
}
printf("%d\n", s.data[s.top]);
}
六.6出栈
//顺序栈—出栈
void Out(Stack* s)
{
//判断是否是空栈
if (s->top == -1)
{
printf("读取失败\n");
return;
}
while (s->top != -1)
{
s->data[s->top] = 0;
s->top--;
}
}
六.7栈销毁
这里我们使用了动态开辟,我们应该先清除数据域,再释放开辟好的内存!养成良好习惯
//顺序栈—栈销毁
void Undermine(Stack* s)
{
//判断是否是空栈
if (s->top == -1)
{
printf("读取失败\n");
return;
}
while (s->top != -1)
{
s->data[s->top] = 0;
s->top--;
}
free(s->data);
s->data = NULL;
printf("销毁成功\n");
}
七.合并代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
#include "lim.h"
int main()
{
Stack s;
//需要存入的元素
int n = 1;
//顺序栈—初始化
Perliminery(&s);
//顺序栈—判断栈空
Vacant(s);
//顺序栈—入栈
Enter(&s, n);
//顺序栈—读取栈顶元素
Read(s);
//顺序栈—出栈
Out(&s);
Enter(&s, n);
//顺序栈—栈销毁
Undermine(&s);
return 0;
}
头文件:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdbool.h>
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef struct Orderly
{
int *data;//存放数据
int top;//栈顶指针
}Stack;
//顺序栈—初始化
void Perliminery(Stack*s);
//顺序栈—判断栈空
bool Vacant(Stack s);
//顺序栈—入栈
void Enter(Stack*s,int n);
//顺序栈—读取栈顶元素
void Read(Stack s);
//顺序栈—出栈
void Out(Stack* s);
//顺序栈—栈销毁
void Undermine(Stack* s);
函数实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
//顺序栈—初始化
void Perliminery(Stack* s)
{
//给栈开辟空间
s->data = (int*)malloc(MAX * sizeof(int));
if (s->data == NULL)
{
perror("malloc");
return;
}
//初始化栈顶指针
s->top= -1;
}
//顺序栈—判断栈空
bool Vacant(Stack s)
{
if (s.top == -1)
{
return false;
}
else
return true;
}
//顺序栈—入栈
void Enter(Stack* s,int n)
{
//判断栈满
if (s->top == MAX - 1)
{
return;
}
s->top = 0;
s->data[s->top] = n;
}
//顺序栈—读取栈顶元素
void Read(Stack s)
{
//判断空栈
if (s.top == -1)
{
printf("读取失败\n");
return;
}
printf("%d\n", s.data[s.top]);
}
//顺序栈—出栈
void Out(Stack* s)
{
//判断是否是空栈
if (s->top == -1)
{
printf("读取失败\n");
return;
}
while (s->top != -1)
{
s->data[s->top] = 0;
s->top--;
}
}
//顺序栈—栈销毁
void Undermine(Stack* s)
{
//判断是否是空栈
if (s->top == -1)
{
printf("读取失败\n");
return;
}
while (s->top != -1)
{
s->data[s->top] = 0;
s->top--;
}
free(s->data);
s->data = NULL;
printf("销毁成功\n");
}
八.栈的链式存储结构
栈的链式存储就是字面意思,利用链表来实现这种数据存储。在链式存储中,存到栈的每个元素被封装成一个个节点,通过指针连接,形成链表,它具有动态性。咱们得步骤是:定义结构体,实现各个操作函数,然后测试(下面未导入,只确保程序正确,可自行测试!),需要注意是否是野指针的情况。下面咱们依次来实现各个操作!
九.链式栈—链表方式实现
九.1栈的创建
我们需要定义链式栈的节点结构 与 链式栈结构,前者就是链表的节点,后者就是栈顶指针。如下:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdbool.h>
#include<stdlib.h>
#include<stdio.h>
//节点结构
typedef struct StackNode
{
int data;//数据域
struct StackNode* next;//指针域
}StackNode;
//链式栈结构
typedef struct Stack
{
StackNode* top;//栈顶指针
int size;//当前元素个数
}Stack;
九.2栈的初始化
这个函数我们可以不需要参数,直接在函数里面给栈开辟空间,开辟成功后然后初始化
//链式栈的初始化
Stack* Perliminery()
{
//给栈开辟空间
Stack* Linked = (Stack*)malloc(sizeof(Stack));
//判断空间有效性
if (Linked == NULL)
{
perror("malloc");
return NULL;
}
//初始化栈
Linked->top = NULL;
Linked->size = 0;
return Linked;
}
九.3判断栈空
链式栈为空注意的是栈顶指针指向为空,而不是*top为-1,这点需要区分顺序栈与链式栈的不同
//判断栈空
bool Vacant(Stack* Lined)
{
if (Lined->top == NULL)
{
printf("栈为空\n");
return false;
}
else
return true;
}
九.4入栈
入栈的话就是存储元素到节点了,这里咱们调用链式栈,然后在函数里面去给它开辟空间。
首先top开始是空的,那么第一个节点的next是top,然后改变栈顶指针指向,在插入第二个、第三个节点的时候,top的值就是前一个节点了,它的头节点是逐渐改变的!如下图:
//入栈
void Enter(Stack* Lined, int n)
{
//给数据开辟节点空间
StackNode* newnode = (StackNode*)malloc(sizeof(StackNode));
//判断空间有效性
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->data = n;
//新节点指向原来的栈顶
newnode->next = Lined->top;
//更换栈顶指针
Lined->top = newnode;
//数据个数增加
Lined->size++;
}
九.5读取栈顶元素
注意咱们是在栈顶指针里面开辟的节点哦!
//读取栈顶元素
void Read(Stack* Lined)
{
//判断栈空
if (Lined->top == NULL)
{
printf("读取失败\n");
return;
}
printf("%d\n", Lined->top->data);
}
九.6出栈
//出栈
void Out(Stack* Lined)
{
//判断栈空
if (Lined->top == NULL)
{
printf("出栈失败\n");
return;
}
//记录头节点
StackNode* tail = Lined->top;
//改变栈顶指针指向
Lined->top = Lined->top->next;
//释放原头节点
free(tail);
tail = NULL;
//元素个数减少
Lined->size--;
}
九.7栈销毁
注意先释放节点,再释放栈空间
//栈销毁
void Undermine(Stack* Lined)
{
//判断栈空
if (Lined->top == NULL)
{
printf("读取失败\n");
return;
}
while (Lined->size != 0)
{
//记录头节点
StackNode* tail = Lined->top;
//改变栈顶指针指向
Lined->top = Lined->top->next;
//释放原头节点
free(tail);
tail = NULL;
//元素个数减少
Lined->size--;
}
//释放栈空间
free(Lined);
Lined = NULL;
}
十.合并代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
int main()
{
//这里的n代指要存的数据
int n = 0;
//链式栈的初始化
Stack* Lined = Perliminery();
//判断栈空
Vacant(Lined);
//入栈
Enter(Lined,n);
//读取栈顶元素
Read(Lined);
//出栈
Out(Lined);
//栈销毁
Undermine(Lined);
return 0;
}
头文件:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdbool.h>
#include<stdlib.h>
#include<stdio.h>
//这里的n代指要存的数据
int n = 1;
//节点结构
typedef struct StackNode
{
int data;//数据域
struct StackNode* next;//指针域
}StackNode;
//链式栈结构
typedef struct Stack
{
StackNode* top;//栈顶指针
int size;//当前元素个数
}Stack;
//链式栈的初始化
Stack* Perliminery();
//判断栈空
bool Vacant(Stack* Lined);
//入栈
void Enter(Stack* Lined ,int n);
//读取栈顶元素
void Read(Stack* Lined);
//出栈
void Out(Stack* Lined);
//栈销毁
void Undermine(Stack* Lined);
函数实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
//链式栈的初始化
Stack* Perliminery()
{
//给栈开辟空间
Stack* Linked = (Stack*)malloc(sizeof(Stack));
//判断空间有效性
if (Linked == NULL)
{
perror("malloc");
return NULL;
}
//初始化栈
Linked->top = NULL;
Linked->size = 0;
return Linked;
}
//判断栈空
bool Vacant(Stack* Lined)
{
if (Lined->top == NULL)
{
printf("栈为空\n");
return false;
}
else
return true;
}
//入栈
void Enter(Stack* Lined, int n)
{
//给数据开辟节点空间
StackNode* newnode = (StackNode*)malloc(sizeof(StackNode));
//判断空间有效性
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->data = n;
//新节点指向原来的栈顶
newnode->next = Lined->top;
//更换栈顶指针
Lined->top = newnode;
//数据个数增加
Lined->size++;
}
//读取栈顶元素
void Read(Stack* Lined)
{
//判断栈空
if (Lined->top == NULL)
{
printf("读取失败\n");
return;
}
printf("%d\n", Lined->top->data);
}
//出栈
void Out(Stack* Lined)
{
//判断栈空
if (Lined->top == NULL)
{
printf("出栈失败\n");
return;
}
//记录头节点
StackNode* tail = Lined->top;
//改变栈顶指针指向
Lined->top = Lined->top->next;
//释放原头节点
free(tail);
tail = NULL;
//元素个数减少
Lined->size--;
}
//栈销毁
void Undermine(Stack* Lined)
{
//判断栈空
if (Lined->top == NULL)
{
printf("读取失败\n");
return;
}
while (Lined->size != 0)
{
//记录头节点
StackNode* tail = Lined->top;
//改变栈顶指针指向
Lined->top = Lined->top->next;
//释放原头节点
free(tail);
tail = NULL;
//元素个数减少
Lined->size--;
}
//释放栈空间
free(Lined);
Lined = NULL;
}
十一.顺序栈与链式栈总结
首先链式栈没有固定的一个容量,咱们每次入栈直接调用函数就行了。读取栈顶元素复杂度为O(1),入栈和出栈的时间复杂度均为O(n)。而每次增加元素存储都需要额外开辟空间,每次自定义函数总需要考虑栈是否是空栈。且无法任意读取某个元素,只能访问栈顶元素。链式栈相对于顺序栈复杂一点点,需要在栈顶指针里面开辟节点,然后改变指针指向,期间需要理清连接关系!
————————————————
队列
十二.队列的基本概念
这里我就以容易理解的形式去介绍队列:队列的满足先进先出(First-In-First-Out,FIFO),它可以在一端插入另一端删除,新元素被插入到队列的末尾(也就是队尾),元素只能在队列的前端(队首)被删除。队列由以下三个结构组成:
队头:允许删除的一端,又称队首
队尾:允许插入的一端()
队列长度:即队列中的元素数量
因此队尾那一端是入队的,队首那一端就是出队的!如下图:
十三.队列的基本操作
队列中比较基础的操作步骤如下,下面我会依次实现!
初始化队列:创建一个空队列,并给它分配空间
入队:将一个元素添加到队列的尾部,如果队列未满,则将新元素添加到队尾
出队:在队列中删除一个元素,如果队列非空,删除队头的元素,队头指针前移
十四.队列的顺序存储结构
队列的顺序存储也是使用连续的地址来存放队列的元素。需要设置的两个指针:队头指针与队尾指针,通过两个下标来指向队列的首尾,对于rear的指向,可先往下读!如图所示:(下图real纠正为rear!)
下面实现的顺序队列是特别基础的!非常适合新手小白去学习,解释备注也很清楚!
十五.顺序队列—数组方式实现
十五. 1队列的创建
这里我们采用的是数组,成员自然应该有一个数组,然后有队头、队尾,最大成员为21,而实际只能存储20个,因为rear下标是需要多走一位的,避免以rear为下标指到数组外面,当rear位21,就队满了!此时实际刚好存到了20个元素。
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#define MAX 21
typedef struct Older
{
int arr[20];//定义数组
int head;//队头下标
int rear;//队尾下标
}Older;
十五.2队列初始化
初始化两个下标,以及初始化一下这个数组!
//初始化队列
void Perliminery(Older*s)
{
//初始化队列成员
s->head = 0;
s->rear = 0;
// 初始化一下arr数组
for (int i = 0; i < MAX-1; i++)
{
s->arr[i] = 0;
}
}
十五.3入队
入队需要注意,我们改变的是这个结构体成员,自然应该传结构体指针,大家get到了吗!
//入队
void Inport(Older* s, int data)
{
//先看是否满队
if (s->rear == MAX)
{
printf("队列已满\n");
return;
}
//数据存到队尾指针下标处
s->arr[s->rear] = data;
//队尾指针右移
s->rear++;
}
十五.4出队
出队需要注意:我们是以队头为下标进行出队的,因此无需真正的去删除元素,只需要改变下标
//出队
void Export(Older* s)
{
//判断是否是空对列
if (s->rear == s->head)
{
printf("空队列,无法出队\n");
return;
}
//删除的时候,我们本质是队头下标右移即可
s->head++;
}
十五.5打印队列元素
//打印队列元素
void Printf(Older s)
{
//判断是否是空对列
if (s.rear == s.head)
{
printf("空队列,无法打印\n");
return;
}
//注意不要去直接使用head,不然会改变队头下标
printf("队列元素:");
for (int i = s.head; i != s.rear; i++)
{
printf("%d ", s.arr[i]);
}
printf("\n");
}
十六.合并代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
int main()
{
Older s;
int data = 0;//入队的元素
//初始化队列
Perliminery(&s);
//入队
data = 1;
Inport(&s,data);
//出队
Export(&s);
//打印队列元素
Printf(s);
return 0;
}
头文件:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#define MAX 21
typedef struct Older
{
int arr[20];//定义数组
int head;//队头下标
int rear;//队尾下标
}Older;
//初始化队列
void Perliminery(Older* s);
//入队
void Inport(Older* s, int data);
//出队
void Export(Older* s);
//打印队列元素
void Printf(Older s);
函数实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
//初始化队列
void Perliminery(Older*s)
{
//初始化队列成员
s->head = 0;
s->rear = 0;
// 初始化一下arr数组
for (int i = 0; i < MAX-1; i++)
{
s->arr[i] = 0;
}
}
//入队
void Inport(Older* s, int data)
{
//先看是否满队
if (s->rear == MAX)
{
printf("队列已满\n");
return;
}
//数据存到队尾指针下标处
s->arr[s->rear] = data;
//队尾指针右移
s->rear++;
}
//出队
void Export(Older* s)
{
//判断是否是空对列
if (s->rear == s->head)
{
printf("空队列,无法出队\n");
return;
}
//删除的时候,我们本质是队头下标右移即可
s->head++;
}
//打印队列元素
void Printf(Older s)
{
//判断是否是空对列
if (s.rear == s.head)
{
printf("空队列,无法打印\n");
return;
}
//注意不要去直接使用head,不然会改变队头下标
printf("队列元素:");
for (int i = s.head; i != s.rear; i++)
{
printf("%d ", s.arr[i]);
}
printf("\n");
}
十七.队列的循环存储结构
循环队列是一种利用固定大小的数组实现的高效队列结构,以【模运算】实现环形存储,避免了“假溢出”。按照之前顺序队列的理解,我们先简单理解为将顺序队列的首尾连接!下面看细节!
环形结构:队头与队尾下标在未达到数组的末尾时,通过取模回到原点
队满判断:(rear+1)%size==head(理解如下)
对空判断:rear==head
十八.循环队列—数组方式实现
十八.1 结构创建
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef struct Older
{
int arr[20];//定义数组
int head;//队头下标
int rear;//队尾下标
}Older;
十八.2初始化
//初始化
void Perliminery(Circular* s)
{
s->head = 0;
s->rear = 0;
// 初始化一下arr数组
for (int i = 0; i < MAX - 1; i++)
{
s->arr[i] = 0;
}
}
十八.3入队
重点理解环形入队!
//入队
void Inport(Circular* s,int data)
{
//判断队满
if ((s->rear+1) % MAX == s->head)
{
printf("队满,存储失败\n");
return;
}
s->arr[s->rear] = data;
//环形移动
s->rear = (s->rear + 1) % MAX;
}
十八.4出队
//出队
void Export(Circular* s)
{
//判断对空
if (s->rear == s->head)
{
printf("队空,无法出队\n");
return;
}
//队头下标前移
s->head = (s->head + 1) % MAX;
}
十八.5打印
//打印
void Print(Circular s)
{
//判断队空
if (s.rear == s.head)
{
printf("队空,无法打印\n");
return;
}
printf("\n");
printf("队列元素:");
for (int i = s.head; i < s.rear; i++)
{
printf("%d ", s.arr[i]);
}
}
十九.合并代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
int main()
{
//存放的数据
int data = 0;
Circular s;
//初始化
Perliminery(&s);
//入队
data = 1;
Inport(&s ,data);
//出队
Export(&s);
//打印
Print(s);
return 0;
}
头文件:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef struct Circular
{
int arr[20];//定义数组
int head;//队头下标
int rear;//队尾下标
}Circular;
//初始化
void Perliminery(Circular* s);
//入队
void Inport(Circular* s,int data);
//出队
void Export(Circular* s);
//打印
void Print(Circular s);
函数实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
//初始化
void Perliminery(Circular* s)
{
s->head = 0;
s->rear = 0;
// 初始化一下arr数组
for (int i = 0; i < MAX - 1; i++)
{
s->arr[i] = 0;
}
}
//入队
void Inport(Circular* s,int data)
{
//判断队满
if ((s->rear+1) % MAX == s->head)
{
printf("队满,存储失败\n");
return;
}
s->arr[s->rear] = data;
//环形移动
s->rear = (s->rear + 1) % MAX;
}
//出队
void Export(Circular* s)
{
//判断队空
if (s->rear == s->head)
{
printf("队空,无法出队\n");
return;
}
//队头下标前移
s->head = (s->head + 1) % MAX;
}
//打印
void Print(Circular s)
{
//判断队空
if (s.rear == s.head)
{
printf("队空,无法打印\n");
return;
}
printf("\n");
printf("队列元素:");
for (int i = s.head; i < s.rear; i++)
{
printf("%d ", s.arr[i]);
}
}
二十.队列的链式存储结构
链式队列肯定需要链表!我们选择单链表来实现,其次需要有两个指针,指向队尾和队首。在上面我们可以看到新增的节点从队尾进入,连接在第一个节点的后面,然后按照先进先出原则,我们直接从第一个节点开始出列就行了,因此单链表很适合这种结构顺序!下面我们来实现代码:
二十一.链式队列—链表方式实现
二十一 .1搭建结构
我们需要定义两个结构体:节点结构、队列结构。如何全部写在一个结构体里面,会导致参数变的许多,影响效率,错误率高!
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdbool.h>
#include<stdlib.h>
#include<stdio.h>
#define MAX 10
//节点结构
typedef struct StackNode
{
int data;//数据域
struct Node* next;//指针域
}Node;
//链式队列结构
typedef struct Stack
{
Node* head;//队头指针
Node* rear;//队尾指针
int size;//当前元素个数
}Linked;
二十一.2初始化
记得先malloc开辟队列空间,然后在空间里面进行初始化,初始化的时候因为head与rear都是指针,自然应该初始化为指向空,size设置为0,然后返回一个队列结构体的指针!
//初始化
Linked* Perliminery()
{
//malloc给队列开辟空间
Linked* Newnode = (Linked*)malloc(sizeof(Linked));
//判断空间有效性
if (Newnode == NULL)
{
perror("malloc");
return NULL;
}
//初始化空间
Newnode->rear = NULL;
Newnode->head = NULL;
Newnode->size = 0;
return Newnode;
}
二十一.3入队
入队需要开辟新节点,注意这个节点应该添加到队尾的末端,需要判断是不是空队列,然后根据情况更新rear与head指向,同时size要自增!注意动态开辟的判断!
//入队
void Export(Linked* Newnode, int data)
{
//动态开辟节点
Node* News = (Node*)malloc(sizeof(Node));
//判断空间有效性
if (News == NULL)
{
perror("malloc");
return;
}
//存放元素、初始化节点
News->data = data;
News->next = NULL;
Newnode->size++;
//判断队空,队空就都指向新节点,否则连接新节点、移动rear指针
if (Newnode->size == 0)
{
Newnode->rear = News;
Newnode->head = News;
}
else
{
Newnode->rear->next = News;
Newnode->rear = News;
}
}
二十一.4出队
出队需要记录当前的第一个节点,然后改变队头指针,还要考虑出队后是否是空队列,需要队rear进行讨论!
//出队
void Inport(Linked* Newnode)
{
//判断空队列
if (Newnode->size == 0)
{
printf("空队列,无法出队\n");
return;
}
Node* tail = Newnode->head;
//改变队头指针已及队列元素个数
Newnode->head = Newnode->head->next;
Newnode->size--;
//如果出队后变为空队列,变换队尾指针为空
if (Newnode->head == NULL)
{
Newnode->rear = NULL;
}
free(tail);
tail = NULL;
}
二十一.5打印
判断对空,打印注意改变队头指针!
//打印
void Print(Linked* Newnode)
{
//判断空队列
if (Newnode->size == 0)
{
printf("空队列,无法打印\n");
return;
}
printf("\n");
printf("队列元素:");
Node* tail = Newnode->head;
while (tail)
{
printf("%d ", tail->data);
tail = tail->next;
}
}
二十二.合并代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
int main()
{
//要存进去的元素
int data = 0;
//初始化
Linked* Newnode = Perliminery();
//入队
data = 1;
Export(Newnode, data);
data = 2;
Export(Newnode, data);
data = 3;
Export(Newnode, data);
//出队
Inport(Newnode);
//打印
Print(Newnode);
return 0;
}
头文件:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdbool.h>
#include<stdlib.h>
#include<stdio.h>
#define MAX 10
//节点结构
typedef struct StackNode
{
int data;//数据域
struct Node* next;//指针域
}Node;
//链式队列结构
typedef struct Stack
{
Node* head;//队头指针
Node* rear;//队尾指针
int size;//当前元素个数
}Linked;
//初始化
Linked* Perliminery();
//入队
void Export(Linked* Newnode, int data);
//出队
void Inport(Linked* Newnode);
//打印
void Print(Linked* Newnode);
函数实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
//初始化
Linked* Perliminery()
{
//malloc给队列开辟空间
Linked* Newnode = (Linked*)malloc(sizeof(Linked));
//判断空间有效性
if (Newnode == NULL)
{
perror("malloc");
return NULL;
}
//初始化空间
Newnode->rear = NULL;
Newnode->head = NULL;
Newnode->size = 0;
return Newnode;
}
//入队
void Export(Linked* Newnode, int data)
{
//动态开辟节点
Node* News = (Node*)malloc(sizeof(Node));
//判断空间有效性
if (News == NULL)
{
perror("malloc");
return;
}
//存放元素、初始化节点
News->data = data;
News->next = NULL;
Newnode->size++;
//判断队空,队空就都指向新节点,否则连接新节点、移动rear指针
if (Newnode->size == 0)
{
Newnode->rear = News;
Newnode->head = News;
}
else
{
Newnode->rear->next = News;
Newnode->rear = News;
}
}
//出队
void Inport(Linked* Newnode)
{
//判断空队列
if (Newnode->size == 0)
{
printf("空队列,无法出队\n");
return;
}
Node* tail = Newnode->head;
//改变队头指针以及队列元素个数
Newnode->head = Newnode->head->next;
Newnode->size--;
//如果出队后变为空队列,变换队尾指针为空
if (Newnode->head == NULL)
{
Newnode->rear = NULL;
}
free(tail);
tail = NULL;
}
//打印
void Print(Linked* Newnode)
{
//判断空队列
if (Newnode->size == 0)
{
printf("空队列,无法打印\n");
return;
}
printf("\n");
printf("队列元素:");
Node* tail = Newnode->head;
while (tail)
{
printf("%d ", tail->data);
tail = tail->next;
}
}
二十三.顺序队列与链式队列总结
顺序队列元素数量比较固定,而链式的相对就比较灵活!顺序队列内存连续、访问局部性高,访问快!可以快速访问,而链式的节点分布不连续,因此无“逻辑”可言。链式难度相对更大,需要考虑多种情况,需要细敲打磨,只要链式基础过关,再把链式队列能自己实现,那么顺序队列也就轻松了!