一、基于数组实现的队列
#include<stdio.h>
#include<stdlib.h>
#define size 10 // 宏定义一个大小为10的队列
// 基于数组实现的队列称为顺序队列
// 定义一个队列的数组结构体
typedef struct queue
{
int data[size]; // 利用数组实现队列元素的定位
int head; // 队头下标
int tail; // 队尾下标
}Array_queue;
// 建立一个空队列
Array_queue * Create_queue()
{
// 申请空间
Array_queue* q = (Array_queue*)malloc(sizeof(Array_queue));
if (q == NULL)
{
return 0;
}
q->head = q->tail = 0; // 队头和队尾下标都初始化为0
return q;
}
// 入队
void enqueue(Array_queue* q, int x)
{
// q->tail = size表示队末没有空间了
if (q->tail == size)
{
// 若队列前面有空余空间,则将队内所有元素往前移
for (int i = q->head; i < q->tail; i++)
{
q->data[i - q->head] = q->data[i];
}
// 搬移完后更新队头和队尾指针
q->tail -= q->head;
q->head = 0;
}
q->data[q->tail] = x;
q->tail++;
}
// 出队(队内元素索引出队)
void dequeue(Array_queue* q, int x) // 传入队内元素并使其出队
{
while (x != q->data[q->head]) // 移动队头指针
{
q->head++;
}
q->head++;
}
// 打印队列
void Print_queue(Array_queue* q)
{
int i = q->head; // 从队头开始打印
while (i != q->tail)
{
printf("%d ", q->data[i]);
i++;
}
printf("\n");
}
int main()
{
int j = 10;
Array_queue* q = Create_queue();
printf("开始入队:\n");
while (j--)
{
// 在进行入队之前加个判断,看队列是否已满,防止数组溢出
if ((q->head == 0) && (q->tail == size))
{
printf("队列已满!\n");
break;
}
enqueue(q, 10 - j); // 入队
printf("%d入队后,队内元素为:", 10 - j);
Print_queue(q);
}
dequeue(q, 5); // 出队
printf("元素5出队后,队内元素为:");
Print_queue(q); // 打印队列
puts("队列末尾已经无空间,再次执行入队操作时,需将队内元素整体往前移");
puts("使队头指针重新初始化为0");
// 重新执行入队操作
enqueue(q, 110);
printf("元素110入队后,队内元素为:");
Print_queue(q);
printf("队尾还剩4个数据空间\n");
return 0;
}
运行结果:
基于数组实现的队列在进行入队操作时,需要警惕的一点就是数组溢出问题,因为基于数组实现的队列是通过下标的移动来进行的,当队列满了之后,队尾指针已经达到数组的最大值,这时就需要进行数据的迁移,若队头前面还有剩余空间,则将队列元素整体往前移,让队头下标重新置为0,否则重新申请一块更大的数组空间来存放队列。
二、基于链表实现的队列
#include<stdio.h>
#include<stdlib.h>
//基于链表实现的队列称为链式队列
// 定义队列结点
typedef struct Node
{
int data; // 元素
struct Node* next;
}Node;
// 定义队列的结构体
typedef struct LinkList_queue
{
Node* head; // 队头指针
Node* tail; // 队尾指针
}LinkList_queue;
LinkList_queue * Create_queue()
{
// 申请空间
LinkList_queue* q = (LinkList_queue*)malloc(sizeof(LinkList_queue));
if (q == NULL)
{
return;
}
q->head = q->tail = NULL; // 将队头队尾初始化为NULL
return q; // 返回q
}
// 入队
void enqueue(LinkList_queue* q, int x)
{
Node* p = (Node*)malloc(sizeof(Node)); // 申请一个结点空间
p->data = x; // 往结点内传值
p->next = NULL; // 结点的next设为NULL
if ((q->tail == NULL) && (q->head == NULL)) // 判断队头和队尾是否为空,用于第一次入队
{
q->head = p; // 队头和队尾都要指向p结点
q->tail = p;
}
else
{
q->tail->next = p; // 让p成为当前尾部的下一结点
q->tail = p; // 尾部指针指向p
// 队头和队尾通过p结点相连,在内存空间地址上是相同的
// 我们只需要移动队尾结点,使其指向队尾元素即可
}
}
// 出队 按元素来索引出队
void dequeue(LinkList_queue* q, int x)
{
if (q->head == NULL) // 判断队列是否为空
{
return;
}
else
{
// 移动队头指针
while (q->head->data != x)
{
q->head = q->head->next;
}
q->head = q->head->next; // 只移动队头指针,不移动队尾指针
// 如果q->head == NULL说明队列已经被清空,若此时想要再重新入队
// 则根据前面入队的代码需将q->tail重新置为空
if (q->head == NULL)
{
q->tail = NULL;
}
// 出队时,若队列未清空,则不涉及队尾指针的修改
}
}
void Print_queue(LinkList_queue* q)
{
Node* p = (Node*)malloc(sizeof(Node)); // 申请一个结点空间
p = q->head;
if (q->head == NULL) // 如果队列为空
{
printf("队列已经被清空!");
}
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
int main()
{
int i = 5;
LinkList_queue* q = Create_queue(); // 先创建一个空队列
printf("开始入队:\n");
while (i--)
{
enqueue(q, 5 - i); // 进队
printf("%d入队后,队内元素为:", 5 - i);
Print_queue(q); // 打印队列
}
enqueue(q, 6);
printf("6入队后,队内元素为:");
Print_queue(q);
dequeue(q, 4); // 出队
printf("元素4出队后,队内元素为:");
Print_queue(q); // 打印队列
dequeue(q, 6); // 出队
printf("元素6出队后,队内元素为:");
Print_queue(q); // 打印队列
// 重新执行入队操作
enqueue(q, 7);
printf("元素7入队后,队内元素为:");
Print_queue(q); // 打印队列
dequeue(q, 7); // 出队
printf("元素7出队后,队内元素为:");
Print_queue(q); // 打印队列
free(q); // 养成好习惯,清空q,防止内存溢出
return 0;
}
运行结果:
基于链表实现的队列没有空间限制,出队即删除元素,无法找回,只能重新入队。
三、循环队列
#include<stdio.h>
#include<stdlib.h>
#define size 5 // 宏定义一个大小为5的队列
// 定义一个队列的数组结构体
typedef struct queue
{
int data[size]; // 储存队列元素
int head; // 队头下标
int tail; // 队尾下标
int n; // 记录队列容量
}Loop_queue;
// 建立一个空队列
Loop_queue* Create_queue()
{
// 申请空间
Loop_queue* q = (Loop_queue*)malloc(sizeof(Loop_queue));
if (q == NULL)
{
return 0;
}
q->head = q->tail = q->n = 0; // 队头和队尾下标都初始化为0
return q;
}
// 入队
void enqueue(Loop_queue* q, int x)
{
if (((q->tail + 1) % size) == q->head) // 判断队列是否已满
{
// if(ture) 说明队列已满,使队列容量+1
// 用于跳出主函数的循环并打印队列已满的提示
q->n++;
}
else
{
q->data[q->tail] = x;
/*循环队列入队操作的核心代码*/
q->tail = (q->tail + 1) % size;
q->n++; // 每入一次队就使队列容量+1
}
}
// 出队(按元素索引出队)
void* dequeue(Loop_queue* q, int x)
{
while (x--)
{
/*循环队列出队操作的核心代码*/
q->head = (q->head + 1) % size;
// 每出一次队,就让队列的容量-1
q->n--;
}
}
// 打印队列
void Print_queue(Loop_queue* q)
{
int i = q->head; // 从队头开始打印
int j = q->n;
if (q->head == q->tail)
{
printf("队列为空,无法执行出队操作!");
}
else if (q->head == size - 1)
{
printf("队列已经被清空!");
q->head = q->tail = q->n = 0; // 队头和队尾下标以及队列容量都初始化为0
}
else
{
while (j--)
{
if (i < size)
{
printf("%d ", q->data[i]);
i++;
}
else
{
printf("%d ", q->data[i - size]);
i++;
}
}
}
printf("\n");
}
int main()
{
int j = 5;
Loop_queue* q = Create_queue();
puts("***循环队列***");
puts("开始入队:");
while (j--)
{
enqueue(q, 5 - j); // 入队
// 入队后判断队列是否已满
if ((q->n % size) == q->head) // 易错点:q->tail从入队函数出来后已经是4了
{
printf("队列已满!请等待队首元素出队后再执行入队操作\n");
q->n -= 1; // 使队列容量恢复回正常值
break;
}
printf("%d入队后,队内元素为:", 5 - j);
Print_queue(q);
}
dequeue(q, 3);
printf("元素3出队后,队内元素为:");
Print_queue(q); // 打印队列
enqueue(q, 5);
printf("元素5入队后,队内元素为:");
Print_queue(q);
enqueue(q, 6);
printf("元素6入队后,队内元素为:");
Print_queue(q);
dequeue(q, 6);
printf("元素6出队后,队内元素为:");
Print_queue(q); // 打印队列
free(q);
return 0;
}
运行结果:
1.循环队列操作要点
- 循环队列需要额外定义一个变量来记录队列的大小
- 队头指针(head)和队尾指针(tail)以及队列大小都初始化为0,队列每增加一个元素就+1,每减少一个元素就-1
- 入队操作核心代码:tail = (tail + 1) % size,size是队列的大小
- 出队操作核心代码:head = (head + 1) % size
- 队满条件:head == tail
- 队空条件:(tail + 1) % n = head
- 队满与队空的条件一定要分析好,否则程序可能会运行出错!!!
2.运行结果的判断
- 看队首和队尾元素是否都能出队?
- 队首元素出队后是否能继续入队?
- 是否能持续进行出队和入队操作?
- 队满和队空的时候是否能给出提示?
3.循环队列特点:
(1)与<顺序队列>相比,循环队列在进行数据的搬移时更加地自然,不需要重新申请一个额外的存储空间;
(2)由于入队操作的的原因,当队满时,队尾指针指向的位置实际上并没有数据,所以循环队列会浪费一个存储空间;