所属专栏:初始数据结构❤️
>博主首页:初阳785❤️
>代码托管:chuyang785❤️
>感谢大家的支持,您的点赞和关注是对我最大的支持!!!❤️
>博主也会更加的努力,创作出更优质的博文!!❤️
>关注我,关注我,关注我,重要的事情说三遍!!!!!!!!❤️
队列的概念:
只允许一段进行插入数据,在另一端删除数据操作的特殊线性表队列具有先进先出FIFO(fist in fist out)
队列出列和进列的专有名词:
入队列:
进行插入操作的一端称为队尾(Enqueue)
。
出队列:
进行删除操作的一端称为队头(Dequeue)
。
我们可以举一些生活中的例子来理解什么是队列。就好比学生在食堂打饭,学什么总是排好了队伍,谁先到谁就先可以先打到饭,后到的总是排在最后面,也是最晚打到饭的,而同时最先达到饭的也同样是最先找到位置坐下吃饭的。所以这就是队列的特点,先来的先走,后来的后走。
要是实现栈,有两种方法:1.数组(数组栈),2.链表(链表栈)
。
如果是数组的话:
我们知道队列的特点就是先进队列的先出,后进队列的后出
,也就是说在数组中下标为0处的地方就是队头,数组最后才的数据就是队尾。所以我们要进行进队
的话就比较简单了,就是直接在数组后面添加数据就行了,但是如果要进行出列的话那么数组就需要进行移动数据,这个就先当的费时。如果是链表的话:``出列和进列
就分别对应着头删和尾插
,而链表这一操作并不需要要移动数据,就会比较简单。所以综上所述我们本次队列的实现采用单链表的形式来实现。
我们分析一下,如果我们要出队列的话,这个也是比较简单的,我们的队头就是head
头指针,就是要删除head
这个数据,这个只需要在定义一个next=head->next
变量,再free(head)
最后再让我们head=next
就行了。但是我们要怎么实现进队列呢?我们不妨再定义一个变量tail
尾指针来表示我们的队尾。这样做到好处就是我们不需要再找尾指针了,只需要子tail后面接上就行了。
而对比之前的单链表来讲的话,单链表为什么只是定义了一个head
头指针呢,原因是如果我们定义了tail
尾指针,我们单链表在进行尾删的时候tail
尾指针没什么作用,也就是说单链表的整体实现定义尾指针的意义不大,所以就不需要。但是我们的队列的话是不需要进行尾删的,也就是说我们的队列可以将尾指针充分的利用起来。
而既然我们定义了head
头指针和tail
尾指针这两个值,一般我们定义了多个值就可以使用一个结构将他们放在一起的,这样做后面传值/址
的时候就不许要将每个址/值
都传过去,只需要把这个结构传过去就行了,就能更简化我们的代码。
typedef int QDataType;
//初始化指针
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
typedef struct QueueNode
{
struct QueueNode* next;//记录下一个节点
QDataType data; //存放数据
}QueueNode;
typedef struct Queue
{
QueueNode* head; //头指针
QueueNode* tail; //尾指针
}Queue;
既然我们定义了尾指针,那我们的尾插就变得相当的简单了。
首先我们初始化head
头指针和tail
尾指针都是NULL
所以我们再插入数据的时候先得判断是否为空,如果是NULL
的话我们就要给head
和tail
赋值。接这就是往tail后面插入数据了。
插入的话我们得先malloc
一个空间,这个新空间节点的地址newNode
,接着就是tail->next=newNode
,然后把tail
给newNode
。
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
if (newNode != NULL)
{
newNode->data = x;
newNode->next = NULL;
if (pq->head == NULL)//判断依赖链表是否为空,如果是空那么赋值
{
pq->head = pq->tail = newNode;
}
else//链接
{
pq->tail->next = newNode;
pq->tail = newNode;
}
}
else
{
exit(-1);
}
}
这个bool
类型再【数据结构】栈中详细讲解了,有兴趣的小伙伴可以看一看。这里就不多讲了。
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
删除的话我们只要定义一个指针变量来记下头节点的下一个节点就可以进行一系列的操作了。
但是这里要注意的是我们还要判断数据是否被删除完了
,也就是判断链表是否为空。
而最重要的一点就是,我们把数据都删除了也就是head=NULL
的时候,我们还要进行手动的让tail=NULL
。
如果我们没有手动的让tail
置为NULL
的话这个时候tail就是一个野指针
了,虽然这个一时间不会出什么错误,但是在后续遍历数据的时候一旦遍历到了这个野指针,那么就是一个随机值,就会出错了。
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)//防止全部删完之后tail成为野指针
pq->tail = NULL;
}
我们知道我们队列的特点就是谁先进的谁先出来,所以我们是不能直接拿到中间位置的数据的
。
而我们是可以之直接拿到队头和队尾
的数据的,因为我们有头指针head
和尾指针tail
,直接返回这个节点的数据就行了。
这里同样的我们也要判断链表是否为空
。
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
这个和上面的拿到队头数据是一样的道理。
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
计算个数也是比较简单的,定义一个变量来当作计数器
,然后依次遍历链表。
int QueueSize(Queue* pq)
{
int n = 0;
QueueNode* cur = pq->head;
while (cur)
{
++n;
cur = cur->next;
}
return n;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur!=NULL)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->tail = pq->head = NULL;
}
#pragma once
#include
#include
#include
#include
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QueueNode;
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
//初始化
void QueueInit(Queue* pq);
//销毁空间
void QueueDestroy(Queue* pq);
//入队列(尾插)
void QueuePush(Queue* pq, QDataType x);
//出队列(头删)
void QueuePop(Queue* pq);
//找队头数据
QDataType QueueFront(Queue* pq);
//找队尾数据
QDataType QueueBack(Queue* pq);
//数据个数
int QueueSize(Queue* pq);
//判读数据是否为空
bool QueueEmpty(Queue* pq);
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
//初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
//销毁空间
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur!=NULL)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->tail = pq->head = NULL;
}
//入队列(尾插)
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
if (newNode != NULL)
{
newNode->data = x;
newNode->next = NULL;
if (pq->head == NULL)
{
pq->head = pq->tail = newNode;
}
else
{
pq->tail->next = newNode;
pq->tail = newNode;
}
}
else
{
exit(-1);
}
}
//出队列(头删)
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)//防止全部删完之后tail成为野指针
pq->tail = NULL;
}
//找队头数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
//找队尾数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
//数据个数
int QueueSize(Queue* pq)
{
int n = 0;
QueueNode* cur = pq->head;
while (cur)
{
++n;
cur = cur->next;
}
return n;
}
//判读数据是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void TextQueue1()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueueDestroy(&q);
}
void TextQueue2()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
QueueDestroy(&q);
}
void TextQueue3()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
printf("%d ", QueueFront(&q));
QueuePop(&q);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
QueueDestroy(&q);
}
int main()
{
//TextQueue1();
//TextQueue2();
TextQueue3();
return 0;
}