目录
1>>导言
在把栈学习完后,步入新的章节------队列,队列是一种特殊的线性表,队列是啥意思呢?栈表示先进后出,入栈出栈操作,类似一个水杯从上边喝水,那么队列是一个先进先出,入列出列的操作,类似生活中的排队,也类似被司马光砸破的水缸,先进来的水总是先出去,这便是队列。在表的后端进行插入操作。进行插入操作的端称为队尾 **,进行删除操作的端称为队头。**到这同学们应该是听懂了一部分,那么队列的底层结构用什么更为合适呢?我们一起来看看,假设用数组,那很显然,总会存在一个时间复杂度为O(N)的(自己写的函数),因为毕竟是有n个数。
比如这里,1号是队头,先进先出,排队出去。底层结构就用单链表来实现更简单,入列就是尾插,出列就是头删,取头结点元素。这里请大家思考:单链表就一个头结点,尾插到第n个数据,那么时间复杂度为O(N),有什么办法将它降为O(1)吗?在结构中给出答案:
因此我们就来看看队列的结构
2>>队列的结构
注意这里还是和之前的一样包含3个文件,包括:queueI(队列头文件、queuel、(队列尾部文件,test(测试文件)
队列的结构如下
data跟之前一样,在更改数据类型时可以统一修改,它的结构也是跟单链表无太大差异,唯一有所不同的是:我们要再写一个结构体,这个结构体用来存放头结点和尾结点。还有数据个数,这样可以使得时间复杂度降为O(1);
虽然我们多写了一个结构体,但是让时间复杂度从O(N)降为了O(1),因此在导言中给出的思考就解决了。
接下来根据下面图片的每一个声明做出相应的解释。
3>>初始化
初始化的时候,当然进来要先判断pq是否是空指针。需要把结构体内部的phead和ptail,头结点和尾结点置为空指针,并且让表示结点(人头数)个数的size置为0;在test文件创建完q并返回q的地址,进行初始化。
4>>打印
打印的代码也是一样,进来需要先判断pq是否是空指针,然后建立一个结构体指针,pcur,pcur为pq指向的下一个地址,
在这张图中也就是2;最后当pcur为空指针时,跳出循环。
5>>入列
入列进来也需要判断是否时空指针,为空则pq为假,NULL是0,assert进行断言报错,不为空则进入入列代码,首先:创建一个新结点,新节点类型为结构体指针,这里都跟单链表尾插是一样的,然后使用malloc开辟一个空间,大小为结构体指针的大小。当内存满时,会返回一个空指针,因此我们需要进行判断,为空则打印错误信息并退出,不为空则对新节点初始化------next置为空,val=x;至此为止,新节点创建完毕。
开始进行尾插,也就是入列
这里需要判断是否是第一个结点,如果是,则直接将头和尾直接置为第一个结点的地址,如果不是,则将尾结点的下一个地址改为新节点也就是newnode,然后原来的尾结点更新为新的尾结点。
最后将结点(人头数)加1即可。
6>>出列
在出列的代码里,assert还需要判断是否有结点可以删除,否则会对空指针解引用
6.1>>判断是否为空
所以这里就需要再写一个判断是否为空,如果头结点是空指针就为空,返回true。
那么出列代码的assert要对结果取反,不为空的时候返回false,然后取反为true,这样才能继续执行下面的代码。这里也需要判断一个结点和多个结点的情况,一个结点时,需要把头结点和尾结点置为空指针,因为就他一个能删除,多个结点时,提前存储下一个结点的地址 ,在对pq指向的phead进行释放,然后phead等于下一个结点地址即可完成出列。最后别忘了将结点成员数减一。
7>>取队头和队尾数据and统计个数
这三个比较简单,就放在一起啦,宝子们一起看看就好。
8>>队列销毁
最后是队列的一个销毁,还是和打印差不多,都需要提前存好下一个结点的地址,然后一个一个的释放,最后将头结点和尾结点都置为空指针,然后将size置为0.
9>>三个文件代码
queue.h
cpp
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<stdbool.h>
typedef int data;
typedef struct queuenode {
data val;
struct queuenode* next;
}queuenode;
typedef struct queue {
queuenode* phead;
queuenode* ptail;
int size;
}queue;
void qinit(queue*pq);//初始化
void qpirnt(queue* pq);//打印
void qpush(queue* pq, data x);//入列
void qpop(queue* pq);//出列
data qfront(queue* pq);//取队头数据
data qback(queue* pq);//取队尾数据
bool qempty(queue* pq);//判断队列是否为空
int qsize(queue* pq);//统计队列个数
void qdestroy(queue* pq);//队列的销毁
queue.c
cpp
#include"queue.h"
void qinit(queue* pq) {
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
void qpirnt(queue* pq) {
assert(pq);
queuenode* pcur;
pcur = pq->phead;
while (pcur) {
printf("%d -> ", pcur->val);
pcur = pcur->next;
}
printf("\n");
}
void qpush(queue* pq,data x) {
assert(pq);
queuenode* newnode = (queuenode*)malloc(sizeof(queuenode));
if (newnode == NULL) {
perror("malloc");
exit(1);
}
newnode->next = NULL;
newnode->val = x;
if (pq->phead == NULL) {
pq->phead = pq->ptail = newnode;
}
else {
pq->ptail->next=newnode;
pq->ptail = pq->ptail->next;
}
pq->size++;
}
void qpop(queue* pq) {
assert(pq);
assert(!qempty(pq));
if (pq->phead==pq->ptail)
pq->phead = pq->ptail = NULL;
else
{
queuenode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
data qfront(queue* pq) {
assert(pq);
assert(!qempty(pq));
return pq->phead->val;
}
data qback(queue* pq) {
assert(pq);
assert(!qempty(pq));
return pq->ptail->val;
}
bool qempty(queue* pq) {
assert(pq);
return pq->phead == NULL;
}
int qsize(queue* pq) {
assert(pq);
return pq->size;
}
void qdestroy(queue* pq) {
assert(pq);
queuenode* pcur = pq->phead;
while (pcur) {
queuenode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
test.c
cpp
#include"queue.h"
void test() {
queue q;
qinit(&q);
qpush(&q,1);
qpush(&q, 2);
qpush(&q, 3);
qpush(&q, 4);
qpirnt(&q);
printf("%d\n", qsize(&q));
qpop(&q);
printf("%d\n", qsize(&q));
}
int main() {
test();
return 0;
}
10>>总结
今天带着宝子们学习了队列的知识点,包括入列,出列等等队列的操作,希望宝子们在这篇文章能学会使用队列,也希望宝子们数据结构越学越好,无瓶颈!谢谢宝子们观看,期待下一篇与你相见!