1、队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除操作的特殊线性表,队列具有__先进先出__FIFO(First In First Out)
入队列:进行插入操作的一端称为__队尾__。
出队列:进行删除操作的一端称为__对头__。
数据存放:现在有数据:A、B、C、D要存放,那存放数据顺序就是一次存入,并且存放的位置如下:
1.1、队列的实现概念
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果数组的结构,出队列在数组头上出数据,效率会比较低。
因为数组队列在处一个数据后们需要整体挪动数据向前,所以麻烦,这里不如链表。而链表在出数据是,把节点释放,然后将phead在指向要出数据的后一个数据即可,并且我们也记录个尾节点ptail,用来链接放在队列中新数据即可。
所以队列的实现主要以链表队列为主要。
2、链队列的实现
队列主要有以下接口函数
- 初始化(QueueInit)
- 销毁(QueueDestroy)
- 扩容(BuyQueueNode)
- 队尾插入数据(QueuePush)
- 对头删除数据(QueuePop)
- 取队尾数据(QueueBack)
- 取对头数据(QueueFront)
- 统计队列中数据个数(QueueSize)
- 判断队列是否为空(QueueEmpty)
2.1、定义结构体
这里需要重点说明这个结构体的定义。
1、首先我们定义一个QueueNode结构体,这个结构体就是动态扩容的结点。里面有一个Next指针域,还有一个data数据域。如下:
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
2、然后是重点来了:这里我们需要创建两个QueueNode*类型的指针:head,tail。分别用来队列的头结点和尾节点。如下:
typedef struct Queue
{
struct QueueNode* head;
struct QueueNode* tail;
}Queue;
然后整体结构体定义就如下:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* Next;
}QNode;
typedef struct Queue
{
struct QueueNode* head;
struct QueueNode* tail;
}Queue;
2.2、队列初始化
将头结点和尾节点置为NULL即可。
//队列初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
2.3、销毁
依次遍历结点进行销毁
//销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->Next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
2.4、扩容
//扩容
QNode* BuyQueueNode(QDataType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->Next = NULL;
return newnode;
}
2.5、队尾插入数据
队列在队尾入数据,因为我们提前设置了尾结点(tail),那操作就容易了,先将tail的Next域指向新节点的地址,然后再将新节点的地址赋值给tail,便于下次再尾插。
但是由于我们没创建哨兵位,所以在进行结点的插入和删除时,需要考虑边界问题。
那这里,特殊情况就是,如果是第一次插入数据,也就是当pq->head == NULL时,我们可以直接将新节点的地址赋值给头结点和尾节点。
//对尾插入数据
void QueuePush(Queue* pq, QDataType x)
{
QNode* newnode = BuyQueueNode(x);
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->Next = newnode;
pq->tail = newnode;
}
}
2.6、对头删除数据
核心思想:先保存头结点的后驱结点的地址,然后释放头结点,然后将先前保存头结点的后驱结点的地址赋值给头结点。
特殊情况:当pq->head == NULL时,也就意味着整个队列为空了,所以我们还需要将pq->tail = NULL。
//队头删除数据
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QNode* next = pq->head->Next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)
{
pq->tail = NULL;
}
}
2.7、取队尾数据
//取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
2.8、取对头数据
//取对头数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
2.8、统计队列元素个数
使用个计数器,依次遍历队列中的结点,来统计队列元素个数。
//统计队列元素个数
int QueueSize(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
int count = 0;
QNode* cur = pq->head;
while (cur)
{
count++;
cur = cur->Next;
}
return count;
}
2.10、判断队列是否为空
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
2.11、main函数测试程序
下面测试程序功能:遍历输出队列中的数据。
#include "queue.h"
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
QueuePush(&q, 6);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
QueueDestroy(&q);
return 0;
}
输出:
3、全代码展示
这里使用三个文件:
- queue.h:用于结构体、各种函数接口的声明
- queue.c:用于各种函数接口的定义。
- test.c:用于创建链表,实现链表。
3.1、queue.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* Next;
}QNode;
typedef struct Queue
{
struct QueueNode* head;
struct QueueNode* tail;
}Queue;
//队列初始化
void QueueInit(Queue* pq);
//销毁
void QueueDestroy(Queue* pq);
//扩容
QNode* BuyQueueNode(QDataType x);
//对尾插入数据
void QueuePush(Queue* pq, QDataType x);
//队头删除数据
void QueuePop(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//取对头数据
QDataType QueueFront(Queue* pq);
//统计队列元素个数
int QueueSize(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
3.2、queue.c
#include "queue.h"
//队列初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
//销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->Next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
//扩容
QNode* BuyQueueNode(QDataType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->Next = NULL;
return newnode;
}
//对尾插入数据
void QueuePush(Queue* pq, QDataType x)
{
QNode* newnode = BuyQueueNode(x);
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->Next = newnode;
pq->tail = newnode;
}
}
//队头删除数据
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QNode* next = pq->head->Next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)
{
pq->tail = NULL;
}
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
//取对头数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
//统计队列元素个数
int QueueSize(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
int count = 0;
QNode* cur = pq->head;
while (cur)
{
count++;
cur = cur->Next;
}
return count;
}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
3.3、test.c
#include "queue.h"
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
QueuePush(&q, 6);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
QueueDestroy(&q);
return 0;
}