1.什么是队列
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
2.队列的实现
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
第一步:创建一个头文件和两个源文件
在头文件中进行结构定义和函数声明
定义链表的节点,包含一个数据域和一个指针域, 因为我们需要使用链表来实现队列
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType val;
}QNode;
以下是实现队列的方法,也声明在头文件中,我们传递一个头指针指向了这个链表,但是对于队列来说,一个头指针不是那么方便需要一直找尾所以我们定义一个头指针和一个尾指针,这样就很方便了,但是需要传二级指针,因为要想形参改变实参就需要传地址,下面的设计可以实现,但是不好,那么还有什么办法呢?
//队尾插入
oid QueuePush(QNode** pphead, QNode** pptail, QDataType x);
//队头删除
void QueuePop(QNode** pphead, QNode** pptail);
既然是多个值我们就在定义一个结构体,,结构里面一个指向头一个指向尾,这样我们传结构体的地址就可以改变实参了,这样就解决了我们传二级的问题,我们在加一个size用来记录队列的元素个数
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
//初始化
void QueueInit(Queue* pd);
//销毁
void QueueDestroy(Queue* pd);
//队尾插入
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);
第二步:队列接口的实现
初始化
void QueueInit(Queue* pd)
{
assert(pd);
pd->phead = NULL;
pd->ptail = NULL;
pd->size = 0;
}
销毁
void QueueDestroy(Queue* pd)
{
assert(pd);
QNode* cur = pd->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pd->phead = pd->ptail = NULL;
pd->size = 0;
}
我们需要把动态开辟的节点进行释放,不然会导致内存泄漏,释放完后让头指针和尾指针都置为NULL,规避野指针的出现, size也还原为0
队尾插入
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->next = NULL;
newnode->val = x;
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
传过来的地址不能为NULL如果为空我们就不能对他进行解引用,所以要加个断言,初始化的时候我们并没有开辟空间,竟然要插入数据那么就需要开辟空间,用一个临时变量保存,如果开辟失败直接提前结束程序,如果尾节点等于NULL说明没有节点,那我们让头节点和尾节点都指向我们刚刚开辟好的节点,最后size++
队头删除
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
//一个节点
if (pq->phead->next == NULL)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
//多个节点
else
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
既然要删除数据那么就必要要有数据所以加个断言判断一下,如果只有一个节点那么就直接进行释放然后置为空就行了,如果有多个节点就需要先把头节点的下一个节点的地址保存起来然后进行释放,最后size--
取队头的数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
return pq->phead->val;
}
取队尾的数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
取队头的值和队尾的值只要有值直接取即可,也是非常的简单
查看数据的个数
int Queuesize(Queue* pq)
{
assert(pq);
return pq->size;
}
元素的个数不就是size嘛直接返回即可
判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
size等于0不就说明是空嘛
第三步:测试test.c
#include "Queue.h"
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
printf("%d ", QueueFront(&q));
QueuePop(&q);
QueuePush(&q, 3);
QueuePush(&q, 4);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
QueueDestroy(&q);
return 0;
}
3.用队列实现栈
OJ链接:用队列实现栈
原始模版 :
typedef struct {
} MyStack;
MyStack* myStackCreate() {
}
void myStackPush(MyStack* obj, int x) {
}
int myStackPop(MyStack* obj) {
}
int myStackTop(MyStack* obj) {
}
bool myStackEmpty(MyStack* obj) {
}
void myStackFree(MyStack* obj) {
}
/**
* Your MyStack struct will be instantiated and called as such:
* MyStack* obj = myStackCreate();
* myStackPush(obj, x);
* int param_2 = myStackPop(obj);
* int param_3 = myStackTop(obj);
* bool param_4 = myStackEmpty(obj);
* myStackFree(obj);
*/
思路分析:首先这道题需要我们使用两个队列来完成栈的实现,而栈是后进先出,让两个队列来回导数据,我们保持一个存数据,一个为空,入数据入不为空的队列,如果需要出数据, 先让不为空的队列的前n-1个数据导入到为空的队列中, 然后在出数据, 此时正好就是最后一个数据, 也就是后进先出。
如下图举个例子:在q1和q2都为空的时候入数据入哪个都行,假设q2为空那就让它一直保持为空
现在我们出数据我们让q1的前n-1个数入到q2里面去然后把4给pop掉
以此内推,保持一个存数据,一个为空,入数据入不为空的队列,出数据通过空的导一下
现在我们开始写代码:
因为C语言没有自带的队列所以我们需要把写好的队列放进去,C++会有自带的队列。
MyStack里面需要两个队列所以我们定义两个队列,myStackCreate其实就是我们的初始化,这里不可以直接 MyStack pst, 因为函数里面创建的变量出了函数就销毁了,所以我们需要开辟一块空间,然后分别进行初始化
typedef struct
{
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack*pst = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&(pst->q1));
QueueInit(&(pst->q2));
return pst;
}
入队:哪个队列不为空就入哪个
void myStackPush(MyStack* obj, int x) {
if(!QueueEmpty(&(obj->q1)))
{
QueuePush(&(obj->q1),x);
}
else
{
QueuePush(&(obj->q2),x);
}
}
出队:我们用一个假设法,找到不为空的就开始导数据,把size-1导走后删除最后一个就是栈顶的数据
int myStackPop(MyStack* obj) {
//假设法
Queue*empty = &obj->q1;
Queue*noEmpty = &obj->q2;
if(!QueueEmpty(&(obj->q1)))
{
noEmpty = &(obj->q1);
empty = &(obj->q2);
}
//不为空前size-1导走,删除最后一个就是栈顶数据
while(Queuesize(noEmpty) > 1)
{
QueuePush(empty,QueueFront(noEmpty));
QueuePop(noEmpty);
}
int top = QueueFront(noEmpty);
QueuePop(noEmpty);
return top;
}
返回栈顶元素:哪个不为空哪个就是栈顶元素直接返回即可
int myStackTop(MyStack* obj) {
if(!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else
{
return QueueBack(&obj->q2);
}
}
判空:直接调用我们队列函数的判空即可
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&(obj->q1)) && QueueEmpty(&(obj->q2));
}
销毁:我们直接调用队列里面的销毁函数即可,最后把我们动态开辟的内存进行释放
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
全部代码如下:
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType val;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
//初始化
void QueueInit(Queue* pd);
//销毁
void QueueDestroy(Queue* pd);
//队尾插入
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);
//初始化
void QueueInit(Queue* pd)
{
assert(pd);
pd->phead = NULL;
pd->ptail = NULL;
pd->size = 0;
}
//销毁
void QueueDestroy(Queue* pd)
{
assert(pd);
QNode* cur = pd->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pd->phead = pd->ptail = NULL;
pd->size = 0;
}
//队尾插入
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->next = NULL;
newnode->val = x;
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
//队头删除
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
//一个节点
if (pq->phead->next == NULL)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
//多个节点
else
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
//取队头的数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->size != 0);
return pq->phead->val;
}
//取队尾的数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
//个数
int Queuesize(Queue* pq)
{
assert(pq);
return pq->size;
}
//判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
typedef struct
{
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack*pst = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&(pst->q1));
QueueInit(&(pst->q2));
return pst;
}
void myStackPush(MyStack* obj, int x) {
if(!QueueEmpty(&(obj->q1)))
{
QueuePush(&(obj->q1),x);
}
else
{
QueuePush(&(obj->q2),x);
}
}
int myStackPop(MyStack* obj) {
//假设法
Queue*empty = &obj->q1;
Queue*noEmpty = &obj->q2;
if(!QueueEmpty(&(obj->q1)))
{
noEmpty = &(obj->q1);
empty = &(obj->q2);
}
//不为空前size-1导走,删除最后一个就是栈顶数据
while(Queuesize(noEmpty) > 1)
{
QueuePush(empty,QueueFront(noEmpty));
QueuePop(noEmpty);
}
int top = QueueFront(noEmpty);
QueuePop(noEmpty);
return top;
}
int myStackTop(MyStack* obj) {
if(!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else
{
return QueueBack(&obj->q2);
}
}
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&(obj->q1)) && QueueEmpty(&(obj->q2));
}
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
/**
* Your MyStack struct will be instantiated and called as such:
* MyStack* obj = myStackCreate();
* myStackPush(obj, x);
* int param_2 = myStackPop(obj);
* int param_3 = myStackTop(obj);
* bool param_4 = myStackEmpty(obj);
* myStackFree(obj);
*/
总结
队列(Queue)是一种线性数据结构,其特殊之处在于它遵循先进先出(FIFO)的原则。队列的主要操作包括在队列的一端插入元素(称为入队,enqueue)和在另一端移除元素(称为出队,dequeue)。由于这种操作限制,队列在访问和操作元素时表现出一种特殊的顺序性。
队列的主要特点包括:
-
先进先出(FIFO):队列中的数据元素按照它们进入队列的顺序进行排列。最早进入队列的元素将最早被移除,而最新进入队列的元素将最后被移除。
-
操作受限:队列只允许在特定的两端进行操作。一端用于插入元素(队尾),另一端用于移除元素(队头)。这种限制确保了队列的先进先出特性。
-
应用广泛:队列在计算机科学和软件工程中有广泛的应用。它们常用于实现各种算法,如广度优先搜索(BFS),并在多种场景中发挥着关键作用,如任务调度(操作系统中的任务队列)、消息传递(在并发编程和网络通信中)、事件处理(如用户界面的点击事件队列)等。
队列的实现方式有多种,包括基于数组的循环队列和基于链表的队列等。这些实现方式各有优缺点,具体选择取决于应用场景和性能需求。在实际应用中,队列的使用通常需要与其他数据结构和算法相结合,以实现复杂的程序逻辑和功能。