数据结构——队列

好了,前面我们已经了解了栈的实现后,现在让我们来了解一下与它相关的队列的实现吧!

队列的概念及结构

队列:只允许在一端进行插入数据操作 ,在另一端进行删除数据操作的特殊线性表,

队列具有先进先出FIFO(First In First Out)

**入队列:**进行插入操作的一端称为队尾

出队列:进行删除操作的一端称为队头

它的结构图:

现在,我们先来与栈进行对比区别:

我们浅浅来回顾一下栈的内容

1.栈是一种后进先出的数据结构,队列是先进先出的

2.栈只能在栈顶进行输入的插入和删除操作

3.栈是有入栈和出栈操作,出栈就是从栈中删除一个元素

4.顺序表和链表都可以用来实现栈,不过一般都使用顺序表,因为栈想当于是阉割版的顺序表,只用到了顺序表的尾插和尾删操作,顺序表的尾插和尾删不需要搬移元素效率非常高,故一般都是使用顺序表实现

如果是链栈,一般需要进行头插或者头删操作,而顺序栈一般进行尾插和尾删操作,链表的操作比顺序表复杂,因此使用顺序结构实现栈更简单

至此,那么在队列中更适合用那种形式呢?

队列适合使用链表实现,使用顺序结构(即固定的连续空间)实现时会出现假溢出的问题,因此大佬们设计出了循环队列,循环队列就是为了解决顺序结构实现队列假溢出问题的(这个后面会写)

为了解决后面的疑惑,我们先来分清下面的问题

有人会问, 为什么之前写的链表和栈有时候用指针,有时候用结构体?

答:单链表用指针:不管是带哨兵位还是不带,总是有一个指针指向第一个节点。要么指向头节点,要么某一个节点

而栈用结构体:如果也搞成一个栈的指针可不可以?没必要。

如果搞成那样,就会出现恶心的事情:这个栈的init得去malloc

如果给你一个指针,你去malloc怎么办,init这里是不是就得用二级指针(麻烦)

把结构体malloc出来。那现在为啥不要呢?这本身就是一个结构体,不需要malloc了

所以,你要改变结构体,你就用结构体的指针就够了啊。

但是,如果你想外面定义一个指针,里面再去malloc也是可以的,要么用二级指针/返回值

在栈初始化那里,malloc后给给ps->a。,那么有人又问了,为啥这里又没有用二级呢?因为

在这个地方,改变的是结构体的成员,要改变结构体,结构体的指针够不够?之前说过要改变int,用int*。里面传指针,必须要有解引用。回到那里,你是不是要改变结构体,那ps是结构体的指针,结构体的指针,那么这个->是不是就相当于解引用这种东西

好了,有了上面的了解,我们开始下一步了。

现在我们来实现一下队列。

首先,我们明白:队列是先进先出

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低

即下面图:

首先,我们来定好框架:

typedef char QDatatype;

链式结构:表示队列
typedef struct QueueNode
{
	struct QueueNode* next;
	QDatatype data;
}QNode;

队列的结构
typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

解释:为了更加方面,我们采用的是两个结构体的方式, 目的是可以让队列可以在一端进行插入数据,在另一端进行删除,所以我们再定义一个结构,里面含有两个指针,指向队列的头和尾

初始化部分

void QueueInit(Queue* pq)
{
	assert(pq);

	pq->head = pq->tail = NULL;
	pq->size = 0;
}

这里想一想为什么要断言?

因为队列可以为空,但是指向头指针和尾指针的结构体不能为空,所以一开始就要断言

然后把头尾指针置空,size为0.即完成了初始化啦。

接着,我们有了初始化,必定会有销毁的函数,

销毁部分

void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pq->head = pq->tail = NULL;
	pq->size = 0;
}

解释:

1.同样,不能为空,我们得断言。

2.如下图:依次释放,最后再把head和tail置为空,size置0.

插入部分

void QueuePush(Queue* pq, QDatatype x)
{
    创建新节点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
    放数据
	newnode->data = x;
	newnode->next = NULL;

	if (pq->head == NULL)
	{
		assert(pq->tail == NULL);

		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}

	pq->size++;
}

解释:

因为队列(先入先出)只能尾那边插。

1.创建新节点,即malloc一个。

2.过程

这里一开始就空的话,得断言一下此时的tail也是和head一样都是空的。

3.最后,你插入了一个数据,size的大小必然也是变的,得更新一下。

删除部分

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);

	
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}

	pq->size--;
}

1.队列中的删除部分在头那边删

2.指向头指针和尾指针的结构体不能为空,一开始同样断言。

3.此外,你要删除,一开始肯定是得有数据的,不然也没意义删了,因此也要保证你的头指针不能为空。也要断言。

那么,怎么判断只有一个数据的时候呢?

答:即判断head的下一个是否为空

4.多个数据的时候,先定义一个next指针,指向head的下一个,再释放就可以了。

5.最后,再更新size--就完成了。

得出size大小

int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

判断是否空

bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;
}

返回头数据

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;
}

由于这几个部分都比较简单,就不再一一解释了。

最后,附上总代码。

Queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"

void QInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QDestory(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq, QDatatype x)
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	if (pq->head == NULL)
	{
		assert(pq->tail == NULL);
		pq->head = pq->tail = newnode;

	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;

}
void QueuePop(Queue* pq)
{
	assert(pq);
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}

int Size(Queue* pq)
{
	assert(pq);
	return pq->size;
}
bool QEmpty(Queue* pq)
{
	assert(pq);
	return pq->size==0;
}

QDatatype QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QEmpty(pq));
	return pq->head->data;
}
QDatatype QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QEmpty(pq));
	return pq->tail->data;
}

Queue.h

#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
typedef char QDatatype;
typedef struct Queuenode
{
	struct Queuenode* next;
	QDatatype data;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

void QInit(Queue* pq);
void QDestory(Queue* pq);
void QueuePush(Queue* pq,QDatatype x);
void QueuePop(Queue* pq);
int Size(Queue* pq);
bool QEmpty(Queue* pq);
QDatatype QueueFront(Queue* pq);
QDatatype QueueBack(Queue* pq);

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"

int main()
{
	Queue st;
	QInit(&st);
	QueuePush(&st,1);
	QueuePush(&st, 2);
	QueuePush(&st, 3);
	QueuePush(&st, 4);
	QueuePush(&st, 5);
	QueuePush(&st, 6);



	while (!QEmpty(&st))
	{
		printf("%d ", QueueFront(&st));
		QueuePop(&st);
	}
		QDestory(&st);
	return 0;
}

最后,到了本次鸡汤环节:

孤独让你强大,让你成为更好的人!

相关推荐
圣道寺32 分钟前
审计文件标识作为水印打印在pdf页面边角
java·前端·python·pdf·学习方法
USER_A0011 小时前
CSS笔记基础篇02——浮动、标准流、定位、CSS精灵、字体图标
笔记·css3
Petrichorzncu1 小时前
算法刷题笔记——图论篇
笔记·算法·图论
LuckyLay1 小时前
Golang学习笔记_28——工厂方法模式(实例)
笔记·学习·golang·工厂方法模式
王老师青少年编程2 小时前
gesp(C++五级)(12)洛谷:B4051:[GESP202409 五级] 小杨的武器
开发语言·数据结构·c++·算法·gesp·csp
喜-喜2 小时前
Python学习之旅:入门阶段(七)数据结构
数据结构·python·学习
安冬的码畜日常2 小时前
【Vim Masterclass 笔记21】S09L39:Vim 设置与 vimrc 文件的用法示例(二)
笔记·vim·自学笔记·vimrc·vim定制·vim快捷键
轩源源2 小时前
数据结构——AVL树的实现
开发语言·数据结构·c++·avl树·函数模板·双旋·单旋
Camel卡蒙2 小时前
数据结构——堆(介绍,堆的基本操作、堆排序)
数据结构·算法
东京老树根3 小时前
Excel 技巧12 - 如何在Excel中输入对号叉号(★),字体Wingdings2
笔记·学习·excel