队列是什么
- 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表
- 队列具有先进先出 FIFO(First In First Out)
- 入队列:进行插入操作的一端称为队尾
- 出队列:进行删除操作的一端称为队头
队列的数据结构可以想象成我们的食堂吃饭排队,先排的先走
与栈一样队列的结构是规定死的,即插入必须在队尾插入,删除只能在队头,必须满足先进先出原则
队列的实现
- 数组队列
- 链表队列
我们用数组实现的时候,尾插和头删都会遇到一个难点,即需要挪动数据,时间复杂度O(N)
而我们的链表实现不需要,在已知尾结点地址情况下,时间复杂度是O(1)
定义结构体
我们定义结构体,一想就是
typedef int datatype;
typedef struct Qnode
{
struct Qnode* next;
datatype data;
}Qnode;
但为了方便我们的尾插和头删,我们要定义2个结构体指针指向尾和头
也就是说,在初始化的时候,我们要定义这2个指针,并且每一次插入删除,都要手动使头尾指针改变
尤其这样,我们不如再定义一个结构体
typedef int datatype;
typedef struct Qnode
{
struct Qnode* next;
datatype data;
}Qnode;
typedef struct Queue
{
Qnode* head;
Qnode* tail;
int size;
}Queue;
我们要不要将2个结构体合二为一?
不需要。
- 我们的QNode是定义的结点的结构,即我们的整个链表
- Queue是定义的指向链表中头尾指针的结构
我们的每一个结点需要有头尾指针和size吗?不需要!
都是各司其职的,合二为一反而加大维护成本
void test1()
{
Queue q;
queueinit(&q);
queuedestroy(&q);
}
我们这样写就不需要用二级指针了,因为我们是要改变结构体,只需要传结构体指针即可,即一级指针
初始化函数
void queueinit(Queue* pst)
{
assert(pst);
pst->head = NULL;
pst->tail = NULL;
pst->size = 0;
}
这里,我们清楚我们定义的结构体Qnode是帮助我们创建链表结点的,我们的结构体Queue包含整个链表的头尾指针,所以我们将定义链表的头指针返回给Queue来保存,也就是说我们是操作多半是针对Queue的
free函数
void queuedestroy(Queue* pst)
{
assert(pst);
Qnode* cur = pst->head;
while (cur)
{
Qnode* next = cur->next;
free(cur);
cur = next;
}
pst->head = pst->tail = NULL;
pst->size = 0;
}
free你malloc出来的东西
值得注意的是,free完后,要给pst->head pst->tail置空,并且让size=0,不让就是野指针
判空函数
bool queueempty(Queue* pst)
{
assert(pst);
// return pst->size == 0;
return pst->head == NULL && pst->tail == NULL;
}
我们可以用2个比较操作符来用于返回值,在栈的章节有讲
入队列
void queuepush(Queue* pst, datatype x)
{
assert(pst);
Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));
if (!newnode)
{
perror("malloc");
return;
}
newnode->next = NULL;
newnode->data = x;
if (!queueempty(pst))
{
pst->tail->next = newnode;
pst->tail = newnode;
}
else
{
pst->head = pst->tail = newnode;
}
pst->size++;
}
Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));
注意这里的malloc的类型是Qnode不是其他的类型,要知道自己想要malloc什么结点出来
尾插后,要改变头尾指针和size
出队列
void queuepop(Queue* pst)
{
assert(pst);
assert(!queueempty(pst));
if (!pst->head->next)
{
free(pst->head);
pst->head = pst->tail = NULL;
}
else
{
Qnode* next = pst->head->next;
free(pst->head);
pst->head = next;
}
pst->size--;
}
我们要考虑只有一个结点和多个结点的情况
很多人认为单纯这样即可
Qnode* next = pst->head->next; free(pst->head); pst->head = next;
那请问,
- 队列为空的情况下呢,
- 只有一个结点的情况下呢,多个呢?
- size需不需要–?
- 一个结点时,我们想一想,头尾指针是不是都是指向同一个结点的,如果我们单纯将头指针free掉,那尾指针是不是就是野指针了
- 多结点时,上面代码可行
- 没有结点时,需要我们assert判空
- 最后别忘了将size–。
为了避免想当然,我们在写代码的时候应该多画图多思考
取队头元素
datatype queuefront(Queue* pst)
{
assert(pst);
assert(!queueempty(pst));
return pst->head->data;
}
唯一值得注意的是,要判空,不然没有队列为空还在访问
取队尾元素
datatype queueback(Queue* pst)
{
assert(pst);
assert(!queueempty(pst));
return pst->tail->data;
}
在我们官方的文档下面,是有取队尾元素的操作的
获取元素个数
与我们栈一样,我们也需要用一个函数来获取
datatype queuesize(Queue* pst)
{
assert(pst);
return pst->size;
}