文章目录
前言
数据结构的常见线性表,分别是顺序表,链表,栈,队列
本篇给大家带来链表结构实现队列和讲解,这也是线性表结构的最后一篇,大家一起共勉
1. 队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾出队列:进行删除操作的一端称为队头
2. 队列的实现设计
队列的特性是队头出数据,队尾入数据,这里同样顺序表
和链表
都可以实现
- 链表:头插效率高,可是尾插需要找尾啊,同样效率不高
- 顺序表:涉及到头部操作,顺序表就显得无能为力
顺序表结构是固定死了的,链表可以变形,我们只需要在多定义一个尾结点即可,说道这里,肯定会有疑问,为什么之前单链表的时候不这样做,因为单链表尾删需要找尾的前一个,可队列只需要尾插,不需要找前一个。
综上所诉,我推荐用链表实现,不过需要额外定义了一个尾结点
3. 栈的功能实现
首先我们看下结构类型的定义和实现功能
#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;
}QueueNode;
// 定义队列
// 队列里面有头和尾结点,头和尾结点的类型是单个节点的结构
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
// 初始化
void QueueInit(Queue* pq);
// 判断队列是否为空
bool QueueEmpty(Queue* pq);
// 入队
void QueuePush(Queue* pq, QDataType x);
// 出队
void QueuePop(Queue* pq);
// 获取队头数据
QDataType QueueFront(Queue* pq);
// 获取队尾数据
QDataType QueueBack(Queue* pq);
// 队列的大小
int QueueSize(Queue* pq);
// 销毁
void QueueDestroy(Queue* pq);
这里说明下这定义两个结构的作用和区别
首先我们用链表的话,需要定义单个节点(data和next),目的是存数据和通过指针来链接下一个节点,这就是结构体QueueNode
。
这里又定义了一个队列结构体(Queue),目的是放下头和尾两个结点,因为这两个节点实现了队列和方便访问头和尾,这两个结点类型都是单个节点结构(QueueNode),这就是结构体Queue
简单来说就是嵌套结构,队列里两层结构
- 第一层:方便访问头和尾。
- 第二层:通过头和尾访问data和next
如访问队列头节点的数据:队列的头的data,q->head->data
3.1. 初始化
// 初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
3.2. 判断队列是否为空
如果队列的头节点为NULL返回true
,不为空返回false
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
3.3. 入队(队尾插入数据)
- 创建新节点
- 插入新节点,分两种情况
- 第一种:队列为空,头和尾就需要赋值为新节点,头赋值是了标志第一个结点,尾赋值是方便下一次插入数据
- 第二种:队列不为空,尾指针的next链接新节点,在更新尾结点
// 入队
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
// 创建新节点
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
assert(newNode);
newNode->data = x;
newNode->next = NULL;
// 插入数据分两种情况
// 1. 队列为空:头和尾赋值为新节点
// 2. 队列不为空:直接尾插,在更新尾结点
if (pq->head == NULL)
{
pq->head = pq->tail = newNode;
}
else
{
pq->tail->next = newNode;
pq->tail = newNode;
}
}
3.4. 出队(队头删除数据)
- 队列为空不能删除
- 保存头节点下一个节点,防止free后,就找不到的情况
- 释放头节点
- 刚才保存的结点设新的头
// 出队
void QueuePop(Queue* pq)
{
assert(pq);
// 空队列不能删除
assert(!QueueEmpty(pq));
// 头删
QueueNode* headNext = pq->head->next;
free(pq->head);
pq->head = headNext;
// 头为空表示队列空了,因为上面的步骤只把头给置空,尾没有,tail就是野指针了
if (pq->head == NULL)
pq->tail = NULL;
}
3.5. 获取队头数据
// 获取队头数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
// 空队列不能获取数据
assert(!QueueEmpty(pq));
return pq->head->data;
}
3.6. 获取队尾数据
注意:空队列不能获取数据
// 获取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
// 空队列不能获取数据
assert(!QueueEmpty(pq));
return pq->tail->data;
}
3.7. 队列的大小
链表只能通过遍历来获取数据个数,条件是为cur==NULL
结束
// 队列的大小
int QueueSize(Queue* pq)
{
assert(pq);
int size = 0;
QueueNode* cur = pq->head;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
3.8. 销毁
类似于单链表的销毁,只不过最后需要把头和尾都设为NULL
// 销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
4. 功能测试
队列和栈一样是不允许遍历,由于只有队头和队尾两端的数据可以被使用,但是只有队头能取数据,所以要把队头的数据取了,才能取下一个,要实现打印数据,需要一个一个取数据,在出队,队列为空结束
5. 总结
链表学习完,实习队列并不难,只不过在单链表和基础上多添加了尾结点,因此造成嵌套结构体,一共两层,主要理解和熟悉队列的特性(先进先出)和链表结构的多样性。