数据结构与算法——队列原理及C语言底层实现
- 队列概念
- 顺序队列
- 1. 顺序队列原理
- 2. 队列的创建
- 3. 入队与出队
- 4. 判断满队与空队
- 5. 清空队列与释放空间
- 6. 主流程测试
- 链式队列
- 1. 链式队列的创建
- 2. 链式队列入队
- 3. 链式队列出队
- 4. 判断是否为空队
- 5. 清空队列与释放空间
- 6. 主流程测试
- 栈与队列综合应用——球钟问题
参考博文:【数据结构与算法】程序内功篇五–队列
队列概念
- 队列是限制在两端进行插入操作和删除操作的线性表
- 允许进行插入操作的一端称为“队尾”
- 允许进行删除操作的一端称为“队头”
- 当线性表中没有元素时,称为“空队”
- 特点 :先进先出(FIFO)
顺序队列
1. 顺序队列原理
- 规定: front指向对头元素的位置,rear指向队尾元素的下一个位置
- 在队列操作过程中,为了提高效率,以调整指针代替队列元素的移动,并将数组作为循环队列的操作空间。
- 为区别空队和满队,满队元素个数比数组元素个数少一个。
因此顺序队列的结构体定义为:
typedef int data_t; //定义栈中数据元素类型
#define N 64 //定义队列容量
//队列的定义
typedef struct{
data_t data[N]; //用数组作为队列的存储空间
int front,rear; //指示队头和队尾位置的指针
}sequeue_t; //顺序队列类型定义
2. 队列的创建
①申请队列内存空间
②初始化数据
③设置队列头等于队列尾
//创建队列
sequeue* queue_create()
{
sequeue *sq;
//申请内存空间
sq = (sequeue *)malloc(sizeof(sequeue));
if(sq == NULL){
printf("malloc sequeue failed\n");
return NULL;
}
//初始化数据
memset(sq->data, 0, sizeof(sq->data));
//定义队列头尾为0
sq->front = 0;
sq->rear = 0;
return sq;
}
3. 入队与出队
3.1 入队
①参数检查与判断是否为满队
②队尾插入元素
③队尾指针偏移
//入队列 -1: 入队失败 0 : 入队成功
int enqueue(sequeue *sq, data_t value)
{
//入口参数检查
if(sq == NULL){
printf("sequeue is NULL\n");
return -1;
}
//判断队列是否为满队列
if((sq->rear+1) % N == sq->front){
printf("sequeue is full\n");
return -1;
}
//队尾存入元素
sq->data[sq->rear] = value;
sq->rear = (sq->rear + 1) % N;
return 0;
}
3.2 出队
①参数检查与判断是否为空队
②获取队头元素
③队头指针偏移
//出队
data_t dequeue(sequeue *sq)
{
//入口参数检查
if(sq == NULL){
printf("sequeue is NULL\n");
return -1;
}
//空队
if(sq->front == sq->rear){
return -1;
}
//获取队头数据
data_t ret;
ret = sq->data[sq->front]; //队头数据
sq->front = (sq->front + 1) % N; //队头指针偏移
return ret;
}
4. 判断满队与空队
4.1 判断是否为空队 ,空队:队头指针 = 队尾指针
//判断队列是否为空队列
// 1: 空队 0: 非空队 -1:参数异常
int queue_empty(sequeue *sq)
{
//入口参数检查
if(sq == NULL){
printf("sequeue is NULL\n");
return -1;
}
if(sq->front == sq->rear)
return 1;
else
return 0;
}
4.2 判断是否为满队,满队:队头指针 = 队尾指针+1
//判断队列是否为满队列
// 1: 满队 0: 非满队 -1:参数异常
int queue_full(sequeue *sq)
{
//入口参数检查
if(sq == NULL){
printf("sequeue is NULL\n");
return -1;
}
//满队
if((sq->rear + 1) % N == sq->front)
return 1;
else
return 0;
}
5. 清空队列与释放空间
5.1 清空队列
//清空队列
int queue_clear(sequeue *sq)
{
//入口参数检查
if(sq == NULL){
printf("sequeue is NULL\n");
return -1;
}
sq->front = sq->rear = 0;
return 0;
}
5.2 释放队列空间
//释放队列空间
sequeue* queue_free(sequeue *sq)
{
//入口参数检查
if(sq == NULL){
printf("sequeue is NULL\n");
return NULL;
}
free(sq);
sq = NULL;
}
6. 主流程测试
int main()
{
sequeue *sq;
sq = queue_create();
if(sq == NULL)
return -1;
//入队
enqueue(sq,1);
enqueue(sq,2);
enqueue(sq,3);
enqueue(sq,4);
while(!queue_empty(sq)) //队列不为空
{
printf("dequeue: %d\n",dequeue(sq)); //出队
}
queue_free(sq); //释放队列
return 0;
}
链式队列
链式队列相当于就是在队尾插入,队头删除的链式结构,由队头指针和队尾指针控制队列的操作。
1. 链式队列的创建
①申请队列内存空间
②申请头尾节点空间
③节点数据赋值
//创建队列
linkqueue* queue_create()
{
linkqueue *lq;
//申请内存空间
lq = (linkqueue *)malloc(sizeof(linkqueue));
if(lq == NULL){
printf("malloc linkqueue failed\n");
return NULL;
}
//队列头尾指针申请内存空间
linkqueue->front = linkqueue->rear = (listNode*)malloc(sizeof(listNode));
if(linkqueue->front == NULL){
printf("malloc listNode failed\n");
return NULL;
}
//节点数据与指针赋值
lq->front->data = 0;
lq->front->next = NULL;
return lq;
}
2. 链式队列入队
①封装孤立节点p
②队尾插入节点p
③更新队尾指针rear
//入队列 -1: 入队失败 0 : 入队成功
int enqueue(linkqueue *lq, data_t value)
{
//入口参数检查
if(lq == NULL){
printf("linkqueue is NULL\n");
return -1;
}
//1.封装孤立节点p
listNode* p = (listNode*)malloc(sizeof(listNode));
if(p == NULL){
printf("malloc listNode failed\n");
return -1;
}
p->data = value;
p->next = NULL;
//2.队尾插入节点p
lq->rear->next = p;
//3.更新队尾指针rear
lq->rear = p;
return 0;
}
3. 链式队列出队
①判断入口参数及是否为空队
②暂存待删除节点
③队头指针偏移
④释放空间
//出队
data_t dequeue(linkqueue *lq)
{
//入口参数检查
if(lq == NULL){
printf("linkqueue is NULL\n");
return -1;
}
//空队
if(lq->front == lq->rear){
return -1;
}
//暂存待删除节点并释放节点空间
listNode* temp = lq->front; //暂存队头节点
lq->front = temp->next; //队头指针偏移
free(temp); //释放队头空间
temp = NULL;
return lq->front->data;
}
4. 判断是否为空队
空队:队头指针 = 队尾指针
//判断队列是否为空队列
// 1: 空队 0: 非空队 -1:参数异常
int queue_empty(linkqueue *lq)
{
//入口参数检查
if(lq == NULL){
printf("linkqueue is NULL\n");
return -1;
}
if(lq->front == lq->rear)
return 1;
else
return 0;
}
5. 清空队列与释放空间
清空队列数据:
//清空队列
// -1: 函数失败,0: 函数成功
int queue_clear(linkqueue *lq)
{
//入口参数检查
if(lq == NULL){
printf("linkqueue is NULL\n");
return -1;
}
//判断队列是否为空队列
if(lq->front == lq->rear){
printf("linkqueue is empty\n");
return -1;
}
//遍历链表,将数据初始化为0
while(lq->front != NULL)
{
lq->front->data = 0;
lq->front = lq->front->next;
}
return 0;
}
释放队列空间
//释放队列空间
int queue_free(linkqueue *lq)
{
//入口参数检查
if(lq == NULL){
printf("linkqueue is NULL\n");
return -1;
}
listNode * p;
//循环释放节点
while(lq->front)
{
p = lq->front; //暂存节点
lq->front = p->next; //偏移队列头下标
printf("free: %d\n",p->data);
free(p); //释放暂存节点
}
p = NULL;
}
6. 主流程测试
int main()
{
linkqueue *lq;
lq = queue_create();
if(lq == NULL)
return -1;
//入队
enqueue(lq,1);
enqueue(lq,2);
enqueue(lq,3);
enqueue(lq,4);
while(!queue_empty(lq)) //队列不为空
{
printf("dequeue: %d\n",dequeue(lq)); //出队
}
queue_free(lq); //释放队列
return 0;
}
栈与队列综合应用——球钟问题
1、球钟简介
球钟是一个利用球的移动来记录时间的简单装置,它有三个可以容纳若干个球的指示器:分钟指示器,五分钟指示器,小时指示器。若分钟指示器中有2个球,五分钟指示器中有6个球,小时指示器中有5个球,则时间为5:32
2、球钟工作原理
- 每过一分钟,球钟就会从球队列的队首取出一个球放入分钟指示器,分钟指示器最多可容纳4个球。
- 当放入第五个球时,在分钟指示器的4个球就会按照他们被放入时的相反顺序加入球队列的队尾。而第五个球就会进入五分钟指示器。
- 按此类推,五分钟指示器最多可放11个球,小时指示器最多可放11个球。
3、问题阐述
当小时指示器放入第12个球时,原来的11个球按照他们被放入时的相反顺序加入球队列的队尾,然后第12个球也回到队尾。这时,三个指示器均为空,回到初始状态,从而形成一个循环。因此,该球钟表示时间的范围是从0:00到11:59。
问题:
现设初始时球队列的球数为27,球钟的三个指示器初态均为空。要经过多久,球队列才能恢复到原来的顺序?
4. 程序设计
#include <stdio.h>
#include "linkqueue.h"
#include "sqstack.h"
//检查队列是否升序
int check(linkqueue *lq);
int main()
{
linkqueue* lq; //队列名称
int i; //时钟球编号
sqStack * stack_hour; //定义三个栈容器
sqStack * stack_five;
sqStack * stack_min;
int min = 0; //分钟计时器
int value; //保存出队的值
//创建链式队列
lq = queue_create();
if(lq == NULL){
printf("lq create failed\n");
return -1;
}
//27个球顺序入队
for(i = 1; i <= 27; i++)
enqueue(lq, i);
//创建栈
stack_hour = stack_create(11);
stack_five = stack_create(11);
stack_min = stack_create(4);
if(stack_hour == NULL || stack_five == NULL || stack_min == NULL){
printf("create stack failed\n");
return -1;
}
while(1)
{
min++; //每过1分钟
if(!queue_empty(lq)) //队不空
{
value = dequeue(lq);//出队
//分钟计时器不满,放分钟计时器
if(!stack_full(stack_min))
{
stack_push(stack_min,value); //入分钟栈
}
else //分钟计时器满
{
//栈不空 出栈入队列
while(!stack_empty(stack_min))
{
enqueue(lq, stack_pop(stack_min));
}
//5分钟计时器未满
if(!stack_full(stack_five))
{
stack_push(stack_five, value);
}
else //5分钟计时器满了 例如0:59
{
while(!stack_empty(stack_five)) //清空5分钟计时器
{
enqueue(lq, stack_pop(stack_five)); //出栈入队列
}
//小时计时器未满 小时计时器存入数据
if(!stack_full(stack_hour))
{
stack_push(stack_hour, value);
}
else //小时计时器也满了 11:59
{
while(!stack_empty(stack_hour)) //清空小时计时器
{
enqueue(lq, stack_pop(stack_hour)); //出栈入队列
}
enqueue(lq,value); //栈全满,球放回队列
//0:00
if(check(lq) == 1) //检查队列是否升序
{
break;
}
}
}
}
}
}
printf("total: %d\n",min);
//队列不空 出队
printf("dequeue: ");
while(!queue_empty(lq))
{
printf("%d ",dequeue(lq));
}
printf("\n");
return 0;
}
//检查队列是否升序
//1: 升序 0:无序
int check(linkqueue *lq)
{
if(lq == NULL)
{
printf("lq is NULL\n");
return 0;
}
listNode* p = lq->front->next; //遍历队列
while(p != NULL && p->next != NULL)
{
if(p->data < p->next->data)
p = p->next;
else
return 0;
}
return 1;
}
运行结果:
total: 33120
dequeue: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27