队列
1. 使用方法
- 每次只能加入一个数字,每次只能拿出一个数字
- 队列里的数字有前后顺序,先进的数字在前,后进的数字在后
- 每次从队列里获得的数字一定是最先放进去的数字(先进先出)
2. 实现方法
typedef struct {
int buf[size]; //存放数字的数组
int head; //第一个数字所在的下标(如果队列空则 head 应该等于 tail )
int tail; //下一个数字应该在的下标
}queue;
- 除了这个结构体,还需要为这个队列编写一组相关的关联函数
- 队列的初始化函数 用来把一个没有使用过的队列设置成可使用的状态
- void queue_init(queue *p_queue) {…}
- 队列的清理函数 用来把一个使用过的队列里的所有数字删除
- void queue_deinit(queue*p_queue){…}
- 获得队列里的数字个数的函数
- int queue_size(const queue *p_queue){…}
- 判断队列是否是空的函数
- int queue_empty(const queue *p_queue){…}
- 判断队列是否满的函数
- int queue_full(const queue *p_queue){…}
- 向队列里加数字的函数(用整数类型返回值表示是否把数字加进了队列里)
- int queue_push(queue *p_queue, int val){…}
- 从队列里获得数字的函数(同时删除数字)
- int queue_pop(queue *p_queue, int *p_val){…}
- 从队列里获得数字的函数(不会删除数字)
- int queue_top(const queue *p_queue, int *p_val){…}
- 队列的初始化函数 用来把一个没有使用过的队列设置成可使用的状态
/
//queue_1.h
#ifndef __QUEUE_1_H__
#define __QUEUE_1_H__
typedef struct {
int buf[SIZE]; //记录队列里的数字
int head; //记录第一个数字所在存储区的下标
int tail; //记录下一个数字应该放置的下标
}queue;
//队列初始化函数
void queue_init(queue * p_queue);
//队列的清理函数
void queue_deinit(queue *p_queue);
//获得队列里数字个数的函数
int queue_size(const queue *p_queue);
//判断队列是否为空的函数
int queue_empty(const queue *p_queue);
//判断队列是否满的函数
int queue_full(const queue *p_queue);
//向队列里加入数字的函数
int queue_push(queue *p_queue, int val);
//从队列获得数字的函数(同时删除数字)
int queue_pop(queue *p_queue, int *p_val);
//从队列获得数字的函数(同时删除数字)
int queue_front(const queue *p_queue, int *p_val);
#endif //QUEUE_1_H__
/
//queue_1.c
#include "queue_1.h"
//队列初始化函数
void queue_init(queue * p_queue) {
p_queue->tail = 0; //在队列没有数字的时候,下一个数字应该放在下标为0的存储区里
p_queue->head = 0; //head 等于 tail表示队列里没有数字
}
//队列的清理函数
void queue_deinit(queue *p_queue) {
p_queue->tail = 0; //在队列没有数字的时候,下一个数字应该放在下标为0的存储区里
p_queue->head = 0; //head 等于 tail表示队列里没有数字
}
//获得队列里数字个数的函数
int queue_size(const queue *p_queue) {
//当tail和head相等时返回0
//每当队里里多一个数,tail 会加1,相减结果就会加1
//每当队列里少一个数,head 会加1,相减结果就会减1
return p_queue->tail - p_queue->head;
}
//判断队列是否为空的函数
int queue_empty(const queue *p_queue) {
return p_queue->head == p_queue->tail;
}
//判断队列是否满的函数
int queue_full(const queue *p_queue) {
//队列也有两种实现方法,一种是从前向后使用这些存储区
//换句话说,只要队列最后一个存储区被填上了数字,那么就可以认为这个队列已经满了
//虽然队列先进先出的定义下,当最后一个存储区被填上数字之后,前面下标为为0的存储区
//的数可能已经被拿走了,这个时候下标为0的存储区是可以继续用来填数字的
//但现在实现的这个版本,可以认为,当最后一个存储区被填上数字就可以认为这个存储区已经满了
return p_queue->tail >= SIZE;
}
//向队列里加入数字的函数
int queue_push(queue *p_queue, int val) {
if (p_queue->tail >= SIZE) {
//队列已经满了
return 0;
}
p_queue->buf[p_queue->tail] = val; //把新数组记录到数组里以 tail 做下标的存储区里
p_queue->tail++; //把tail向后移动一步
return 1;
}
//从队列获得数字的函数(同时删除数字)
int queue_pop(queue *p_queue, int *p_val) {
if (p_queue->tail == p_queue->head) {
//如果队列是空的
return 0;
}
*p_val = p_queue->buf[p_queue->head]; //把数组里以head做下标的的存储区的数字传递给调用函数
p_queue->head++; //把head向后移动一步,表示删除这个数字
return 1;
}
//从队列获得数字的函数(不删除数字)
int queue_front(const queue *p_queue, int *p_val) {
if (p_queue->tail == p_queue->head) {
return 0;
}
*p_val = p_queue->buf[p_queue->head];
return 1;
}
/
//queue_main.c
#include <stdio.h>
//如果一个源文件里使用了某个头文件里声明的函数,那这个头文件也是这个源文件的必要头文件
#include "queue_1.h"
int main() {
//声明一个结构体变量代表一个队列,然后对队列做初始化,最后做清理
//使用数据结构的基本代码框架
queue que = {0};
int val = 0;
queue_init(&que);
printf("数字个数是%d\n", queue_size(&que));
printf("队列是否空%d\n", queue_empty(&que));
printf("队列是否满%d\n", queue_full(&que));
queue_push(&que, 10);
queue_push(&que, 20);
queue_push(&que, 30);
printf("数字个数是%d\n", queue_size(&que));
printf("队列是否空%d\n", queue_empty(&que));
printf("队列是否满%d\n", queue_full(&que));
queue_front(&que, &val);
printf("最前面的数字是%d\n", val);
queue_pop(&que, &val);
printf("最前面的数字是%d\n", val);
queue_pop(&que, &val);
printf("最前面的数字是%d\n", val);
//这个时候第一个和第二个存储区数字已经被删了
//但是当 SIZE 下标存储区被填满()过(tail就等于了SIZE),(这个版本)依然认为队列满了
//所以最后即使队列里的数字被删完了,依然显示队列是满的
queue_push(&que, 40);
queue_push(&que, 50);
printf("数字个数是%d\n", queue_size(&que));
printf("队列是否空%d\n", queue_empty(&que));
printf("队列是否满%d\n", queue_full(&que));
while(1) {
if(!queue_pop(&que, &val))
{
break;
}
printf("%d\n", val);
}
printf("数字个数是%d\n", queue_size(&que));
printf("队列是否空%d\n", queue_empty(&que));
printf("队列是否满%d\n", queue_full(&que));
queue_deinit(&que);
return 0;
}
/*
gcc -DSIZE=5 queue_1.c queue_main.c
output:
数字个数是0
队列是否空1
队列是否满0
数字个数是3
队列是否空0
队列是否满0
最前面的数字是10
最前面的数字是10
最前面的数字是20
数字个数是3
队列是否空0
队列是否满1
30
40
50
数字个数是0
队列是否空1
队列是否满1
*/
- 回想一下刚刚讲过的队列,是从前向后使用存储区,当最后一个存储区使用完以后,就不能在往里加入数字了。如果能重复利用已经被拿走数字的存储区,40 和 50 其实是可以放进去的。所以就有了循环队列。
循环队列
当数组里最后一个存储区也被放上数字以后可以把后边的数字放在 下标为0的存储区里(这个存储区里必须没有有效数字)
1. 实现方法
typedef struct{
int buf[SIZE]; //记录数字的数组
int qty; //队列里的数字个数
int tail; //下一个数字所在的下标
} queue;
loop_queue_1.h
// 前后加上预处理指令
#ifndef __LOOP_QUEUE_1_H__
#define __LOOP_QUEUE_1_H__
// qty 可能大于也可能小于 tail
// 当tail 因为大于等于 size 被置为0时
typedef struct{
int buf[SIZE]; //记录数字的数组
int qty; //队列里的实际数字个数
int tail; //下一个数字所在的下标
} queue;
// 删除函数内容和形参名称
// get_head 函数只是在队列里面使用
// 我们不希望队列以外的函数来使用get_head 函数
// 所以 get_head 函数的生命不应该出现在头文件里
// 获得最前面数字所在下标的函数
// int get_head(const queue *);
// 队列的初始化函数
void queue_init(queue *);
// 队列清理函数
void queue_deinit(queue *);
//获得队列里数字个数的函数
int queue_size(const queue *);
// 判断队列是否空的函数
int queue_empty(const queue *);
// 判断队列是否满的函数
int queue_full(const queue *);
// 向队列里加入数字的函数
int queue_push(queue *, int);
// 从队列里获得数字的函数(同时删除数字)
int queue_pop(queue *, int *);
// 从队列里获得数字(不会删除数字)
int queue_front(const queue *, int *);
#endif __LOOP_QUEUE_1_H__
loop_queue_1.c
#include "loop_queue_1.h"
// 获得最前面数字所在下标的函数
int get_head(const queue *p_queue) {
int ret = p_queue->tail - p_queue->qty;
// 当tail被置0以后(由于queue_pop函数,tail比qty先到SIZE), 就会出现ret<0的情况
// 并且这种情况出现以后,ret<0 将一直保持下去,因为每次queue_push 都会给 tail和qty都加一
if (ret < 0) {
ret += SIZE;
}
return ret;
}
// 队列的初始化函数
void queue_init(queue *p_queue) {
p_queue->tail = 0;
p_queue->qty = 0; //代表队列里没有数字
}
// 队列清理函数
void queue_deinit(queue *p_queue) {
p_queue->tail = 0;
p_queue->qty = 0;
}
// 获得队列里数字个数的函数
int queue_size(const queue *p_queue){
// qty 成员变量当做返回值传递给调用函数
return p_queue->qty;
}
// 判断队列是否空的函数
int queue_empty(const queue *p_queue) {
return !p_queue->qty;
}
// 判断队列是否满的函数
int queue_full(const queue *p_queue) {
return p_queue->qty >= SIZE;
}
// 向队列里加入数字的函数
int queue_push(queue *p_queue, int val) {
if (p_queue->qty >= SIZE){
//队列满了
return 0;
}
p_queue->buf[p_queue->tail] = val;
p_queue->tail++;
p_queue->qty++;
// tail 一直加可能会超过存储区范围(当qty因为queue_pop没有超过SIZE,但tail超过SIZE后)
// 当tail = size ,把tail 置为0,就可以绕着圈的填数字了
if (p_queue->tail >= SIZE) {
p_queue->tail = 0;
}
// 这两每次一起加,对get_head的结果不会有任何改变
return 1;
}
// 从队列里获得数字的函数(同时删除数字)
int queue_pop(queue *p_queue, int *p_val) {
if (!p_queue->qty) {
// 队列是空的
return 0;
}
// 使用get_head 函数得到第一个数字所在的下标,把这个存储区里的数字传递给调用函数
*p_val = p_queue->buf[get_head(p_queue)];
// 把qty 减一 就相当于删除了这个数字
p_queue->qty--;
// 然后 get_head 的结果就会加一
return 1;
}
// 从队列里获得数字(不会删除数字)
int queue_front(const queue *p_queue, int *p_val) {
if (!p_queue->qty) {
return 0;
}
*p_val = p_queue->buf[get_head(p_queue)];
}
loop_queue_main.c
#include <stdio.h>
//如果一个源文件里使用了某个头文件里声明的函数,那这个头文件也是这个源文件的必要头文件
#include "loop_queue_1.h"
int main() {
//声明一个结构体变量代表一个队列,然后对队列做初始化,最后做清理
//使用数据结构的基本代码框架
queue que = {0};
int val = 0;
queue_init(&que);
printf("数字个数是%d\n", queue_size(&que));
printf("队列是否空%d\n", queue_empty(&que));
printf("队列是否满%d\n", queue_full(&que));
queue_push(&que, 10);
queue_push(&que, 20);
queue_push(&que, 30);
printf("数字个数是%d\n", queue_size(&que));
printf("队列是否空%d\n", queue_empty(&que));
printf("队列是否满%d\n", queue_full(&que));
queue_front(&que, &val);
printf("最前面的数字是%d\n", val);
queue_pop(&que, &val);
printf("最前面的数字是%d\n", val);
queue_pop(&que, &val);
printf("最前面的数字是%d\n", val);
queue_push(&que, 40);
queue_push(&que, 50);
printf("数字个数是%d\n", queue_size(&que));
printf("队列是否空%d\n", queue_empty(&que));
// 上一个版本这里的判满结果是真(size = 5),因为已经把最后存储区用了
// 这个结果判满的结果是假,因为前面那个 10和20 的存储区已经空出来了
printf("队列是否满%d\n", queue_full(&que));
while(1) {
if(!queue_pop(&que, &val))
{
break;
}
printf("%d\n", val);
}
printf("数字个数是%d\n", queue_size(&que));
printf("队列是否空%d\n", queue_empty(&que));
printf("队列是否满%d\n", queue_full(&que));
queue_deinit(&que);
return 0;
}
/*
gcc -DSIZE=5 loop_queue_1.c loop_queue_main.c
output:
数字个数是0
队列是否空1
队列是否满0
数字个数是3
队列是否空0
队列是否满0
最前面的数字是10
最前面的数字是10
最前面的数字是20
数字个数是3
队列是否空0
队列是否满0 ****这里和之前有不同,上个版本这里是1****
30
40
50
数字个数是0
队列是否空1
队列是否满0
*/