目录
不多废话了,开始硬核学习吧!
顺序队列的思想和原理:
什么是顺序队列?顺序队列,顾名思义,就是一个特殊的顺序表,队首操作只能是删除,队尾操作只能是插入,就像日常超市排队买东西。所以,对于最早进入队列的元素,最早离开队列,最晚进入队列的元素,最后才离开,这也符合我们的日常逻辑。其次,它的物理存储是顺序存储,每个数据元素在计算机内存中彼此相连。
顺序队列分为两种:一般顺序队列,循环顺序队列。
一般的顺序队列的实现:
由于顺序队列是顺序存储,因此我们可以用数组就可以方便地存储队列中的数据元素。队列是一个有限的空间,所以我们在构造一个队列时,要给定队列大小。
其次,如何表示队首和队尾?可以考虑两个指针,front和rear。front是队首数据元素的下标,而为了方便给队列插入数据,我们把rear设定为队尾数据元素的后一个单元的下标。
整个队列用一个结构体表示,那么顺序队列就可以这样构造
#define MAXSIZE 100
typedef struct{
int a[MAXSIZE];
int front, rear;
}Squeue;
那么,如何判断一个队列是空队列呢?我们知道,有两个指针,一个是队首,一个是队尾的下一个单元。那么如果这两个指针处于队列中的同一个位置,是不是就代表这个队列是一个空的队列?也就是说,两个指针的差值正好表示队列现在的元素个数,这和顺序栈是一样的思想。
那么,有了这个思想,我们就可以很轻易地实现判断一个队列是否是个满队列。也就是将两个指针做差,得到队列数据元素个数,与队列的容量要求相比即可。
通常,对于顺序队列有以下基本操作:
1.初始化一个队列。
2.判断队列是否为空
3.展示队列中的所有数据元素
4.把队首元素展示出来
5.在队尾给队列插入一个元素
6.在队首删除一个元素
由于一般的顺序队列和下面将要补充的循环顺序队列在插入和删除方面有所不同,而其他操作基本相同,在这里先给出一般顺序队列的前四个简单操作,插入和删除操作另述:
#include<stdio.h>
#define MAXSIZE 100
typedef struct{
int a[MAXSIZE];
int front, rear;
}Squeue;
Squeue S;
void init(Squeue *S) //初始化一个队列
{
S->front = S->rear = 0;
}
int empty(Squeue S) //判断队列是否为空
{
return (S.front==S.rear?0:1); //这里因为个人习惯,如果是0就表示这个队列是空队列,1代表队列非空
}
void display(Squeue S) //展示队列的所有数据元素
{
if (!empty(S)) //如果队列为空,就没有数据元素可以展示了
{
return;
}else
{
for (int i = S.front; i < S.rear; i++)
{
printf("%5d", S.a[i]);
}
}
}
int get(Squeue S) //得到队首元素
{
if (!empty(S))
{
return;
}
return S.a[S.front];
}
一般顺序队列的插入和删除:
直接上代码:
void insert(Squeue *S, int x)
{
int i;
if(S->rear == MAXSIZE)
{
return;
}
S->a[rear++] = x;
}
void dele(Squeue *S)
{
if(S->front == S->rear)
{
return;
}
S->front++;
}
一般顺序队列总代码:
#include<stdio.h>
#define MAXSIZE 100
typedef struct{
int a[MAXSIZE];
int front, rear;
}Squeue;
Squeue S;
void init(Squeue *S) //初始化一个队列
{
S->front = S->rear = 0;
}
int empty(Squeue S) //判断队列是否为空
{
return (S.front==S.rear?0:1); //这里因为个人习惯,如果是0就表示这个队列是空队列,1代表队列非空
}
void display(Squeue S) //展示队列的所有数据元素
{
if (!empty(S)) //如果队列为空,就没有数据元素可以展示了
{
return;
}else
{
for (int i = S.front; i < S.rear; i++)
{
printf("%5d", S.a[i]);
}
}
}
int get(Squeue S) //得到队首元素
{
if (!empty(S))
{
return;
}
return S.a[S.front];
}
void insert(Squeue *S, int x)
{
int i;
if(S->rear == MAXSIZE)
{
return;
}
S->a[rear++] = x;
}
void dele(Squeue *S)
{
if(S->front == S->rear)
{
return;
}
S->front++;
}
循环顺序队列的实现:
刚才我们暂时没有展示如何插入和删除,我们先来考虑这两个操作的算法思想是如何实现的。
插入,如果在队尾插入一个元素,直接在rear指向的队列位置放入数据元素就行了,同时rear还要+1更新到队尾的下一个单元。
删除,如果在队首删除一个元素,这更加简单,我们只要将front指针直接往后移动一个位置即可。
那么,从这我们就知道了,如果不断地删除队首元素,在数组中,会不断地空出前面的位置,而数组的容量是有限的,会导致越来越多的数组空间被浪费,无疑,队列也会越来越短。怎么重新利用失去的数组空间?
如果在队列初始化时,把队首和队尾连接起来,成为一个环状的队列,那么rear指向数组最后一个单元后,还可以回到数组的第一个单元,这样就可以充分利用数组空间了,这就是循环顺序队列。
我们先看看循环顺序队列怎么实现。常用的方法是,在队列中牺牲一个数据元素,如果数组最大容量为MAXSIZE,那么队列最多能放MAXSIZE - 1个数据元素。此时,队列为满的条件是:
(rear + 1) % MAXSIZE == front
通过联系上面的那个图片,想想为什么会这样。从图片来看,rear的位置就是那个牺牲了的数组单元,同时是队尾的后一个单元,这时候循环队列是满的,因为我们牺牲了数组的一个单元。取余操作保证rear+1不会越界,回到数组首端。
判断队列为空的条件依然是rear == front,和顺序队列一样。
于是得到循环顺序队列的删除和插入操作。
void insert(Squeue *S, int e) //插入操作
{
int i;
if ((S->rear + 1) % MAXSIZE == S->front) //判断队列是否满
{
return;
}
S->a[S->rear] = e;
S->rear = (S->rear + 1) % MAXSIZE; //队尾指针递增,而不是简单的rear++
}
void delete(Squeue *S) //删除操作
{
if (S->front == S->rear)
{
return;
}
S->front = (S->front + 1) % MAXSIZE; //队首指针递增
}
循环顺序队列总代码:
其实,有时候在一些圈类问题用循环顺序队列更好。所以,可以得到循环顺序队列的总实现代码:
#include<stdio.h>
#define MAXSIZE 100
typedef struct{
int a[MAXSIZE];
int front, rear;
}Squeue;
Squeue S;
void init(Squeue *S)
{
S->front = S->rear = 0;
}
int empty(Squeue S)
{
return (S.front==S.rear?0:1);
}
void display(Squeue S)
{
if (!empty(S))
{
return;
}else
{
for (int i = S.front; i < S.rear; i++)
{
printf("%5d", S.a[i]);
}
}
}
int get(Squeue S)
{
if (!empty(S))
{
return;
}
return S.a[S.front];
}
void insert(Squeue *S, int e)
{
int i;
if ((S->rear + 1) % MAXSIZE == S->front)
{
return;
}
S->a[S->rear] = e;
S->rear = (S->rear + 1) % MAXSIZE;
}
void delete(Squeue *S)
{
if (S->front == S->rear)
{
return;
}
S->front = (S->front + 1) % MAXSIZE;
}
希望这篇文章可以帮助到你理解顺序队列,欢迎点赞和收藏~~~祝你学习快乐!