1 队列的基本概念
1.1 定义
- 队列就是一端允许插入,另一端允许删除的线性表
- 队头、队尾、空队列
-
1.2 基本操作
- InitQueue(&Q):初始化队列、构造一个空队列Q
- DestroyQueue(&Q):销毁队列,销毁并释放队列Q所占用的内存空间
- EnQueue(&Q,x):入队、若队列未满,则将x插入,使之成为新的队尾
- DeQueue(&Q,&x):出队、若队列非空、则删除队头元素,并用x返回
- GetHead(Q,&x):读队头元素、若队列非空,则将队头元素赋值给x
- QueueEmpty(Q,&x):判断队列是否为空
2 队列的顺序实现
- 定义顺序队列
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];// 用静态数组存放队列元素
int front,rear;// 队头、队尾指针
}SqQueue;
- 初始化操作:
void InitQueue(SqQueue &Q) {
Q.rear = Q.front = 0;// 初始时,队头】队尾指针都指向0
}
- 判空操作
bool QueueEmpty(SqQueue Q) {
if(Q.rear == Q.front)// 队空条件
return true;
else return false;
}
- 入队操作
bool EnQueue(SqQueue &Q,ElemType x) {
if((Q.rear + 1)%MaxSize == Q.front)// 队满条件
return false;
Q.data[rear] = x;
Q.rear = (Q.rear + 1)%MaxSize;// 队尾指针+1取模
return true;
}
思考1 队满条件是什么?
为什么要空一个位置呢?因为初始化时是Q.rear = Q.front = 0;为了与它区分,我们多空一个位置出来
思考2 Q.rear = (Q.rear + 1) mod MaxSize的原因,为什么不是Q.rear = Q.rear + 1?
如图,思考这个情况,当队尾指针指向MaxSize-1、队头指针指向元素d的位置时,此时想要入队的话,那队尾指针必须要重置回来,不然再Q.rear++的话就跑出数组范围了,那么怎么才能达到这样的效果呢?我们采用对MaxSize取模的操作,只要大于MaxSize了就会重置回来,用模运算将存储空间再逻辑上变成了“环状”
思考3 如何计算队列存在元素个数?
元素个数 = (Q.rear + MaxSize - Q.front)%MaxSize
- 出队操作
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
}
- 读取队头元素
bool QueueEmpty(SqQueue Q,ElemType &x) {
if(Q.rear == Q.front) {// 队空
return false;
}
x = Q.data[Q.front];
return true;
}
2.1 其他的判断队列已满/已空办法
- 定义队列时,多添加一个数据表示队列长度
// 定义
typedef struct {
ElemType data[MaxSize];
int front,rear;
int size;// 记录大小长度
}SqQueue;
// 初始化
void InitQueue(SqQueue &Q) {
Q.rear = Q.front = 0;// 初始时,队头】队尾指针都指向0
size = 0;
}
// 入队操作
bool EnQueue(SqQueue &Q,ElemType x) {
if((Q.rear == Q.front && size == MaxSize)// 队满条件,这样就不浪费空间了
return false;
Q.data[rear] = x;
Q.rear = (Q.rear + 1)%MaxSize;// 队尾指针+1取模
size++;
return true;
}
// 出队操作
bool DeQueue(SqQueue &Q,ElemType &x) {
if(Q.rear == Q.front && size == 0) {// 队空条件
return false;
}
x = Q.data[Q.front];
Q.front = (Q.front + 1)%MaxSize;// 队头元素向后移动一位,取模原因如上
size--;
return true
}
- 定义队列时,添加一个数据记录最近进行的是删除还是插入操作
//定义
typedef struct {
ElemType data[MaxSize];
int front,rear;
int tag;// 最近进行的是插入or删除
}SqQueue;
3 队列的链式实现
3.1 用链式存储实现队列
这两个的区别在于front指针指向的是头结点还是第一个元素
- 带头结点
typedef struct LinkNode {// 链式队列结点
ElemType data;
struct LinkNode *next
}LinkNode;
typedef struct {// 链式队列
LinkNode *front,*rear;// 队列的队头和队尾指针
}LinkQueue;
- 不带头结点
typedef struct LinkNode {// 链式队列结点
ElemType data;
struct LinkNode *next
}LinkNode;
typedef struct {// 链式队列
LinkNode *front,*rear;// 队列的队头和队尾指针
}LinkQueue;
3.2 基本操作
- 带头结点
// 初始化
void InitQueue(LinkQueue &Q) {
// 初始时首尾指针都指向头结点
Q.rear = Q.front = (LinkNode *)malloc(sizeof(LinkNode));
Q.front = Null;
}
// 判断空
bool isEmpty(LinkQueue Q) {
if(Q.rear == Q.front) {
return true;
}else {
return true;
}
}
// 入队(新节点插入在队尾之后)
void EnQueue(LinkQueue &Q,ElemType x) {
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
s.data = x;
s.next = Null;
Q.rear->next = s;
Q.rear = s;
}
// 出队
bool DeQueue(LinkQueue &Q,ElemType &x) {
if(Q.front == Q.rear) {
return false;// 空队列
}else {
LinkNode *p = Q.front->next;
x = p->data;
Q.front = p->next;
if(Q.rear == p) {// 如果你删除的是最后一个结点的话
Q.rear = Q.front;// 需要修改队尾指针指向头结点
}
free(p);
return true;
}
}
- 不带
// 初始化
void InitQueue(LinkQueue &Q) {
// 初始时首尾指针都指向Null
Q.rear = Q.front = Null;
}
// 判断空
bool isEmpty(LinkQueue Q) {
if(Q.rear == Null) {
return true;
}else {
return true;
}
}
// 入队(新节点插入在队尾之后)
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;
Q.rear = s;
}
}
// 出队
bool DeQueue(LinkQueue &Q,ElemType &x) {
if(Q.front == Null) {
return false;// 空队列
}else {
LinkNode *p = Q.front;
x = p->data;
Q.front = Q.front->next;
if(Q.rear == p) {// 如果你删除的是最后一个结点的话
Q.rear = Q.front = Null;// 需要修改队尾指针
}
free(p);
return true;
}
}
4 双端队列
-
双端队列:只允许从双端插入、双端输出的线性表
-
输入受限的双端队列:只允许一端插入、双端删除的线性表
-
输出首先的双端队列:只允许一端删除、两端插入的线性表
在栈中合法的输出序列,在双端队列中必定合法