文章目录
1、认识队列
队列的一大特点就是先进先出,后如后出。队列是有序的,从头出,从尾入。
一个很形象的应用就是医院的挂号机,有序的每次叫队列队头的号码,机器每次从记录的队尾加数据。
2、队列实现
一个队列需要实现的操作如下。
- 入队
- 出队
- 查看队头
- 查看队尾
- 计算队长度
结构的选择有顺序表和链表,从优缺点来看。
- 入队,顺序表和链表尾插的效率都一样,但在空间上链表更好,不会浪费空间。
- 出队,顺序表头删的效率低于链表头删的效率。
- 查看队头,都比较容易。
- 查看队尾,顺序表比较容易,链表需要遍历在尾部查看,如果没有建立尾指针的话。
- 计算对长度,顺序表本身记录了,链表需要遍历计算。
似乎都有自己擅长的领域,那么都实现一遍吧。
3、非循环队列单链表写法
3.1、测试页和头文件
为了在实现一些功能上更加的便利,在创建队列的结构体中创建头尾指针变量,以及一个size记录长度。
头文件中
//Queue.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QueueNode;
//队列结构体 有效避免二级指针
typedef struct Queue
{
QueueNode* front;
QueueNode* tail;
int size;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
//入队
void QueuePush(Queue* pq, QDataType x);
//出队
void QueuePop(Queue* pq);
//返回队头数据
QDataType QueueFront(Queue* pq);
//返回队尾数据
QDataType QueueBack(Queue* pq);
//队列大小
int QueueSize(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
测试文件中
//Test.c
#include"Queue.h"
void QueueTest1()
{
Queue p;
QueueInit(&p);
QueuePush(&p, 1);
QueuePush(&p, 2);
QueuePush(&p, 3);
///*QueuePop(&p);
//QueuePop(&p);
//QueuePop(&p);*/
printf("%d\n", QueueSize(&p));
while (!QueueEmpty(&p))
{
printf("%d ",QueueFront(&p));
QueuePop(&p);
}
QueueDestroy(&p);
}
int main()
{
QueueTest1();
return 0;
}
3.2、接口函数的实现
队列的函数实现比较容易
#include"Queue.h"
//初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->front = NULL;
pq->tail = NULL;
pq->size = 0;
}
//销毁队列
//从头到尾一次一个结点销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* p = pq->front;
while (p)
{
QueueNode* cur = p;
p = p->next;
free(cur);
}
pq->front = NULL;
pq->tail = NULL;
pq->size = 0;
}
//入队
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
//先创建新结点
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//入队
if (pq->front == NULL)
{
pq->front = newnode;
pq->tail = pq->front;
}
else
{
pq->tail->next = newnode;
pq->tail = pq->tail->next;
}
pq->size++;
}
//出队
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QueueNode* p = pq->front;
pq->front = pq->front->next;
free(p);
pq->size--;
}
//看头
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->front->data;
}
//看尾
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
//看长
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
//看空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->front == NULL;
}
4、非循环队列顺序表写法
4.1、测试页和头文件
头文件中
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct Queue
{
int size;
int capacity;
QDataType* a;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
//入队
void QueuePush(Queue* pq, QDataType x);
//出队
void QueuePop(Queue* pq);
//返回队头数据
QDataType QueueFront(Queue* pq);
//返回队尾数据
QDataType QueueBack(Queue* pq);
//队列大小
int QueueSize(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
测试文件中
//Test.c
#include"Queue.h"
void QueueTest1() {
Queue p;
QueueInit(&p);
QueuePush(&p, 1);
QueuePush(&p, 2);
QueuePush(&p, 3);
///*QueuePop(&p);
//QueuePop(&p);
//QueuePop(&p);*/
printf("%d\n", QueueSize(&p));
while (!QueueEmpty(&p))
{
printf("%d ",QueueFront(&p));
QueuePop(&p);
}
QueueDestroy(&p);
}
int main() {
QueueTest1();
return 0;
}
4.2、接口函数的实现
#include"Queue.h"
//初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->a = NULL;
pq->size = -1;
pq->capacity = -1;
}
//销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
free(pq->a);
pq->size = pq->capacity = 0;
}
//入队
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
if (pq->size == pq->capacity)
{
//扩容
int newcapacity = pq->capacity == -1 ? 4 : pq->capacity;
QDataType* tmp = (QDataType*)realloc(pq->a, sizeof(QDataType) * newcapacity);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
pq->a = tmp;
pq->capacity = newcapacity;
}
++pq->size;
pq->a[pq->size] = x;
}
//出队
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
int i = 0;
for (i = 0; i < pq->size; i++)
{
pq->a[i] = pq->a[i + 1];
}
pq->size--;
}
//返回队头数据
QDataType QueueFront(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->a[0];
}
//返回队尾数据
QDataType QueueBack(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->a[pq->size];
}
//队列大小
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size + 1;
}
//判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == -1;
}
5、循环队列的实现
循环队列的实现概念来自一个leetcode上的题。
循环队列的逻辑图
在题目要求中,我们需要完成:
- 队列创建
- 获取队头队尾数据
- 插入删除
- 判空,判满
值得注意的是,队列的长度在一开始就确定,所以是个静态的。
那么,对于循环队列的结构我们选链表还是顺序表?
6、循环队列单链表写法
如果选择链表
- 队列创建,一开始我们就需要循环创建一个长度为K的链表,这个链表有着队头和队尾指针。
- 获取队头队尾数据,只需要通过队头队尾指针获取。
- 插入删除,由于链表长度固定,插入就是尾指针指向的data直接赋值,删除就让队头指针到下一个位置。
- 判空,判满,这个似乎很复杂,下面讨论一下这两种情况。
链表结构的判空与判满:
在创建好 K=6 的链表后,front和back分别表示队头和队尾指针,这个时候如果插入数据,back会有两种插入表示方法,第一种back位置直接插入,front不动back++,这种情况判空很容易,当front和back相等的时候队列为空。
如果一直往下插入,当在back位置插入后,back又回到了front位置,这个时候队列是满的,但与队空的情况出现一致,所以这种方式还是有问题的。
那么就想,在这个循环队列中,能否每次多开辟一个空间,使得back能够在队满的时候能和front有一个距离,并且使得每次插入的操作都是一样的。
所以此时,每当back->next==front时,队列为满。
代码实现
typedef int SLDataType;
typedef struct STList
{
struct STList* next;
SLDataType data;
}SL;
typedef struct {
SL* head;
SL* front;
SL* back;
} MyCircularQueue;
//声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
//创建队列
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->head = obj->front = obj->back = NULL;
//建立循环链表 为了更方便判断队列为空以及队列为满的情况 还是建立K+1的队列长度
int i = 0;
for (i = 0; i <= k; i++)
{
SL* newnode = (SL*)malloc(sizeof(SL));
newnode->next = NULL;
if (obj->head == NULL)
{
obj->head = obj->front = obj->back = newnode;
}
else
{
obj->back->next = newnode;
obj->back = newnode;
}
}
//尾接头 构成循环
obj->back->next = obj->front;
obj->back = obj->front;
return obj;
}
//队列插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (myCircularQueueIsFull(obj))
{
return false;
}
else
{
obj->back->data = value;
obj->back = obj->back->next;
return true;
}
}
//队列删除数据
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return false;
}
else
{
obj->front = obj->front->next;
return true;
}
}
//看队头数据
int myCircularQueueFront(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
return obj->front->data;
}
}
//看队尾数据
int myCircularQueueRear(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
SL* p = obj->front;
while (p->next != obj->back)
{
p = p->next;
}
return p->data;
}
}
//判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->back == obj->front;
}
//判满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return obj->back->next == obj->front;
}
//摧毁队列
void myCircularQueueFree(MyCircularQueue* obj) {
obj->back = obj->head;
obj->front = obj->head;
//将back至尾
while (obj->back->next != obj->head)
{
obj->back = obj->back->next;
}
//从头开始一个一个删结点
while (obj->head != obj->back)
{
obj->head = obj->head->next;
free(obj->front);
obj->front = obj->head;
}
free(obj->back);
obj->head = obj->front = obj->back = NULL;
free(obj);
obj = NULL;
}
7、循环队列顺序表写法
如果使用顺序表:
- 队列创建,开辟一块连续的固定空间,依然是有容量和当前大小。
- 获取队头队尾数据,只需要通过下标0和当前大小来获取。
- 插入删除,插入数据就size++,删除就size–。
- 判空,判满,依然需要我们讨论。
- 顺序表的循环的实现:在size大于k值的时候,需要对size取余数,这样就很好实现了循环。
顺序表结构的判空与判满:
K=6
同样是多建立一个空间,但这也使得,如果我们在改变size的时候,都需要考虑一下size是否大于k+1,能够实现循环效果。
代码实现
typedef int QDataType;
typedef struct {
int k; //队列长度
QDataType* a;
int front;
int back;
} MyCircularQueue;
//声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
//创建队列
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a = (QDataType*)malloc(sizeof(QDataType) * (k + 1)); //多开一个空间
obj->k = k + 1;
obj->front = obj->back = 0;
return obj;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (!myCircularQueueIsFull(obj))
{
obj->back = (obj->back) % (obj->k); //实现循环效果
obj->a[obj->back] = value;
obj->back++;
return true;
}
else
{
return false;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (!myCircularQueueIsEmpty(obj))
{
obj->front++;
obj->front = (obj->front) % (obj->k); //实现循环效果
return true;
}
else
{
return false;
}
}
int myCircularQueueFront(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
return obj->a[obj->front];
}
}
int myCircularQueueRear(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
if (obj->back == 0)
{
obj->back = obj->k;
}
return obj->a[obj->back - 1];
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->back;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (((obj->back) + 1) % (obj->k)) == obj->front;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
obj->k = obj->front = obj->back = 0;
free(obj);
}
本章完。