TD03-队列
一、队列基本概念
队列是一种操作受限的线性表
先进先出(First In First Out)
对头(Front):允许删除的一端,又称队首
队尾(Rear):允许插入的一端
二、队列的顺序存储
队列的顺序存储是指分配一块连续的存储单元存放队列中的元素
1.顺序存储类型描述
//队列顺序存储类型描述
#define MaxSize 10
typedef struct {
Elemtype data[MaxSize];
int front, rear;
}SqQueue;
问题:通过初始化,可以由Q.rear = Q.front = 0判断队列是否为空,但是不能由作为Q.rear==MaxSize判断队列满的条件!!!因为会有假溢出,面对这样的情况,引出了循环队列
2.循环队列
由顺序队列的缺点,将顺序队列臆造为一个环状空间,把存储队列元素从逻辑上视为一个环,称为循环队列。主要利用除法取余运算
初始时:Q.rear = Q.front = 0
队首指针进1:Q.front = (Q.front + 1) % MaxSize;
队尾指针进1:Q.rear = (Q.rear + 1) % MaxSize;
队列长度:(Q.rear - Q.front + MaxSize) % MaxSize
问题:队空条件、队满条件都是Q.rear = Q.front,无法区分队空队满!!! 面对这样的问题,有了三种处理方式
3.判断队空队满的三种方式
1、牺牲一个单元来区分队空队满。入队时少用一个队列单元(比较常用)
队满:(Q.rear + 1) % MaxSize = = Q.front
队空:Q.rear = = Q.front
队列中元素个数:(Q.rear - Q.front + MaxSize) % MaxSize
2、定义类型时加入一个表示元素个数的变量,如int size;
初始化时:rear=front=0; size=0;
插入成功:size++;
删除成功:size–;
队满条件:size= =MaxSize;
队空条件:size= =0;
3、定义类型时加入一个表示队满队空状态的成员,如int tag;
初始化时:rear=front=0; tag=0;
插入成功:tag=1;
删除成功:tag=0;
队满条件:tag=1;
队空条件:tag=0;
(解释:只有删除操作导致队空;只有插入操作导致队满)
4.其他基本操作
1、初始化
//初始化
void InitQueue(SqQueue& Q) {
Q.rear = Q.front = 0; //初始化队首、队尾指针
}
2、判队空
//判队空
bool isEmpty(SqQueue Q) {
if (Q.rear == Q.front) //队空条件
return true;
else
return false;
}
3、入队
//入队
bool EnQueue(SqQueue& Q, Elemtype x) {
if ((Q.rear + 1) % MaxSize == Q.front) //队满
return false;
Q.data[Q.rear] = x;
Q.rear = (Q.rear + 1) % MaxSize; //尾指针加一取模
return true;
}
4、出队
//出队
bool DeQueue(SqQueue& Q, Elemtype& x) {
if (Q.rear == Q.front) //队空
return false;
x = Q.data[Q.front];
Q.front = (Q.front + 1) % MaxSize; //队头指针加一取模
return true;
}
5、取队头元素
//取队头元素
bool GetHead(SqQueue Q, Elemtype& x) {
if (Q.rear == Q.front)
return false;
x = Q.data[Q.front];
return true;
}
6、求循环队列长度
//求循环队列长度
int QueueLength(SqQueue Q) {
return(Q.rear - Q.front + MaxSize) % MaxSize;
}
三、队列的链式存储
队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表
出队:取出队头元素
入队:插入到队尾
1.链式存储类型描述
//队列的链式存储
//记录队长:可以设置变量,从队头开始遍历,统计长度,需要的结点数O(n);若经常需要可以在定义类型时加入length变量进行元素记录
typedef struct LinkNode { //链式队列结点
Elemtype data;
struct LinkNode* next;
}LinkNode;
typedef struct { //链式队列
LinkNode* front, * rear; //设置队尾指针:插入操作时就不需要从前往后查找
}LinkQueue;
2.其他基本操作
1、初始化队列
//初始化队列(带头结点)
void InitQueue(LinkQueue& Q) { //申请一个头结点并让头指针和尾指针同时指向头结点
Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
Q.front->next = NULL;
}
//初始化队列(不带头结点)
void InitQueue2(LinkQueue& Q) {
Q.front = NULL;
Q.rear = NULL;
}
2、判空
//判空(带头结点)
bool IsEmpty(LinkQueue Q) {
if (Q.front == Q.rear) //也可以用Q.front->next = NULL;作为判断
return true;
else
return false;
}
//判空(不带头结点)
bool IsEmpty2(LinkQueue& Q) {
if (Q.front == NULL)
return true;
else
return false;
}
3、入队
//入队(不带头结点)----因为不带头结点,所以第一个元素入队时需要特殊化
void EnQueue(LinkQueue& Q, Elemtype x) {
LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
s->data = x;
s->next = NULL;
if (Q.front == NULL) { //空队列中插入第一个元素单独处理,修改队头队尾指针
Q.front = s;
Q.rear = s;
}
else {
Q.rear->next = s; //非空队列插入元素修改rear尾指针
Q.rear = s;
}
}
4、出队
//出队(带头结点)
bool DeQueue(LinkQueue& Q, Elemtype& x) {
if (Q.front == Q.rear) //判空
return false;
LinkNode* p = Q.front->next; //指针指向出队结点
x = p->data; //返回值
Q.front->next = p->next; //移动front指针,如果只有一个元素,那命这里Q.front->next = p->next = NULL
if (Q.rear == p) //处理:若只有一个元素,删除后Q.rear = Q.front;空队列设置
Q.rear = Q.front;
free(p);
return true;
}
//出队(不带头结点)
bool DeQueue(LinkQueue& Q, Elemtype& x) {
if (Q.front == NULL) //判空
return false;
LinkNode* p = Q.front; //指针指向出队结点
x = p->data; //返回值
Q.front = p->next; //移动front指针
if (Q.rear == p) { //处理:若只有一个元素,删除后空队列设置
Q.front = NULL;
Q.rear = NULL;
}
free(p);
return false;
}
四、顺序存储与链式存储对比
1、顺序存储需要预分配空间,会出现队满;链式存储一般不会队满
2、单链表:适用于数据元素变动较大,而且不存在队列满且产生溢出的问题;适用于程序中多个队列,最好使用链式队列
五、纠错
1、循环队列存储在数组[0…n]中,入队时操作
A:rear=rear+1
B:rear=(rear+1)mod(n-1)
C:rear=(rear+1)mod(n)
D:rear=(rear+1)mod(n+1)
下标范围 0~n,数组容量n+1
2、用链式存储方式的队列进行删除操作时需要
A:仅修改头指针
B:头指针尾指针可能都要修改
头指针尾指针可能都要修改(例:队列中只有一个元素时,队尾指针还要移动:rear=front)