前言:
前面学习了栈,其实很好理解队列这个东西了,因为两者确实很像,但是有些细微的差距,读者可以自行比较一番,如果感觉不能理解的话,那就继续看下去吧。
1、顺序循环队列
(1)顺序循环队列的定义
其实第一次看这个标题的时候,我在想,你队列就队列嘛,为啥还要加一个循环两个字,通过仔细了解之后,发现我不仅要让我写出来的队列代码他成功体现队列特性,也就是先进先出,我们还要保证,如果队列不空的时候,我执行出队操作成功,但又要想到,我第一个元素出队了,那我的指针是不是应该指向下一个元素,让下一个元素成为队头元素。或者是我队列不满的时候,我执行入队操作,要保证我的元素合理放入队尾,依次循环,使得队列能够合理的运行我所有的出队和入队操作。话不多说,我们看代码。
#define MAXSIZE 100 // 定义队列的最大长度
typedef struct {
int data[MAXSIZE]; // 存储队列元素的数组
int front; // 队头指针
int rear; // 队尾指针
} SeqCircularQueue;
可以看到,我们定义了一个数组,两个指针在这个结构体里,因为我们要实现队头出,队尾入,所以必须有两个值来对应着队头元素和队尾元素,所以这里的指针并不是真的指针。
(2)顺序循环队列的初始化
// (2)顺序循环队列的初始化
void InitQueue(SeqCircularQueue *q) {
q->front = q->rear = 0;
}
这里注意,我们已经是设置队空时,两个指针指向同一个位置。所以,当我们队满时,就不能再用这个条件了,队满时应该用什么条件判断呢?我们在入队操作进行讲解。
(3)顺序循环队列的入队
// (3)顺序循环队列的入队
bool EnQueue(SeqCircularQueue *q, int x) {
if ((q->rear + 1) % MAXSIZE == q->front) { // 队列满
return false;
}
q->data[q->rear] = x;
q->rear = (q->rear + 1) % MAXSIZE;
return true;
}
入队操作那就是把我们想要存储的元素放进队列中,那么肯定要判断是否队满,可以看到我们的判断条件是尾指针指向下一节点,因为是int数据类型,所以对我们的最大值,也就是对我们的队长取模,我们可以知道,取模之后的结果就是确定了我们当前的位置,所以这次取模的结果是确定我们尾指针所指向的元素的下一个位置。所以整个判断条件就是,我们尾指针所指向的下一个位置如果也是我们队头指针所指向的位置,此刻队列就是满的。所以在这个循环队列当中,我们采用的是牺牲一个元素位置,来判断队是否满了。如果你要问我有没有其他方法,肯定是有的,比如用一个int值记录当前的元素个数,用来和maxsize判断,或者说用一个bool值用来记录出栈和入栈操作,等等,有兴趣可以私下自己实现一下。
(4)顺序循环队列的出队
// (4)顺序循环队列的出队
bool DeQueue(SeqCircularQueue *q, int *x) {
if (q->front == q->rear) { // 队列空
return false;
}
*x = q->data[q->front];
q->front = (q->front + 1) % MAXSIZE;
return true;
}
出队操作应该不用我多解释了,看得懂入队,那就看得懂出队。
(5)顺序循环队列取队头元素
// (5)顺序循环队列取队头元素
bool GetFront(SeqCircularQueue *q, int *x) {
if (q->front == q->rear) { // 队列空
return false;
}
*x = q->data[q->front];
return true;
}
2、链队列
(1)链队列的定义
// (1)链队列的节点定义
typedef struct QNode {
int data;
struct QNode *next;
} QNode, *QueuePtr;
// (1)链队列的定义
typedef struct {
QueuePtr front; // 队头指针
QueuePtr rear; // 队尾指针
} LinkQueue;
有没有发现,这里的定义分做了两块,一个专门定义节点,一个专门定义队列整体。
其实个人感觉定义在一块也没什么大不了的,但是把,人家既然分开,那么就有分开的道理,我也不多说了。多罗嗦两句,再解释一下代码:节点的结构体我就说了,说一下这个队列的结构体,里面的队头指针和队尾指针其实就是我们节点的结构体指针,每个指针指向我们的节点,所以只要对这两个节点指针进行操作就是对这链队列进行操作。
(2)链队列的初始化
// (2)链队列的初始化
void InitQueue(LinkQueue *q) {
q->front = q->rear = (QueuePtr)malloc(sizeof(QNode));
if (!q->front) {
exit(EXIT_FAILURE);
}
q->front->next = NULL;
}
(3)链队列的入队
// (3)链队列的入队
void EnQueue(LinkQueue *q, int x) {
QueuePtr newNode = (QueuePtr)malloc(sizeof(QNode));
if (!newNode) {
exit(EXIT_FAILURE);
}
newNode->data = x;
newNode->next = NULL;
q->rear->next = newNode;
q->rear = newNode;
}
链队的入队就是尾插法,保证了尾指针永远指向队尾
(4)链队列的出队
// (4)链队列的出队
int DeQueue(LinkQueue *q) {
if (q->front == q->rear) { // 队列空
return -1; // 表示出队失败
}
QueuePtr temp = q->front->next;
int x = temp->data;
q->front->next = temp->next;
if (q->rear == temp) { // 如果出队的是最后一个元素,需要更新队尾指针
q->rear = q->front;
}
free(temp);
return x;
}
出队操作就是把队头指针所指向的元素的后一个元素进行释放,然后队头指针指向头节点,并且取得出队元素的值。
(5)链队列取队头元素
// (5)链队列取队头元素
int GetFront(LinkQueue *q) {
if (q->front == q->rear) { // 队列空
return -1; // 表示取队头元素失败
}
return q->front->next->data;
}
3、完整代码,需要自取
(1)循环顺序队列
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAXSIZE 100 // 定义队列的最大长度
typedef struct {
int data[MAXSIZE]; // 存储队列元素的数组
int front; // 队头指针
int rear; // 队尾指针
} SeqCircularQueue;
// (2)顺序循环队列的初始化
void InitQueue(SeqCircularQueue *q) {
q->front = q->rear = 0;
}
// (3)顺序循环队列的入队
bool EnQueue(SeqCircularQueue *q, int 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(SeqCircularQueue *q, int *x) {
if (q->front == q->rear) { // 队列空
return false;
}
*x = q->data[q->front];
q->front = (q->front + 1) % MAXSIZE;
return true;
}
// (5)顺序循环队列取队头元素
bool GetFront(SeqCircularQueue *q, int *x) {
if (q->front == q->rear) { // 队列空
return false;
}
*x = q->data[q->front];
return true;
}
int main() {
SeqCircularQueue q;
InitQueue(&q);
int x;
EnQueue(&q, 1);
EnQueue(&q, 2);
EnQueue(&q, 3);
if (GetFront(&q, &x)) {
printf("队头元素:%d\n", x);
}
DeQueue(&q, &x);
if (GetFront(&q, &x)) {
printf("新的队头元素:%d\n", x);
}
return 0;
}
(2)链队
#include <stdio.h>
#include <stdlib.h>
// (1)链队列的节点定义
typedef struct QNode {
int data;
struct QNode *next;
} QNode, *QueuePtr;
// (1)链队列的定义
typedef struct {
QueuePtr front; // 队头指针
QueuePtr rear; // 队尾指针
} LinkQueue;
// (2)链队列的初始化
void InitQueue(LinkQueue *q) {
q->front = q->rear = (QueuePtr)malloc(sizeof(QNode));
if (!q->front) {
exit(EXIT_FAILURE);
}
q->front->next = NULL;
}
// (3)链队列的入队
void EnQueue(LinkQueue *q, int x) {
QueuePtr newNode = (QueuePtr)malloc(sizeof(QNode));
if (!newNode) {
exit(EXIT_FAILURE);
}
newNode->data = x;
newNode->next = NULL;
q->rear->next = newNode;
q->rear = newNode;
}
// (4)链队列的出队
int DeQueue(LinkQueue *q) {
if (q->front == q->rear) { // 队列空
return -1; // 表示出队失败
}
QueuePtr temp = q->front->next;
int x = temp->data;
q->front->next = temp->next;
if (q->rear == temp) { // 如果出队的是最后一个元素,需要更新队尾指针
q->rear = q->front;
}
free(temp);
return x;
}
// (5)链队列取队头元素
int GetFront(LinkQueue *q) {
if (q->front == q->rear) { // 队列空
return -1; // 表示取队头元素失败
}
return q->front->next->data;
}
int main() {
LinkQueue q;
InitQueue(&q);
EnQueue(&q, 1);
EnQueue(&q, 2);
EnQueue(&q, 3);
int x = GetFront(&q);
if (x != -1) {
printf("队头元素:%d\n", x);
}
x = DeQueue(&q);
if (x != -1) {
printf("出队的元素:%d\n", x);
}
x = GetFront(&q);
if (x != -1) {
printf("新的队头元素:%d\n", x);
}
// 释放队列内存(注意:这里只释放了队头节点,实际使用中需要遍历队列释放所有节点)
free(q.front);
return 0;
}
这篇文章比较水,因为只要前面的链表和栈都看懂了,看这篇文章几乎没有难点,也不涉及什么深奥的算法,无非都是自己对前面的知识点的回顾和操作,所以也就这样了。如果看不懂的,请好好学习前两个知识点。