队列的基本概念
队列(Queue)也叫队,也是一种操作受限的线性表,在操作队的时候,我们只能在表的一端进行插入,在表的另一端进行删除。不同与栈,队列不再是一条“断头路”,而是单行线了。
观察我们的示意图,我们发现与“栈”的“先进后出”不同,队列是先进先出的,越早入队列,则越早出队列。下面是我简单地定义的一些基本操作函数。
我们要注意的是,栈和队列是相对动态的,我们不能随便读取栈和队列中间的某个数据。
InitQueue(&Q)
: 初始化队列Enqueue(&Q, item)
: 将元素入队。Dequeue(&Q)
: 将队首元素出队。Gethead(Q)
: 返回队首元素QueueEmpty(Q)
: 判断队列是否为空。IsFull(Q)
: 判断队列是否已满。ClearQueue(&Q)
: 清空队列。QueueLength(Q)
: 返回队列中元素的个数。
队列的顺序存储
栈的出口入口都是一端,所以我们只需要一个栈顶指针即可。和栈不同,队列需要两个指针,分别是队头指针(front)和队尾指针(rear),队头指针指向队头元素,队尾指针指向队尾元素的下一个位置。
#include <stdio.h>
#include <stdbool.h>
#define MAXSIZE 100 // 定义队列的最大长度
typedef int ElementType;
typedef struct {
ElementType data[MAXSIZE]; // 队列的存储空间
int front; // 队头指针
int rear; // 队尾指针
} Queue;
当队空时,Q.front == Q.rear == 0,这也是我们的初始化条件。我们依次可以得到初始化代码和判空代码。
void InitQueue(Queue *Q) {
Q->front = 0;
Q->rear = 0;
}
bool IsEmpty(Queue Q) {
return Q.front == Q.rear;
}
看完判空操作,我们来考虑判满操作,我们不能用Q.rear == MaxSize做判满条件。图(d)中仅仅只有一个元素,但满足该条件。所以,下面的函数并不是正真的判满函数,但这个函数我们会在接下来的操作中用到。
bool isFull(Queue *q) {
return q->rear == MAXSIZE - 1;
}
最后我们来看进队和出队操作,在利用我们的判空、“判满” 操作的基础上,我们通过移动队尾/队首指针来实现。
bool enqueue(Queue *q, int element) {
if (isFull(q)) {
return false;
}
q->data[++q->rear] = element;
return true;
}
bool dequeue(Queue *q, int *element) {
if (isEmpty(q)) {
return false;
}
*element = q->data[q->front++];
return true;
}
与我们以前介绍的DS(数据结构)一样,我们使用Python的面向对象编程技术来模拟队列。由于Python功能多样,没有像C一样那样接近硬件,所以展现出来很简洁。
class Queue:
def __init__(self):
self.items = []
def is_empty(self):
return len(self.items) == 0
def enqueue(self, item):
self.items.append(item)
def dequeue(self):
if self.is_empty():
return None
return self.items.pop(0)
队列的链式存储
队列当然也有链式存储,我们照猫画虎地叫它“链队列”,实际上,它是一个同时带有队头指针和队尾指针的单链表。我们这里简单地用c语言来写一个定义例子,等你来初始化、操作!
#include <stdlib.h>
// 定义队列节点的结构体
typedef struct Node {
int data; // 存储的数据
struct Node *next; // 指向下一个节点的指针
} Node;
// 定义队列的结构体
typedef struct {
Node *front; // 队列头指针
Node *rear; // 队列尾指针
} Queue;
循环队列的介绍
循环队列,顾名思义,就是一个环形的队列。
观察循环操作时,意欲搞一些操作函数,比如判空、判满时,我们发现,麻烦来了。
判空和判满时,队首指针和队尾指针都指向了同一个位置。所以,我们只能人为地牺牲一个单元来区分队空和队满。对于循环队列,准确的判断队列是否满的方法是检查rear
指针的下一个位置是否是front
指针的位置,因为我们总是保留一个元素的空间来区分队列为空和队列为满的情况。所以,判断队列是否满的条件应该是(rear + 1) % MaxSize == front
。在下面的代码中,我们会强调取余操作。
#include <stdbool.h>
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int front;
int rear;
} CircularQueue;
void initC_Queue(CircularQueue *q) {
q->front = 0;
q->rear = 0;
}
bool isC_QueueFull(CircularQueue *q) {
return (q->rear + 1) % MAX_SIZE == q->front;
}
bool isC_QueueEmpty(CircularQueue *q) {
return q->rear == q->front;
}
bool Enqueue(CircularQueue *q, int element) {
if (isC_QueueFull(q)) {
return false;
}
q->data[q->rear] = element;
q->rear = (q->rear + 1) % MAX_SIZE;
return true;
}
bool Dequeue(CircularQueue *q, int *element) {
if (isC_QueueEmpty(q)) {
return false;
}
*element = q->data[q->front];
q->front = (q->front + 1) % MAX_SIZE;
return true;
}
class CircularQueue:
def __init__(self, capacity):
self.data = [None] * capacity
self.front = 0
self.rear = 0
self.capacity = capacity
def is_full(self):
return (self.rear + 1) % self.capacity == self.front
def is_empty(self):
return self.rear == self.front
def enqueue(self, item):
if self.is_full():
return False
self.data[self.rear] = item
self.rear = (self.rear + 1) % self.capacity
return True
def dequeue(self):
if self.is_empty():
return None
item = self.data[self.front]
self.front = (self.front + 1) % self.capacity
return item