队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
基于数组的循环队列——C语言描述
基于数组的队列,一般我们只会使用循环队列,而不使用非循环。这样做的目的主要是处于安全考虑,已经空间的开销。循环队列的使用使得安全性大幅提高,并降低了内存的开销以及提高了内存的利用效率。
#include <stdio.h>
#include <stdbool.h>
#define MAXSIZE 20
#define OK 1
#define ERR 0
//创建队列,基于数组。
typedef struct
{
int data[MAXSIZE] = { 0 };
int front;
int rear;
}Queue;
//初始化队列
int InitQueue(Queue* q)
{
q->front = q->rear = 0;
return OK;
}
//返回当前队列的长度。
int QueueLength(Queue* q)
{
return (q->rear - q->front + MAXSIZE) % MAXSIZE;
}
//入队
int EnQueue(Queue* q, int e)
{
//入队需要判断队列是否为满
if ((q->rear + 1) % MAXSIZE == q->front)
return ERR;
q->data[q->rear] = e;
q->rear = (q->rear + 1) % MAXSIZE;
return OK;
}
//出队
int DeQueue(Queue* q, int* e)
{
//出队需要判断队列是否为空
if (q->rear == q->front)
return ERR;
*e = q->data[q->front];
q->front = (q->front + 1) % MAXSIZE;
return OK;
}
//测试所写接口,包括边界条件的测试全部正常。
int main()
{
Queue sq = { 0 };
InitQueue(&sq);
int len = QueueLength(&sq);
printf("corrent lengths:%d\n", len);
for (int i = 0; i < 5; i++)
{
if (EnQueue(&sq, i))
printf("Succeseful!\n");
else
printf("Queue is full!\n");
}
len = QueueLength(&sq);
printf("corrent lengths:%d\n", len);
int e = 0;
for (int i = 0; i < 5; i++)
{
if (DeQueue(&sq, &e))
printf("e of val:%d\n", e);
else
printf("Queue is empty!");
}
len = QueueLength(&sq);
printf("corrent lengths:%d\n", len);
if (DeQueue(&sq, &e))
printf("e of val:%d\n", e);
else
printf("Queue is empty!");
for (int i = 0; i < 20; i++)
{
if (EnQueue(&sq, i))
printf("Succeseful!\n");
else
printf("Queue is full!\n");
}
if (EnQueue(&sq, 10))
printf("Succeseful!\n");
else
printf("Queue is full!\n");
return 0;
}
基于链表的队列——C语言描述
链表队列的创建
基于链表的队列。从名字上看,链表,链表,首先我们的定义一个链表,然后限制的操作使其符合队列的定义那么这个链表就具有了队列的熟悉。
因此我们先着眼于链表。
typedef struct
{
int data;
struct QNode* next;
}QNode;
队列需要2个指针指向这个链表,以限制链表。
typedef struct
{
struct QNode* rear, * front;
}LinkQueue;
链表队列的常见操作
初始化链表队列:
/********************************************************************************************
我认为链表队列的初始化函数不是必须,因为但我们创建一个链表队列时已经初始化
完成了(至少编译器会这样做)。
初始化API存在的意义更像是为了提高可读性,起提示作用。
如果要定义初始化函数,那么我个人觉得需要判断是不是非空队列,否则胡乱初始化
将会导致内存泄露。
以上仅是个人的一些看法,欢迎交流指错。
*********************************************************************************************/
int InitLinkQueue(LinkQueue* q)
{
//判断队列是否非空
if (q->front)
return ERR;
q->front = q->rear = NULL;
return OK;
}
入队
基于链表可认为长度无限制,硬要说也只是内存限制。
int EnLinkQueue(LinkQueue* q, int e)
{
//节点创建及初始化
QNode* p = (QNode*)malloc(sizeof(QNode));
if (!p)
return ERR;
memset(p, 0, sizeof(QNode));
p->data = e;
p->next = NULL;
//判断是否为空队列
if (!q->front && q->rear == q->front)
{
q->front = q->rear = p;
}
else
{
q->rear->next = p;
q->rear = p;
}
return OK;
}
出队
int DeLinkQueue(LinkQueue* q, int* ReturnVal)
{
//判断是否为空队列
if (q->rear == q->front && !q->front)
return ERR;
//需要判断是否只有一个节点,前面已经判断是否为空了,这里不用重复了
if (q->front == q->rear)
{
*ReturnVal = q->front->data;
free(q->front);
q->front = q->rear = NULL;
}
else
{
QNode* tmp = q->front;
*ReturnVal = q->front->data;
q->front = q->front->next;
free(tmp);
tmp = NULL;
}
return OK;
}
判断是否为空队列
int IsEmptyQueue(LinkQueue* q)
{
return (q->rear == q->front && !q->front) ? OK : ERR;
}
API测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define OK 1
#define ERR 0
void DeQueueTest(LinkQueue* q);
void EnQueueTest(LinkQueue* q, int i);
void InitLinkQueueTest(LinkQueue* q);
void IsEmptyQueueTest(LinkQueue* q);
int main()
{
LinkQueue q = { 0 };
InitLinkQueueTest(&q);
for (int i = 1; i < 6; i++)
{
EnQueueTest(&q, i);
}
printf("\n");
for (int i = 0; i < 5; i++)
{
DeQueueTest(&q);
}
putchar('\n');
IsEmptyQueueTest(&q);
DeQueueTest(&q);
return 0;
}
void IsEmptyQueueTest(LinkQueue* q)
{
if (IsEmptyQueue(q))
printf("It's an Empty Queue\n");
else
puts("It's not an empty Queue\n");
}
void InitLinkQueueTest(LinkQueue* q)
{
if (InitLinkQueue(q))
printf("Init Succeseful!\n\n");
else
printf("The queue was existing!\n\n");
}
void EnQueueTest(LinkQueue* q, int i)
{
if (EnLinkQueue(q, i))
{
printf("Succeseful!\t");
printf("val:%d\n", i);
}
else
printf("Failed!\n");
}
void DeQueueTest(LinkQueue* q)
{
int val = 0;
if (DeLinkQueue(q, &val))
{
printf("Succeseful!\t");
printf("val:%d\n", val);
}
else
printf("Failed! The queue is empty!\n");
}
写链表队列的时候意识到main存在多处重复代码,完全可以封装为一个函数,以此避免代码的重复性,提高复用性,我干脆全都封装算了,这样main逻辑也清楚。数组那就不改了,将就下。
其他一些操作其实是链表的内容就不写了,比如统计长度,就是链表的遍历。