简书内代码已上传GitHub:点击我 去GitHub查看代码
队列的相关应用已更新:杨辉三角
如有错误,还请大佬们一定留言指出!!
一.什么是队列?
和栈的特性相反,队列是一种 先进先出(FIFO)的线性表。队列只允许在表的一端进行插入,在表的另一端进行删除操作。
在队列中,允许插入操作的一端叫做 队尾(rear),允许删除操作的一端叫做 队头(front 。
如果有一个队列: q = (a1 , a2 , a3 , a4) , 那么a1 是队头元素 , a4是队尾元素。a1也是第一个进入队列的元素,a4是刚刚插入的元素。所以插入的顺序是:a1,a2,a3,a4 , 删除的顺序是:a4,a3,a2,a1.
队列--图片来源网络
二.怎样实现顺序存储结构的队列?
一开始接触队列,心里就在想:这还不简单?不是和之前顺序栈一样?只是删除操作换了个方向而已...然而,当我开始写了之后...是这样的:
大吃一惊
??如果直接分配一定的存储空间给队列,然后出队入队...整个队列在一直向前移动...就等于说前面分配的空间就浪费了,且很容易造成内存溢出。
后来脑子又灵光一闪,那我每次出队的时候都把整个队列后移不就好了。。。兴致慢慢的去写了,刚准备开始,又想到:每次删除元素时间复杂度都是o(n)...肯定有更好的解决方法,嗯,就是循环队列。
当队尾移动到队列空间末尾的时候,下一次移动我们移动到队头,相当于把队尾和队头接了起来,这样因为移动而无法利用的空间就充分利用了。
三.队列的结构定义:
需要一个基址保存队列空间地址
需要两个整数front、rear表示索引,访问队头队尾
定义如下:
typedef struct{
// 初始化空间基址
QElemtype *base;
// 队头指针
int front;
// 队尾指针
int rear;
}SqQueue;
接下来就是重点了,也是循环队列必须要会的一些基本操作
滑稽
四.队列的基本操作:
初始化队列:
分配空间 、 给队头队尾索引赋值
// 初始化队列
Status InitQueue(SqQueue& Q){
Q.base = (QElemtype*)malloc(sizeof(QElemtype) * MAXQSIZE);
if(!Q.base){
printf("error: InitSqQueue fail\n");
return OVERFLOW;
}
// 初始队列队头队尾指向base
Q.front = Q.rear = 0;
return OK;
}
销毁队列:
释放空间 、 给base赋NULL
最好避免二次释放空间
// 销毁队列
Status DestroyQueue(SqQueue& Q){
free(Q.base);
Q.base = NULL;
return OK;
}
清空队列:
队头队尾索引赋0即可
// 清空队列
Status ClearQueue(SqQueue& Q){
Q.front = Q.rear = 0;
return OK;
}
判断队列是否为空:
队头索引 == 队尾索引 即为空
// 判断是否为空
Status QueueEmpty(SqQueue& Q){
return Q.front == Q.rear ? True : False;
}
获得队列长度:
因为是循环队列,所以队尾地址不一定会大于队尾地址
实际长度应该为 队列空间大小 +(rear - front)
// 获取队列长度
int QueueLength(SqQueue Q){
// 循环队列Q.rear - Q.front可能为负
return ((Q.rear - Q.front + MAXQSIZE) % MAXQSIZE);
}
6.获得队头元素:
// 获得队头元素
Status GetHead(SqQueue Q, QElemtype& e){
// 非空则返回队头元素
if(Q.rear != Q.front){
e = Q.base[Q.front];
return OK;
}
printf("error: SqQueue is Empty\n");
return ERROR;
}
队尾插入元素:
插入前判断队列是否已满
插入后更新队尾索引(记住这是个循环队列)
// 队尾插入元素
Status EnQueue(SqQueue& Q, QElemtype e){
//对列满
if((Q.rear + 1) % MAXQSIZE == Q.front)
return ERROR;
// 插入元素
Q.base[Q.rear] = e;
//更新队尾
Q.rear = (Q.rear + 1) % MAXQSIZE;
return OK;
}
删除队头元素:
重点和7点类似
// 删除队头元素
Status DeQueue(SqQueue& Q, QElemtype& e){
//空对列
if(Q.front == Q.rear)
return ERROR;
// 用e返回队头
e = Q.base[Q.front];
// 更新队头
Q.front = (Q.front + 1) % MAXQSIZE;
return OK;
}
9.遍历队列:
// 从队头到队尾visit遍历
Status QueueTraverse(SqQueue Q, void (*visit)(QElemtype)){
//空对列
if(Q.front == Q.rear)
return ERROR;
for(int i = Q.front ;i != Q.rear ; i = (i + 1) % MAXQSIZE){
visit(Q.base[i]);
}
return OK;
}
都是套路
五.循环队列的应用
再这里不得不又写到约瑟夫问题了:线性表解法
因为循环队列的特性(先进先出),这时候我们只要在计数的同时,删除队头元素,添加至队尾,就可以实现了。
#include "SqQueue.h"
int main(){
int n, m;
scanf("%d %d", &n, &m);
SqQueue q;
InitQueue(q);
//赋值(编号)
for(int i = 1 ; i <= n ; ++i){
EnQueue(q, i);
}
int count = 0, e = 0;
while(QueueLength(q) > 1){
//如果数到m,删除队头元素
if(++count % m == 0){
DeQueue(q, e);
//否则将队头元素删除,添加至队尾
}else{
DeQueue(q, e);
EnQueue(q, e);
}
}
GetHead(q, e);
DestroyQueue(q);
printf("num: %d\n", e);
return 0;
}
另外,有兴趣的话可以看看约瑟夫问题的数学解法。比较n大了这样写还是没有数学解法来的高效:约瑟夫环数学解法
六.完整代码:
#include
#include
//最大队列长度
#define MAXQSIZE 100
//定义状态
#define OVERFLOW -1
#define OK 1
#define ERROR 0
#define True 1
#define False 0
typedef int QElemtype;
typedef int Status;
typedef struct{
// 初始化空间基址
QElemtype *base;
// 队头指针
int front;
// 队尾指针
int rear;
}SqQueue;
// 初始化队列
Status InitQueue(SqQueue& Q){
Q.base = (QElemtype*)malloc(sizeof(QElemtype) * MAXQSIZE);
if(!Q.base){
printf("error: InitSqQueue fail\n");
return OVERFLOW;
}
// 初始队列队头队尾指向base
Q.front = Q.rear = 0;
return OK;
}
// 销毁队列
Status DestroyQueue(SqQueue& Q){
free(Q.base);
Q.base = NULL;
return OK;
}
// 清空队列
Status ClearQueue(SqQueue& Q){
Q.front = Q.rear = 0;
return OK;
}
// 判断是否为空
Status QueueEmpty(SqQueue& Q){
return Q.front == Q.rear ? True : False;
}
// 获取队列长度
int QueueLength(SqQueue Q){
// 循环队列Q.rear - Q.front可能为负
return ((Q.rear - Q.front + MAXQSIZE) % MAXQSIZE);
}
// 获得队头元素
Status GetHead(SqQueue Q, QElemtype& e){
// 非空则返回队头元素
if(Q.rear != Q.front){
e = Q.base[Q.front];
return OK;
}
printf("error: SqQueue is Empty\n");
return ERROR;
}
// 队尾插入元素
Status EnQueue(SqQueue& Q, QElemtype e){
//对列满
if((Q.rear + 1) % MAXQSIZE == Q.front)
return ERROR;
// 插入元素
Q.base[Q.rear] = e;
//更新队尾
Q.rear = (Q.rear + 1) % MAXQSIZE;
return OK;
}
// 删除队头元素
Status DeQueue(SqQueue& Q, QElemtype& e){
//空对列
if(Q.front == Q.rear)
return ERROR;
// 用e返回队头
e = Q.base[Q.front];
// 更新队头
Q.front = (Q.front + 1) % MAXQSIZE;
return OK;
}
// 从队头到队尾visit遍历
Status QueueTraverse(SqQueue Q, void (*visit)(QElemtype)){
//空对列
if(Q.front == Q.rear)
return ERROR;
for(int i = Q.front ;i != Q.rear ; i = (i + 1) % MAXQSIZE){
visit(Q.base[i]);
}
return OK;
}
每天进步一点,加油!
End
END