队列
1.1队列的概念及结构
只允许在一端进行插入插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的特点。
入队列:进行插入操作的一端称为队尾。
出队列:进行删除操作的一端称为队头。
说白了队列就是从队尾入数据,从队头出数据,切要保证先进先出的特点。
1.2 队列的实现
队列也是可以使用数组和链表的结构实现的,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低,(因为当你从头部出数据的时候,需要把后面的数据往前挪动,那么空间复杂度就是O(N),很不方便)但是链表就会好很多,因为队头出数据的时候首先把下一个数据的地址保存下来让其做头就好了。
队列在什么时候能用到呢?
比如说就是先入先出的场景,银行的排队取票服务项目。我要保证先取到票的人先被服务
1.3队列的完整代码
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int QDateType;
typedef struct QueueNode
{
struct QueueNode* _next;
QDateType _date;
}QueueNode;
//队尾入数据,队头出数据
typedef struct Queue
{
QueueNode* _head;
QueueNode* _tail;
}Queue;
void QueueInit(Queue* pq);
void QueueDestory(Queue* pq);
void QueuePush(Queue* pq, QDateType x);
void QueuePop(Queue* pq);
QDateType QueueFront(Queue* pq);
QDateType QueueBack(Queue* pq);
//返回1是空,返回0是非空
int QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);
#include"Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->_head = pq->_tail = NULL;
}
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->_head;
while (cur)
{
QueueNode* next = cur->_next;
free(cur);
cur = next;
}
pq->_head = pq->_tail = NULL;
}
void QueuePush(Queue* pq, QDateType x)
{
assert(pq);
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
if (newNode == NULL)
{
printf("内存不足\n");
exit(-1);
}
newNode->_date = x;
newNode->_next = NULL;
//这个队列一开始就是空的情况
if (pq->_head == NULL) // 因为你初始化的时候头和尾都是NULL,当你的头尾NULL的时候也说明了你的尾也是NULL
{
pq->_head = pq->_tail = newNode;
}
else
{
pq->_tail->_next = newNode;
pq->_tail = newNode;
}
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->_head);
QueueNode* next = pq->_head->_next;
free(pq->_head);
pq->_head = next;
//这就是把所有都给删完了,此时你要是不管这个尾,他就会成为野指针
if (pq->_head == NULL)
{
pq->_tail == NULL;
}
}
QDateType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->_head);//确保队头是有效的,不为空,否则就崩了
return pq->_head->_date;
}
QDateType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->_tail); //确保队尾是有效的,不为空
return pq->_tail->_date;
}
//返回1是空,返回0是非空
int QueueEmpty(Queue* pq)
{
assert(pq);
return pq->_head == NULL ? 1 : 0;
}
int QueueSize(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->_head;
int size = 0;
while (cur)
{
size++;
cur = cur->_next;
}
return size;
}
#include"Queue.h"
void TestQueue()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
QueueDestory(&q);
}
int main()
{
TestQueue();
return 0;
}
1.4 LeetCode第225题—用队列实现栈
链接: link.
用C语言来写,首先你得定义一个队列。
思路:先定义两个队列,这两个队列其中一个是存放数据的,还有一个队列是空的,当你要想实现一个栈的功能的时候,你需要把队列先进入的数据都移到那个空的队列中去,然后再把最后一个入队列的元素pop出来,这样就可以简单的实现一个栈的功能。(你要是想要继续入队列添加元素,需要在有元素的队列进行操作,也就是说,不管如何都要保证,一个队列存储着元素,另一个队列为空,为的就是移元素)
始终保持一个队列里面有数据,一个队列为空且那个有数据的队列把前面的数据往空的队列里面移动,不断重复
typedef int QDateType;
typedef struct QueueNode
{
struct QueueNode* _next;
QDateType _date;
}QueueNode;
//队尾入数据,队头出数据
typedef struct Queue
{
QueueNode* _head;
QueueNode* _tail;
}Queue;
void QueueInit(Queue* pq);
void QueueDestory(Queue* pq);
void QueuePush(Queue* pq, QDateType x);
void QueuePop(Queue* pq);
QDateType QueueFront(Queue* pq);
QDateType QueueBack(Queue* pq);
//返回1是空,返回0是非空
int QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);
void QueueInit(Queue* pq)
{
assert(pq);
pq->_head = pq->_tail = NULL;
}
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->_head;
while (cur)
{
QueueNode* next = cur->_next;
free(cur);
cur = next;
}
pq->_head = pq->_tail = NULL;
}
void QueuePush(Queue* pq, QDateType x)
{
assert(pq);
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
if (newNode == NULL)
{
printf("内存不足\n");
exit(-1);
}
newNode->_date = x;
newNode->_next = NULL;
//这个队列一开始就是空的情况
if (pq->_head == NULL) // 因为你初始化的时候头和尾都是NULL,当你的头尾NULL的时候也说明了你的尾也是NULL
{
pq->_head = pq->_tail = newNode;
}
else
{
pq->_tail->_next = newNode;
pq->_tail = newNode;
}
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->_head);
QueueNode* next = pq->_head->_next;
free(pq->_head);
pq->_head = next;
//这就是把所有都给删完了,此时你要是不管这个尾,他就会成为野指针
if (pq->_head == NULL)
{
pq->_tail == NULL;
}
}
QDateType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->_head);//确保队头是有效的,不为空
return pq->_head->_date;
}
QDateType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->_tail); //确保队尾是有效的,不为空
return pq->_tail->_date;
}
//返回1是空,返回0是非空
int QueueEmpty(Queue* pq)
{
assert(pq);
return pq->_head == NULL ? 1 : 0;
}
int QueueSize(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->_head;
int size = 0;
while (cur)
{
size++;
cur = cur->_next;
}
return size;
}
//首先创建两个队列
typedef struct {
Queue _q1;
Queue _q2;
} MyStack;
/** Initialize your data structure here. */
MyStack* myStackCreate() {
MyStack* st = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&st->_q1);
QueueInit(&st->_q2);
return st;
}
//入数据:我需要入到那个队列不为空的里面,但是我并不知道那个队列是不为空的,又或许一开始的时候两个队列都为空,都还没有任何的元素入队列呢?
//当q1和q2都为空的时候,我把把这个数据添加在任何一个队列中都是可以的
/** Push element x onto stack. */
void myStackPush(MyStack* obj, int x) {
//如果队列q1不是空的,说明他的里面有数据,那么我就直接把数据入到他的里面,否则就如到q2中
if(!QueueEmpty(&obj->_q1))
{
QueuePush(&obj->_q1,x);
}
else
{
QueuePush(&obj->_q2,x);
}
}
/** Removes the element on top of the stack and returns that element. */
int myStackPop(MyStack* obj) {
//放心大胆的用,如果你这里是空的,他压根就不会调用pop这个解口
//如果q1不为空,你需要把数据移动到q2上,然后把最后一个元素pop掉
//一上来我就假设q1是一个空的,q2是非空的,但是谁能保证我的假设对呢?所以还需要添加条件验证
Queue* empty = &obj->_q1;
Queue* nonEmpty = &obj->_q2;
if(QueueEmpty(&obj->_q2))
{
empty = &obj->_q2;
nonEmpty = &obj->_q1;
}
//走到这里一定可以确定一个队列是空的,一个队列是非空的
//保证非空的队列只留下最后一个数据
while(QueueSize(nonEmpty)>1)
{
QueuePush(empty,QueueFront(nonEmpty));
QueuePop(nonEmpty);
}
int top = QueueFront(nonEmpty);
QueuePop(nonEmpty);
return top;//移除掉栈顶元素,并且返回这个栈顶的元素值
}
//获取栈顶的数据并不需要把它pop一下,只需要你的尾的next的date显示
//题目告诉你,如果这个队列时空的他不会调用pop和top的操作
/** Get the top element. */
//队尾的数据就是出栈的时候栈顶
int myStackTop(MyStack* obj) {
if(!QueueEmpty(&obj->_q1))
return QueueBack(&obj->_q1);
else
return QueueBack(&obj->_q2);
}
//这样就对起来了,也是Mystack如果为空就返回1,如果不为空就返回0
/** Returns whether the stack is empty. */
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&obj->_q1) && QueueEmpty(&obj->_q2);
}
void myStackFree(MyStack* obj) {
//MyStack是你malloc出来的,要记得释放
QueueDestory(&obj->_q1);
QueueDestory(&obj->_q2);
free(obj);
}
1.5 LeetCode第232题—用栈实现队列
栈相关知识的:链接: link.
LeetCode题:链接: link.
思路:定义两个栈,其中一个栈是用来入栈数据的,还有一个栈是空的,当你把这个有数据的栈都Pop到空的栈里面的时候,你就会发现,此时这个空的栈里面的数据在进行栈的Pop操作就实现了队列的性质,最先进去的数据最先出来,就实现了栈来实现队列的要求。
typedef int STDateType;
typedef struct Stack
{
STDateType* _a;
int _top; //栈顶下标
int _capacity;
}Stack;
//初始化和销毁
void StackInit(Stack* pst);
void StackDestory(Stack* pst);
//入栈
void StackPush(Stack* pst, STDateType x);
//出栈
void StackPop(Stack* pst);
//获取数据个数
int StackSize(Stack* pst);
//返回1是空,返回0是非空
int StackEmpty(Stack* pst);
//获取栈顶的数据
STDateType StackTop(Stack* pst);
//初始化
void StackInit(Stack* pst)
{
assert(pst);
//这种方式是有不好的地方的,因为但当你需要增容的时候,你就会发现,他的capacity初始化是0,那么你乘2依旧是0,所以建议一开始就给一个固定值
//pst->_a = NULL;
//pst->_top = 0;
//pst->_capacity = 0;
pst->_a = (STDateType*)malloc(sizeof(STDateType)*4);
pst->_top = 0;
pst->_capacity = 4;
}
//销毁
void StackDestory(Stack* pst)
{
assert(pst);
free(pst->_a);
pst->_a = NULL;
pst->_top = pst->_capacity = 0;
}
//入栈
void StackPush(Stack* pst, STDateType x)
{
assert(pst);
//空间不够则增容
if (pst->_top == pst->_capacity)
{
pst->_capacity *= 2;
STDateType* tmp = (STDateType*)realloc(pst->_a, sizeof(STDateType)*pst->_capacity);
if (tmp == NULL)
{
printf("内存不足\n");
exit(-1);
}
else
{
pst->_a = tmp;
}
}
pst->_a[pst->_top] = x;//你所定义的栈顶总是在你放入数据的下一个位置
pst->_top++;
}
//出栈
void StackPop(Stack* pst)
{
assert(pst);
assert(pst->_top > 0);
--pst->_top;
}
//获取数据个数
int StackSize(Stack* pst)
{
assert(pst);
return pst->_top;
}
//返回1是空,返回0是非空
int StackEmpty(Stack* pst)
{
assert(pst);
return pst->_top == 0 ? 1 : 0;
}
//获取栈顶的数据
STDateType StackTop(Stack* pst)
{
assert(pst);
assert(pst->_top > 0);
return pst->_a[pst->_top - 1];//你所定义的栈顶总是在你放入数据的下一个位置
}
//这道题是一个接口型的题目,就是你写好这些接口他去调用来实现一系列的要求
//你的数据不管怎么样都是从pushST里面进入,出栈都是从popST里面出
//首先定义两个栈
typedef struct {
Stack _pushST;
Stack _popST;
} MyQueue;
/** Initialize your data structure here. */
MyQueue* myQueueCreate() {
MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&q->_pushST);
StackInit(&q->_popST);
return q;
}
//始终都是从pushST进栈数据
/** Push element x to the back of queue. */
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->_pushST,x);
}
//移除并返回这个队列的头的数据值
/** Removes the element from in front of queue and returns that element. */
int myQueuePop(MyQueue* obj) {
//这里很巧妙的使用了下面的myQueuePeek这个函数,所以在写的时候是可以考虑先写这个函数的,这样就避免了写重复的代码。
int front = myQueuePeek(obj);
StackPop(&obj->_popST);
return front;
}
/** Get the front element. */
int myQueuePeek(MyQueue* obj) {
//如果此时你的popST里面是有数据情况,此时在popST直接就可以得到队列的第一个数据
if(!StackEmpty(&obj->_popST))
{
return StackTop(&obj->_popST);
}
else
{
//当你的数据都还在pushST里面,popST里面还没有数据的时候
//倒数据
while(!StackEmpty(&obj->_pushST))
{
StackPush(&obj->_popST,StackTop(&obj->_pushST));
StackPop(&obj->_pushST);
}
return StackTop(&obj->_popST);
}
}
/** Returns whether the queue is empty. */
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->_pushST) && StackEmpty(&obj->_popST);
}
//你会发现对于OJ来说,你不对其进行释放也是不会报错的,因为他是检查不出来的,但是对于有一个好的代码,只要你进行了malloc,realloc等操作就要进行free,因为这样才能够避免内存泄漏的事情,不养成好的习惯,后续检查起来就会大大的增加难度。
void myQueueFree(MyQueue* obj) {
//释放掉你所开辟的两个栈
StackDestory(&obj->_pushST);
StackDestory(&obj->_popST);
//最后在释放obj这个指针
free(obj);
}
不管是用栈来实现队列还是用队列来实现栈,基本的思路都是一样的,一开始定义两个栈或者两个队列,然后进行一系列的操作,就是感觉这种写接口型的题目首次开始接触,还有些不适应。
1.6 LeetCode第622题—循环队列的实现
链接: link.
思路:循环队列中所开的空间大小是固定的,从creat哪里就能看出来,一共只有K个空间的大小,当你的数据满的时候,他需要把最开始的结点里面的数据干掉,然后换上新的数据(可不是干掉结点,只是干掉原结点里面的数据)。
错误设计方法:如果你所开辟的数组的front和rear在同一个位置,两个之间并没有预留出一个空的位置。
此时就会产生歧义?到底front和rear在同一个位置的时候这个数组是满的还是空的?
所以设计这个数组的时候必须保留一个空位置!(并不是一定要空最尾上的位置,这个空的位置是可以一直改变的)
在逻辑上的结构是:
//之所以使用数组是因为好判别是否为空为满的情况,可以用表达式来计算
//这里之所以要设置上k,是因为在myCircularQueueCreate中所使用的k是一个局部变量,出了这个局部变量是拿不到的,所以也必须传入结构体中
typedef struct {
int* _a;
int _front;
int _rear;
int _k;
} MyCircularQueue;
//队列实际的个数在这里已经是固定的了,你要求存放k个数据,那么你就要创造k+1个空间的大小,当你的rear+1 = front的时候就可以说明此时的队列已经满了
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* q = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
q->_a =(int*)malloc(sizeof(int)*(k+1));//多开一个空间
q->_front = 0;
q->_rear = 0;
q->_k = k;
return q;
}
//这里我要使用它所定义的两个函数,但是没有头文件声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//向队列插入一个数据,如果成功则返回真
//如果此时这个循环链表已经满了,在插入数据就报错了
if(myCircularQueueIsFull(obj))
return false;
//要清楚你入数据总是在rear下角标的位置上面插入数据
obj->_a[obj->_rear] = value;
obj->_rear++;
//但是有可能你此时的rear已经走到了你所开辟的最后一个空间的位置处,你在++此时就会越过数组
obj->_rear %= (obj->_k+1);//即使你现在rear并不是最后一个位置,你%完以后,是不会有影响的
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//如果此时这个循环队列都是空了,你在出数据就会报错
if(myCircularQueueIsEmpty(obj))
return false;
++obj->_front;//这里你删不删除front位置的元素都不要紧,但是也有可能front加着越过数组的长度
obj->_front %= (obj->_k+1);
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
else
return obj->_a[obj->_front];
}
//这里的rear有一种情况是比较坑的,在写的时候要考虑到
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
//这里有一种特殊的rear情况需要考虑进去
int tail = obj->_rear-1;
//如果此时你的rear刚好在数组下角标为0的位置,说明上一个位置是在数组的最后一个下角标的位置
if(tail == -1)
tail = obj->_k;
return obj->_a[tail];
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->_front == obj->_rear; //相等就是真返回的是1
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->_rear + 1) % (obj->_k+1) == obj->_front;
}
void myCircularQueueFree(MyCircularQueue* obj) {
//先释放最里面一层的,在释放最外面的
free(obj->_a);
free(obj);
}