文章目录
前言
队列是线性表中一种特殊的结构,用来存储和管理数据,并解决一些相关的问题。
提起队列就不得不提起栈了,栈和队列两种结构十分类似,所以在大多数知识体系中都是将栈和队列放在一块对比学习。这一点我是十分认可的,因此我觉得大家在学习队列之余可以参考一下我的上篇文章《C语言设计实现栈》,相信对于你的学习一定会有很好的效果。
本篇文章博主会主要向大家介绍队列的相关概念,以及用C语言设计队列结构,并实现队列的一些基本功能,比如入队列、出队列、返回队头数据等等…
下面开始介绍。
1. 认识队列
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有
先进先出
FIFO(First in First Out).
- 入队列:进行插入操作的一端称为
队尾
。 - 出队列:进行删除操作的一端称为
队头
。
2. 设计队列结构
队列底层可以用数组或者链表的结构实现,相较而言使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
单链表实现队列的示意图如下:
下面就来定义一个队列的结构:
typedef int QDataType;
// 链式结构:表示队列一个结点
typedef struct QueueNode
{
struct QueueNode* _next;
QDataType _data;
}QueueNode;
// 队列的结构
typedef struct Queue
{
QueueNode* _head; //队头
QueueNode* _tail; // 队尾
}Queue;
先定义队列的底层结构单链表结点,链表结点有两个域,一个数据域存放入队列的数据,一个指针域存放指向下一个结点的指针。再定义一个队列结构,队列结构有两个成员,一个是指向队头的指针,一个是指向队尾的指针。
3. 队列相关功能实现
3.1 队列的初始化
实现代码如下:
// 初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->_head = pq->_tail = NULL;
}
队列刚创建之初,先让队列的队头和队尾指向空指针。当然,还要断言接收到的头指针不为空指针。
3.2 数据入队
在用单链表实现队列时,我们规定的是链表的头结点为队头,尾结点处为队尾。根据队列入队的规则,数据要从队尾插入,所以入队的操作实际上就是链表的尾插操作
。
这里要分两种情况进行:
第一:入队前队内没有数据
此时队列结构内的队头和队尾都指向刚入队的新结点。
第二:入队前队内存在其他数据
这时队头指向的位置不动,将队尾指向的结点和新结点链接在一起,然后队尾指向新结点
实现代码如下:
// 队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
printf("内存不足\n");
exit(-1);
}
newnode->_data = x;
newnode->_next = NULL;
if (pq->_head == NULL)
{
pq->_head = pq->_tail = newnode;
}
else
{
pq->_tail->_next = newnode;
pq->_tail = newnode;
}
}
一开始先动态申请一个新结点,如果申请失败直接结束程序。
新结点申请成功之后将要入队的数据放入新结点中,然后根据我上面提到的两种情况进行入队。
3.3 数据出队
根据队列出队列的规则,数据要从队头删除,所以出队列实际上就是单链表的头删操作
。
出队列同样要分为两种情况进行:
第一种:出队列前队列中有一个以上的结点
此时只需释放掉队头结点,然后让队头的下一个结点称为新的队头,队尾不用进行任何操作。
第二种:出队列前队列中只有一个结点
此时释放完队头之后,队头指向空指针,同时还需要手动让队尾也指向空指针。
实现代码如下:
// 队头出队列
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->_head);
QueueNode* next = pq->_head->_next;
free(pq->_head);
pq->_head = next;
if (pq->_head == NULL)
{
pq->_tail = NULL;
}
}
3.4 获取队头数据
获取队头数据只需返回队头指针所指向结点的数据域内容即可,实现代码如下:
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->_head);
return pq->_head->_data;
}
3.5 获取队尾数据
获取队尾数据只需返回队尾指针所指向结点的数据域内容即可,实现代码如下:
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->_tail);
return pq->_tail->_data;
}
3.6 检测队列是否为空,如果为空返回非零结果,如果非空返回0
检测队列是否为空只需判断队头指向的结点是否为空,实现代码如下:
int QueueEmpty(Queue* pq)
{
assert(pq);
return pq->_head == NULL ? 1 : 0;
}
此处返回一个条件表达式的值即可,如果头结点为空返回1,不为空返回0.
3.7 获取队列中有效元素个数
获取队列有效元素个数,需要将整个队列遍历一遍,有多少个结点就有多少个元素。
实现代码如下:
int QueueSize(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->_head;
int size = 0;
while (cur)
{
++size;
cur = cur->_next;
}
return size;
}
定义一个指针指向头结点,从头结点开始遍历。定义一个整型变量size统计结点个数,遍历一个结点size加1,最终返回size的值即可。
3.8 销毁队列
销毁队列还是需要遍历队列的每一个结点,遍历一个释放一个。等到所有结点都释放掉,再将队头和队尾指针置为空指针。
实现代码如下:
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;
}
本篇文章到这里就全部结束了,希望可以为大家带来帮助。