数据结构之队列
Posted 芒果再努力
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之队列相关的知识,希望对你有一定的参考价值。
前言:
前面,我们学习了栈,单链表,双向链表,顺序表的内容。大家可以看看我上面写过的文章!
栈 | 双链表 | 单链表 |
动态顺序表 | 静态顺序表 |
目录
一.什么是队列
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out)入队列:进行插入操作的一端称为队尾出队列:进行删除操作的一端称为队头
队尾入数据,队头出数据
问题:选择什么结构实现队列?
1.若我们使用数组实现队列:插入数据很方便,时间复杂度为O(1),但是删除数据,需要将后面的数据往前覆盖。需要遍历数组效率低,时间复杂度为O(N)
2.若我们使用单链表实现队列:入数据相当于尾插,出数据相当于头删。若我们只定义一个头指针,尾插数据则要遍历找到尾节点,效率低。但是若我们定义两个指针,一个指向头节点,一个指向尾节点。这样的话,入数据和出数据效率都是O(1)。
综上:我们选择单链表实现队列!
二.项目创建
工程文件 | 存放的内容 |
---|---|
Queue.h | 函数声明,宏定义 |
Queue.c | 实现顺序表函数接口的定义 |
test.c | 测试函数接口是否能实现功能 |
三.队列代码实现
1.队列结构体的创建
队列:先进先出
只允许一端插入,另一端删除
队尾:入数据 队头:出数据为了方便后序更改变量,我们用typedef重命名变量。创建结构体队列节点,再创建一个结构体Queue用来存放指向队头的节点和队尾的节点。
typedef int QDataType;
//节点
typedef struct QueueType
{
QDataType data;
struct QueueType* next;
}QNode;
typedef struct Queue
{
QNode* head; //指向队头节点
QNode* tail; //指向队尾节点
}Queue;
我们定义Queue结构体变量pq
struct Queue pq;
2.初始化头尾指针
初始化指向队头和队尾的指针为空 。传值时,因为形参是实参的临时拷贝,所以我们要传址。
//初始化头尾指针
void QueueInit(struct Queue* pq)
{
pq->head = NULL;
pq->tail = NULL;
}
3.队尾入数据
动态开辟节点,要判断是否开辟成功! 入数据相当于尾插
注:Queue结构体的头指针(pq->head)指向的就是头节点
情况1:当队列无数据时,直接将头指针和尾指针指向新开辟的节点
情况2:当队列中有数据时,第一个节点链接新节点。尾指针指向新开的节点
情况1:
情况2:
//队尾入数据 ---用尾指针尾插
void QueuePush(Queue* pq, QDataType x)
{
//创建节点
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
printf("malloc fail\\n");
exit(-1);
}
else
{
newnode->data = x;
newnode->next = NULL;
}
//队列无数据时
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode; //尾节点链接新节点
pq->tail = newnode;//队尾指针指向新节点
}
}
4.队头出数据
队头出数据相当于头删
出数据要保证队列中要有数据
情况1:只有一个节点(即pq->head->next == NULL)时,直接释放第一个节点。并且把头指针和尾指针置空!
情况2:有多个节点,保存第二个节点,释放第一个节点,然后头指针指向第二个节点。
//队头出数据---用头指针头删
void QueuePop(Queue* pq)
{
assert(pq);
//要保证队列中有数据
assert(pq->head);
//当只有一个节点时->free掉第一个节点
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
//多个节点---保存第二个节点,释放第一个节点,头指针指向第二个节点
else
{
QNode* next = pq->head->next;//pq->head指向的就是第一个节点 pq->head->next 第二个节点
free(pq->head);
pq->head = next;
}
}
5.求队列长度
1.因为队列长度肯定是大于等于0的,所有函数返回类型可以为size_t
size_t 即为:unsigned int。
2.使用计数器,遍历队列求队列长度
//求队列长度
int QueueSize(Queue* pq)
{
//遍历求长度
assert(pq);
int size = 0;
QNode* cur = pq->head; //第一个节点
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
6.返回队头数据
头指针指向的就是头节点
//返回队头数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->head);//防止队列为空
return pq->head->data;
}
7.返回队尾数据
尾指针指向的就是尾节点
//返回队尾数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->head);//防止队列为空
return pq->tail->data;
}
8.判断队列是否为空
最初定义头指针为空,如果仍为空指针说明队列为空!
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
9.队列销毁
节点是动态开辟的,所以我们要销毁空间,防止内存泄漏!遍历队列进行销毁
步骤:释放节点时,要先保留下一个节点,不然就找不到下一个节点!
注意:头指针和尾指针要置空
//销毁
void QueueDestory(Queue* pq)
{
assert(pq);
//遍历销毁
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;//保留下一个节点
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
四.总结
1.使用单链表实现队列,定义前后指针,一个指向队头,一个指向队尾!这样的话出数据和入数据的效率都是O(1)。 队头出数据,队尾入数据!
那之前我们实现单链表时为什么不使用双指针呢?-->因为这样我们只能解决尾插,不能解决尾删,还得要找到上一个节点。
2.打印数据时,不能直接打印!不然的话就不满足队列:先进先出的特点。要先取队头数据,再打印,然后出队头数据。以此循环。
五.原码链接
以上是关于数据结构之队列的主要内容,如果未能解决你的问题,请参考以下文章