队列的介绍
队列的概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
队列的实现
由队列的定义及队列具有先进先出FIFO(First In First Out) 的特点,采用链表来实现队列是十分合适的(队列会对头部进行频繁操作,数组就不好处理头部。而链表就十分简单)
队列的结构
队列使用单链表实现的,所以节点是必不可少的。该节点包含了存储的数据和指向下一个节点的指针。但在使用队列时我们要找头,尾节点来实现出队列和入队列的操作,所以我们再定义一个结构体Queue来管理这个队列。Queue这个结构体里有两个指向节点的指针来指向队列的头和尾,还有一个记录元素个数的size。有了这样一个结构体我们就能很好的管理队列了,提高了效率。
typedef int QDataType;
typedef struct QueueNode//队列节点
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue//用来管理队列
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
队列的初始化
结构体Queue指向了节点,所以对Queue操作就好
队列的初始化即将管理节点的头尾指针置为NULL并把size置为0即可
assert断言是为了防止没有将结构体的地址传过来进行防范
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
入队列
入队列是在队尾进行数据插入。当申请好节点后,需要区分两种状况。
- 第一种是还没有节点的情况,这时需要将头尾指针都指向插入的新节点
- 第二种是已经有节点了,只需要将尾节点连接新插入的节点,再更新尾节点即可。
- 最后别忘记把size++。
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* node = (QNode*)malloc(sizeof(Queue));
if (node == NULL)
{
perror("malloc failed");
return;
}
node->data = x;
node->next = NULL;
if (pq->phead == NULL)//还没有节点的情况
{
pq->phead = pq->ptail = node;
}
else//已经有节点的情况
{
pq->ptail->next = node;
pq->ptail = node;
}
pq->size++;
}
出队列
出队列是头节点出队列,即删除头节点。删除节点先得看看是否为空,为空还删这不是搞笑嘛。由于节点是在堆区malloc出来的,所以在头节点出队列后需要释放该节点,否则会造成内存泄漏。
删除操作需要先将头节点的下一个节点的位置先保存,这样就能找到新的头节点,如果直接释放头节点,将找不到新的头节点。释放头节后将头节点更新,并把size–。
- 还需要注意的是删除也要分两种情况
- 一种是还有多个节点的情况,按上述的方法删除没有问题。
- 但当只有一个节点时,如果还按上述方法就行不通了。当只有一个节点时,头尾指针都是指向该节点,释放后,上述方法会将phead置为NULL没错,但是ptail仍然指向这个释放了的节点,这样就会使ptail变为野指针。所以需要单独处理这种情况
- 当phead和ptail相等且不为空时就是只有一个节点的情况,找到后将两者都置为空就可以了
- 两种情况都记得把size–
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
//QNode* Next = pq->phead->next;//缺少对ptail的考虑,当只有一个节点时,ptail会变成野指针
//free(pq->phead);
//pq->phead = Next;
//pq->size--;
if (pq->phead!=NULL
&&(pq->phead == pq->ptail))//处理只有一个节点的问题
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
pq->size--;
}
else
{
QNode* Next = pq->phead->next;
free(pq->phead);
pq->phead = Next;
pq->size--;
}
}
获取队列元素个数
这个很简单,返回size就可以
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
获取队列队头数据
在一番常规检查后找到头节点将其数据返回。
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->phead->data;
}
获取队列队尾数据
操作同上
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
判断队列是否为空
上面已经看到过使用这个函数了,该函数返回类型为布尔类型。要实现也简单,即判断phead是否为空。
如果为空,即返回真,不为空返回假,再搭配assert使用。具体使用方法可以看看上一篇栈的实现中对判断栈是否为空这个函数的讲解
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
队列的销毁
用判断队列是否为空这个函数作为循环条件。
先用del指针来记录要free的节点,再更新头节点,最后释放del指向的节点。循环这个操作直至循环停止
void QueueDestroy(Queue* pq)
{
assert(pq);
while (!QueueEmpty(pq))
{
QNode* del = pq->phead;
pq->phead = pq->phead->next;
free(del);
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
自此,队列的实现也完成了,希望对大家有所帮助,也请佬们指出不足,共同进步。
完整代码
老规矩,依旧三个文件
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;
}QNode;
typedef struct Queue//用来管理队列
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
void QueueInit(Queue* pq);//初始化
void QueueDestroy(Queue* pq);//销毁
void QueuePush(Queue* pq, QDataType x);//入队列
void QueuePop(Queue* pq);//出队列
int QueueSize(Queue* pq);//队列个数
QDataType QueueFront(Queue* pq);//获取队头数据
QDataType QueueBack(Queue* pq);//获取队尾数据
bool QueueEmpty(Queue* pq);//判空
Queue.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
while (!QueueEmpty(pq))
{
QNode* del = pq->phead;
pq->phead = pq->phead->next;
free(del);
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* node = (QNode*)malloc(sizeof(Queue));
if (node == NULL)
{
perror("malloc failed");
return;
}
node->data = x;
node->next = NULL;
if (pq->phead == NULL)
{
pq->phead = pq->ptail = node;
}
else
{
pq->ptail->next = node;
pq->ptail = node;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
//QNode* Next = pq->phead->next;//缺少对ptail的考虑,当只有一个节点时,ptail会变成野指针
//free(pq->phead);
//pq->phead = Next;
//pq->size--;
if (pq->phead!=NULL
&&(pq->phead == pq->ptail))//处理只有一个节点的问题
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
pq->size--;
}
else
{
QNode* Next = pq->phead->next;
free(pq->phead);
pq->phead = Next;
pq->size--;
}
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->phead->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void QueueTest()
{
Queue que;
QueueInit(&que);
//QueuePush(&que, 1);
//QueuePush(&que, 2);
//QueuePush(&que, 3);
//QueuePush(&que, 4);
//QueuePop(&que);
//printf("Size:%d\n", QueueSize(&que));
//while (!QueueEmpty(&que))//队列数据的打印
//{
// QDataType front = QueueFront(&que);
// QueuePop(&que);
// printf("%d ", front);
//}
QueueDestroy(&que);
}
int main()
{
QueueTest();
return 0;
}