大纲
- 队列假溢出问题
- 循环队列的空间/满判断推理
- 队列的顺序存储设计/链式存储设计
一、队列假溢出问题
队列
队列同 栈 一样也是一种线性表,它是在表的一端插入数据,在另一端取出元素。插入元素的这个一端称之为队尾,取出元素的一端称之为队头。
队列的特性:
- 只能从队尾插入,从队头取出(入队,出队)
- FIFO,先进先出。
![](https://img-blog.csdnimg.cn/20200414215209327.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NoZW5nZGFWb2xsZXliYWxs,size_16,color_FFFFFF,t_70)
假溢出
如上图C 假设队列的长度为6,初始化空队列如下如所示。c1、c2 出队,这时候fornt(队头)指针指向2,rear(队尾)指针指向3。
然后 c4、c5、c6 相继入队,c3、c4相继出队。这个时候正常情况下front 指针指向下标为 4的位置,rear 指向5。如果出队的速度快与入队的速度,就有可能 front 和 rear 都指向4的位置,这时候认为队满,但是实际上队里还有许多空闲位置。 这种情况是由于一头出队,一头入队的机制照成的,这种现象叫 - - 假溢出。
为了解决在队尾空出来一个位置,不通过front 与 rear 相等的方式判断队满(因为 front 与rear 相等也是空队列的标识,所以只能用来判断一种状态)。
二、循环队列的空间/满判断推理
加入不是循环的,因为队列是一端进一端出的,如果一直出队入队,前面出队的位置是空着的这样有再多的内存也不够用(或者是进行位移,但是消耗会很大)。为了解决这样的情况,所以使用 循环队列(为什么要用循环的,后面”假溢出“会说明),也就是逻辑上队列是一个环形(并不是说内存是环形,而是逻辑上)。在使用队列时,使用连个变量表示队头和队尾(例如:Q.front 队头 Q.rear 队尾)。
通过队头队尾的指针可以判断队列是否为空或则是否存满。如下图
如上图,队空的时候 Q.front == Q.rear,队满的时候也是 Q.front == Q.rear。这样的话就无法判断到这个时候是队空还是队满,多以就需要在队尾空出一个位置,不能存满。也就是说只有队空的时候Q.front == Q.rear,其他时候就不会是Q.front == Q.rear。
判断队空和队满的公式:
队空:Q.front == Q.rear
队满:(Q.rear + 1) % MAXSIZE == Q.front; MAXSIZE 为开辟的队列长度
三、队列的顺序存储设计/链式存储设计
同栈一样,队列也可以通过 顺序 来实现,也可以通过 链式 来实现。
#include <stdio.h>
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 //存储空间初始分配量
typedef int Status;//Status 是函数的类型,其值是函数结果状态代码l,如OK等
typedef int QElemType;//QElemType 类型根据实际情况而定,这里假设为int
1、顺序存储
//循环队列 顺序存储队列结构
typedef struct{
// 1、开辟连续空间存储队列数据内容
QElemType data[MAXSIZE];
// 2、索引(对头/队尾)
int fornt;
int rear
} SqQueue;
//2、将队列清空
//队列也是线性表,所以不需要把数据删除,只需要把队头和队尾都指向0,即可。在用到的时候自动会覆盖掉对应位置上的数据
Status ClearQueue(SqQueue *Q){
Q->fornt = Q->rear = 0;
return OK;
}
//3、是否为空
//判断队列是否为空
//为空返回 TRUE 否者返回 FALSE
Status QueueEmpty(SqQueue Q){
if (Q.fornt == Q.rear) return TRUE;
return FALSE;
}
//4、长度
//返回队列的元素个数(长度)
//因为是循环的所以队尾指针指向的值可能小于队头,实际的长度应该是两个值的差的绝对值
//也可以写成 (Q.rear - Q.fornt + MAXSIZE) % MAXSIZE;
int QueueLength(SqQueue Q){
return abs(Q.rear - Q.fornt);
}
//5、队头
//若队列不为空,则用e返回Q的队头元素,并返回OK,否者返回ERROR
Status GetHead(SqQueue Q, QElemType *e){
if (Q.rear == Q.fornt) return ERROR;
*e = Q.data[Q.fornt];
return OK;
}
//6、入队
//若队列没有满,则插入元素e 为新队尾元素
Status EnQueue(SqQueue *Q, QElemType e){
if ((Q->rear + 1) % MAXSIZE == Q->fornt) return ERROR;//已满
Q->data[Q->rear] = e;//元素e赋值给队尾
Q->rear = (Q->rear + 1) % MAXSIZE;//因为是循环队列,所以不能简单的用 ++
return OK;
}
//7、出队
//若队列不为空,则删除Q中队头的元素,用e 返回值。
Status DeQueue(SqQueue *Q, QElemType *e){
if (Q->rear == Q->fornt) return ERROR;//为空
*e = Q->data[Q->fornt];
Q->fornt = (Q->fornt + 1) % MAXSIZE;//因为是循环队列,所以不能简单的用 ++
return OK;
}
//8、遍历
//从队头到队尾依次输出队列的每个元素数组
//只是看一下内部的数据,无关从从头还是从尾开始
Status QueueTraverse(SqQueue Q){
int i;
i = Q.fornt;
while ((i + Q.fornt) != Q.rear) {
printf("%d ",Q.data[i]);
i = (i + 1) % MAXSIZE;
}
printf("\n");
return OK;
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
Status j;
int i = 0;
SqQueue Q;
QElemType d;
InitQueue(&Q);
printf("初始化队列后,队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
printf("入队:\n");
while (i < 10) {
EnQueue(&Q, i);
i++;
}
QueueTraverse(Q);
printf("队列长度为: %d\n",QueueLength(Q));
printf("现在队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
printf("出队:\n");
//出队
DeQueue(&Q, &d);
printf("出队的元素:%d\n",d);
QueueTraverse(Q);
//获取队头
j=GetHead(Q,&d);
if(j)
printf("现在队头元素为: %d\n",d);
ClearQueue(&Q);
printf("清空队列后, 队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
return 0;
}
//*********************************
初始化队列后,队列空否?1(1:空 0:否)
入队:
0 1 2 3 4 5 6 7 8 9
队列长度为: 10
现在队列空否?0(1:空 0:否)
出队:
出队的元素:0
1 2 3 4 5 6 7 8
现在队头元素为: 1
清空队列后, 队列空否?1(1:空 0:否)
2、链式队列
//结点结构
typedef struct QNode{
QElemType data;
struct QNode * next;
}QNode, *QueuePtr;
// 队列链表结构
typedef struct{
QueuePtr front,rear;//队头、队尾指针
}LinkQueue;
//1、初始化队列
Status InitQueue(LinkQueue * Q){
// 1、新创建一个结点
QueuePtr p = (QueuePtr)malloc(sizeof(QNode));
if (p == NULL) return ERROR;
// 2、将队头、队尾指向新创建的结点
Q->front = p;
Q->rear = p;
// 3、头结点的指针置空
Q->front->next = NULL;
return OK;
}
//2、销毁队列Q
//用Q->fornt 去遍历队列,然后释放。用 Q->rear 指向遍历到的下一个结点 防止链表后面的结点丢失
Status DestoryQueue(LinkQueue *Q){
while (Q->front) {
Q->rear = Q->front->next;
free(Q->front);
Q->front = Q->rear;
}
return OK;
}
//3、将队列置空
Status ClearQueue(LinkQueue *Q){
QueuePtr p,q;
p = Q->front->next;
Q->rear = Q->rear;
Q->front->next = NULL;
while (p) {
q = p->next;
free(p);
p = q;
}
return OK;
}
//4、判断队列 Q 是否为空
Status QueueEmpty(LinkQueue Q){
if (Q.front == Q.rear)return TRUE;
return FALSE;
}
//5、队列长度
int QueueLength(LinkQueue Q){
int count = 0;
QueuePtr p;
// p = Q.front;
// while (p != Q.rear) {
p = Q.front->next;
while (p) {
p = p->next;
count ++;
}
return count;
}
//5、插入元素 e 为队列Q的新元素
Status EnQueue(LinkQueue *Q, QElemType e){
// 创建新结点并赋值
QueuePtr p = malloc(sizeof(QNode));
if (!p) return ERROR;
p->data = e;
p->next = NULL;
Q->rear->next = p;//将新结点插到队尾
Q->rear = p;//修改队尾指针
return OK;
}
//7、出队
Status DeQueue(LinkQueue *Q, QElemType *e){
if (Q->front == Q->rear) return ERROR;
//将要删除的队头结点暂时存储在p
QueuePtr p;
p = Q->front->next;
//将要删除的队头结点的值赋值给e
*e = p->data;
//将原队列头结点的后继p->next 赋值给头结点后继
Q->front->next = p->next;
//若队头就是队尾,则删除后将rear指向头结点
if (Q->rear == p->next) Q->rear = Q->front;
free(p);
return OK;
}
//8、获取头元素
Status GetHead(LinkQueue Q, QElemType *e){
if (Q.front == Q.rear) return FALSE;
*e = Q.front->next->data;//返回队头元素的值,队头不变
return TRUE;
}
//9、遍历队列
Status QueueTraverse(LinkQueue Q){
QueuePtr p;
p = Q.front->next;
while (p) {
printf("%d ",p->data);
p = p->next;
}
printf("\n");
return OK;
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
Status iStatus;
QElemType d;
LinkQueue q;
//1.初始化队列q
iStatus = InitQueue(&q);
//2. 判断是否创建成
if (iStatus) {
printf("成功地构造了一个空队列\n");
}
//3.判断队列是否为空
printf("是否为空队列?%d (1:是 0:否)\n",QueueEmpty(q));
//4.获取队列的长度
printf("队列的长度为%d\n",QueueLength(q));
//5.插入元素到队列中
EnQueue(&q, -3);
EnQueue(&q, 6);
EnQueue(&q, 12);
printf("队列的长度为%d\n",QueueLength(q));
printf("是否为空队列?%d (1:是 0:否)\n",QueueEmpty(q));
//6.遍历队列
printf("队列中的元素如下:\n");
QueueTraverse(q);
//7.获取队列头元素
iStatus = GetHead(q, &d);
if (iStatus == OK) {
printf("队头元素是:%d\n",d);
}
//8.删除队头元素
iStatus =DeQueue(&q, &d);
if (iStatus == OK) {
printf("删除了的队头元素为:%d\n",d);
}
//9.获取队头元素
iStatus = GetHead(q, &d);
if (iStatus == OK) {
printf("新的队头元素为:%d\n",d);
}
//10.清空队列
ClearQueue(&q);
//11.销毁队列
DestoryQueue(&q);
return 0;
}
//*******************************
成功地构造了一个空队列
是否为空队列?1 (1:是 0:否)
队列的长度为0
队列的长度为3
是否为空队列?0 (1:是 0:否)
队列中的元素如下:
-3 6 12
队头元素是:-3
删除了的队头元素为:-3
新的队头元素为:6