目录
前言💬
在线性表中的顺序表、链表、栈等已经完成了;本篇博客也就是线性表的最后一个内容----队列;队列的操作也是与栈一致,十分简单;然而,队列因为牵扯到尾插和头删;因此在此选用链表来实现是比较简单高效的!
1.队列结构与介绍💬
队列是在队头进行删除,在队尾进行插入;这一点与栈是不同的;栈是统一在栈顶进行操作;
而因为队列在队头进行删除的,使用数组的话,需要将后面数据整体向前挪动,这种办法无疑是很麻烦且效率很低的,所以利用链表进行删除则可以很好的规避这一问题,只需要改变头指针的指向即可删除!
2.相关功能接口💬
在论述了队列的结构,接下来需要对队列的结构体进行定义;
typedef int QueueDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QueueDataType data;
}QueueNode;
typedef struct Queue
{
QueueNode* front;
QueueNode* rear;
int size;
}Queue;
队列用链表进行实现,所以每一段其实就是一个结点;因此定义了QueueNode;而队列需要一段在头部进行操作,一段在尾部进行操作,所以定义了指向头的指针与指向尾的指针;由此也就构成了队列;
2.1初始化🎶
void InitQueue(Queue* qe)
{
assert(qe);
qe->front = NULL;
qe->rear = NULL;
qe->size = 0;
}
2.2入队🎶
void PushQueue(Queue* qe, QueueDataType x)
{
assert(qe);
//创造结点
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//链接
if (qe->rear == NULL)
{
qe->front = qe->rear = newnode;
}
else
{
qe->rear->next = newnode;
qe->rear = qe->rear->next;
//qe->rear = newnode;
}
qe->size++;
}
队列由结点链接而成,所以在入队时需要进行结点的创造,当队列中无数据时,此时首尾指向NULL。如上图所示:
在入队后,首尾同时指向新结点;而当队列中已存在数据时;此时就只需要将newnode赋给qe的下一个即next即可,在将rear尾部指针向后挪到即可!
2.3出队与判空🎶
//判空
bool QueueEmpty(Queue* qe)
{
assert(qe);
return qe->front == NULL && qe->rear == NULL;
}
//出队
void PopQueue(Queue* qe)
{
assert(qe);
assert(!QueueEmpty(qe));
if (qe->front->next == NULL)
{
free(qe->front);
qe->front = qe->rear = NULL;
}
else
{
QueueNode* del = qe->front;
qe->front = qe->front->next;
free(del);
del = NULL;
}
qe->size--;
}
出队的逻辑十分简单,只需要将头指针向后移动即可;但唯一需要注意的是,当
if (qe->front->next == NULL)
判断不存在的时候,且此时只存在一个结点的时候,将头指针向后移动并释放空间,此时front=NULL,但是rear还是指向被释放的那块空间,因此rear也就成了野指针!所以只存在一个结点的时候需要单独进行判断!
2.4返回队首与队尾数据🎶
//获取队尾元素
QueueDataType QueueBackData(Queue* qe)
{
assert(qe);
assert(!QueueEmpty(qe));
return qe->rear->data;
}
//获取队首元素
QueueDataType QueueFrontData(Queue* qe)
{
assert(qe);
assert(!QueueEmpty(qe));
return qe->front->data;
}
2.5获取有效数据个数🎶
int QueueSize(Queue* qe)
{
assert(qe);
return qe->size;
}
2.6销毁🎶
void DestoryQueue(Queue* qe)
{
assert(qe);
QueueNode* cur = qe->front;
while (cur)
{
QueueNode* del = cur;
cur = cur->next;
free(del);
}
qe->rear = qe->front = NULL;
qe->size = 0;
}
3完整代码💬
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<errno.h>
#include<string.h>
#include<stdbool.h>
typedef int QueueDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QueueDataType data;
}QueueNode;
typedef struct Queue
{
QueueNode* front;
QueueNode* rear;
int size;
}Queue;
//初始化
void InitQueue(Queue* qe);
//入队
void PushQueue(Queue* qe, QueueDataType x);
//出队
void PopQueue(Queue* qe);
//判空
bool QueueEmpty(Queue* qe);
//打印
void PrintQueue(Queue* qe);
//销毁
void DestoryQueue(Queue* qe);
//获取队列有效数据个数
int QueueSize(Queue* qe);
//获取队尾元素
QueueDataType QueueBackData(Queue* qe);
//获取队首元素
QueueDataType QueueFrontData(Queue* qe);
//初始化
void InitQueue(Queue* qe)
{
assert(qe);
qe->front = NULL;
qe->rear = NULL;
qe->size = 0;
}
//入队
void PushQueue(Queue* qe, QueueDataType x)
{
assert(qe);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//链接
if (qe->rear == NULL)
{
qe->front = qe->rear = newnode;
}
else
{
qe->rear->next = newnode;
qe->rear = qe->rear->next;
//qe->rear = newnode;
}
qe->size++;
}
//出队
void PopQueue(Queue* qe)
{
assert(qe);
assert(!QueueEmpty(qe));
if (qe->front->next == NULL)
{
free(qe->front);
qe->front = qe->rear = NULL;
}
else
{
QueueNode* del = qe->front;
qe->front = qe->front->next;
free(del);
del = NULL;
}
qe->size--;
}
//获取队列有效数据个数
int QueueSize(Queue* qe)
{
assert(qe);
return qe->size;
}
//获取队尾元素
QueueDataType QueueBackData(Queue* qe)
{
assert(qe);
assert(!QueueEmpty(qe));
return qe->rear->data;
}
//获取队首元素
QueueDataType QueueFrontData(Queue* qe)
{
assert(qe);
assert(!QueueEmpty(qe));
return qe->front->data;
}
//判空
bool QueueEmpty(Queue* qe)
{
assert(qe);
return qe->front == NULL && qe->rear == NULL;
}
//打印
void PrintQueue(Queue* qe)
{
assert(qe);
QueueNode* cur = qe->front;
while (cur != NULL)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
//销毁
void DestoryQueue(Queue* qe)
{
assert(qe);
QueueNode* cur = qe->front;
while (cur)
{
QueueNode* del = cur;
cur = cur->next;
free(del);
}
qe->rear = qe->front = NULL;
qe->size = 0;
}
void test()
{
Queue qe;
InitQueue(&qe);
PushQueue(&qe, 1);
PushQueue(&qe, 2);
PushQueue(&qe, 3);
PushQueue(&qe, 4);
PrintQueue(&qe);
PopQueue(&qe);
PopQueue(&qe);
/*PopQueue(&qe);
PopQueue(&qe);*/
PrintQueue(&qe);
QueueDataType top = TopQueue(&qe);
printf("top=%d ", top);
int ret = QueueSize(&qe);
printf("size=%d\n", ret);
QueueDataType back = QueueBackData(&qe);
printf("back=%d ", back);
DestoryQueue(&qe);
}
int main()
{
test();
}
4.利用队列实现栈💬
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
1.void push(int x) 将元素 x 压入栈顶。
2.int pop() 移除并返回栈顶元素。
3.int top() 返回栈顶元素。
4.bool empty() 如果栈是空的,返回 true ;否则,返回 false
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/implement-stack-using-queues
在上述介绍完队列后,应该如何利用队列实现栈呢?
栈的特性是在一端进行操作的,而队列则是一端进行删除,一端进行插入;
对于栈不清楚的可以点击这个链接进入查看:🎶我是链接
需要完成栈的四部分操作,插入、删除、栈顶、判空;在上面博客中我给出了队列的建立,所以直接在下来进行引用即可。
typedef int QueueDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QueueDataType data;
}QueueNode;
typedef struct Queue
{
QueueNode* front;
QueueNode* rear;
int size;
}Queue;
//初始化
void InitQueue(Queue* qe);
//入队
void PushQueue(Queue* qe, QueueDataType x);
//出队
void PopQueue(Queue* qe);
//判空
bool QueueEmpty(Queue* qe);
//打印
void PrintQueue(Queue* qe);
//销毁
void DestoryQueue(Queue* qe);
//初始化
void InitQueue(Queue* qe)
{
assert(qe);
qe->front = NULL;
qe->rear = NULL;
qe->size = 0;
}
//入队
void PushQueue(Queue* qe, QueueDataType x)
{
assert(qe);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//链接
if (qe->rear == NULL)
{
qe->front = qe->rear = newnode;
}
else
{
qe->rear->next = newnode;
qe->rear = qe->rear->next;
//qe->rear = newnode;
}
qe->size++;
}
//出队
void PopQueue(Queue* qe)
{
assert(qe);
assert(!QueueEmpty(qe));
if (qe->front->next == NULL)
{
free(qe->front);
qe->front = qe->rear = NULL;
}
else
{
QueueNode* del = qe->front;
qe->front = qe->front->next;
free(del);
del = NULL;
}
qe->size--;
}
//返回队首元素
QueueDataType QueueFrontData(Queue* qe)
{
assert(qe);
assert(!QueueEmpty(qe));
return qe->front->data;
}
//获取队尾元素
QueueDataType QueueBackData(Queue* qe)
{
assert(qe);
assert(!QueueEmpty(qe));
return qe->rear->data;
}
//获取队列有效数据个数
int QueueSize(Queue* qe)
{
assert(qe);
return qe->size;
}
//判空
bool QueueEmpty(Queue* qe)
{
assert(qe);
return qe->front == NULL && qe->rear == NULL;
}
//打印
void PrintQueue(Queue* qe)
{
assert(qe);
QueueNode* cur = qe->front;
while (cur != NULL)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
//销毁
void DestoryQueue(Queue* qe)
{
assert(qe);
QueueNode* cur = qe->front;
while (cur)
{
QueueNode* del = cur;
cur = cur->next;
free(del);
}
qe->rear = qe->front = NULL;
qe->size = 0;
}
//************************
//实现
//************************
typedef struct
{
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate()
{
//MyStack obj;出函数后会进行销毁
MyStack *obj=(MyStack*)malloc(sizeof(MyStack));
InitQueue(&(obj->q1));
InitQueue(&(obj->q2));
return obj;
}
void myStackPush(MyStack* obj, int x)
{
if(!QueueEmpty(&(obj->q1)))
{
PushQueue(&(obj->q1),x);
}
else
{
PushQueue(&(obj->q2),x);
}
}
int myStackPop(MyStack* obj)
{
Queue *empty=&obj->q1;
Queue *noempty=&obj->q2;
if(!QueueEmpty(&(obj->q1)))
{
empty=&obj->q2;
noempty=&obj->q1;
}
while(QueueSize(noempty)>1)
{
PushQueue(empty,QueueFrontData(noempty));
PopQueue(noempty);
}
int ret=QueueFrontData(noempty);
PopQueue(noempty);
return ret;
}
int myStackTop(MyStack* obj)
{
if(!QueueEmpty(&(obj->q1)))
{
return QueueBackData(&(obj->q1));
}
else
{
return QueueBackData(&(obj->q2));
}
}
bool myStackEmpty(MyStack* obj)
{
return QueueEmpty(&(obj->q1))&&QueueEmpty(&(obj->q2));
}
void myStackFree(MyStack* obj)
{
if(!QueueEmpty(&(obj->q1)))
{
DestoryQueue(&(obj->q1));
}
else
{
DestoryQueue(&(obj->q2));
}
free(obj);
}
关于利用两个队列实现栈,始终需要保持一个空队列供删除操作;而当两个队列都为空时说明这个栈就是空的;返回栈顶元素时,只需要找到非空队列,然后取其尾部数据即可!
5.栈实现队列💬
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/implement-queue-using-stacks
利用2个队列实现栈,自然也可以利用栈去实现队列!
typedef int StackDataType;
typedef struct Stack
{
StackDataType* arr;
int top;
int capacity;
}Stack;
//初始化
void InitStack(Stack* st);
//压栈
void PushStack(Stack* st, StackDataType x);
//弹栈
void PopStack(Stack* st);
//打印
void PrintStack(Stack* st);
//判空
bool StackEmpty(Stack* st);
//获取栈顶元素
StackDataType StackTop(Stack* st);
//获取栈的有效数据个数
int StackSize(Stack* st);
//销毁
void StackDestory(Stack* st);
//初始化
void InitStack(Stack* st)
{
assert(st);
st->capacity = st->top = 0;
st->arr = NULL;
}
//判断容量
static void CheackCapacity(Stack* st)
{
assert(st);
if (st->capacity == st->top)
{
int newcapacity = st->capacity == 0 ? 4 : (st->capacity * 2);
StackDataType* temp = (StackDataType*)realloc(st->arr, newcapacity * sizeof(StackDataType));
if (temp == NULL)
{
perror("malloc fail");
exit(-1);
}
st->arr = temp;
st->capacity = newcapacity;
}
}
//压栈
void PushStack(Stack* st, StackDataType x)
{
assert(st);
//判断容量
CheackCapacity(st);
//
st->arr[st->top] = x;
st->top++;
}
//弹栈
void PopStack(Stack* st)
{
assert(st);
assert(!StackEmpty(st));
st->top--;
}
//获取栈顶元素
StackDataType StackTop(Stack* st)
{
assert(st);
assert(!StackEmpty(st));
return st->arr[st->top-1];
}
//获取栈的有效数据个数
int StackSize(Stack* st)
{
assert(st);
return st->top;
}
//判空
bool StackEmpty(Stack* st)
{
assert(st);
return st->top == 0;//在压栈的过程中,因为存在后置++,一旦存入数据,top就会指向下一个空间
}
//销毁
void StackDestory(Stack* st)
{
assert(st);
free(st->arr);
st->arr = NULL;
st->capacity = st->top = 0;
}
typedef struct
{
Stack s1;//push
Stack s2;//pop
} MyQueue;
MyQueue* myQueueCreate()
{
MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));
InitStack(&obj->s1);
InitStack(&obj->s2);
return obj;
}
void myQueuePush(MyQueue* obj, int x)
{
PushStack(&obj->s1,x);
}
int myQueuePop(MyQueue* obj)
{
if(StackEmpty(&obj->s1))
{
int ret=StackTop(&obj->s2);
PopStack(&obj->s2);
return ret;
}
else
{
if(StackEmpty(&obj->s2))
{
while(!StackEmpty(&obj->s1))
{
int tmp=StackTop(&obj->s1);
PushStack(&obj->s2,tmp);
PopStack(&obj->s1);
}
int ret=StackTop(&obj->s2);
PopStack(&obj->s2);
return ret;
}
else
{
int ret=StackTop(&obj->s2);
PopStack(&obj->s2);
return ret;
}
}
}
int myQueuePeek(MyQueue* obj)
{
if(StackEmpty(&obj->s1))
{
int ret=StackTop(&obj->s2);
return ret;
}
else
{
if(StackEmpty(&obj->s2))
{
while(!StackEmpty(&obj->s1))
{
int tmp=StackTop(&obj->s1);
PushStack(&obj->s2,tmp);
PopStack(&obj->s1);
}
int ret=StackTop(&obj->s2);
return ret;
}
else
{
int ret=StackTop(&obj->s2);
return ret;
}
}
}
bool myQueueEmpty(MyQueue* obj)
{
return StackEmpty(&obj->s1)&&StackEmpty(&obj->s2);
}
void myQueueFree(MyQueue* obj)
{
StackDestory(&obj->s1);
StackDestory(&obj->s2);
free(obj);
obj=NULL;
}
整个代码中比较麻烦的就是pop,在pop过程中,需要对push的那个队列进行处理;而处理则分为2种情况!
需要提前注意的是,出队列出的是队首元素!
- 当push队列中无元素,此时只需要在pop中进行删除即可
- 当push中存在的元素,就先需要将push中的元素依次写入pop队列中,因为栈是后进先出,且删除时需要删除的是队列的首元素,即push栈的栈底元素,因此需要将push栈中依次写入pop栈中,如图所示:
- 而寻找队列首元素的原理与POP一致,在此就不多介绍了!
而在POP与SEEK函数中,可以对其相同的地方进行封装也是可以的!
总结:
关于利用队列实现栈和栈实现队列这两种形式也就介绍完成了;其共同之处都在于需要两个队列或者栈进行处理;在利用队列实现栈时,因为需要实现栈的特性,所以在删除时两个队列其中一个始终是要为空的,将非空队列中除队尾元素全部写入空队列中,此时删除非空队列的唯一一个元素也就对应了栈的在栈顶位置进行操作这一特点!
而用栈实现队列时,需要将一个栈作为Push时使用,一个栈作为Pop时使用,这是因为栈在弹元素时,会将栈中元素顺序颠倒,而队列在删除时需要删除的就是队首的元素,此时正好栈底的元素也处于顶部,刚好也就对应了队列删除时的特性;需要注意的是Push时,push队列是否为空!
Ending💬
线性表也就全部介绍完了;关于利用队列实现栈和栈实现队列这两种在文中也都有提到,其原理也大抵都讲清楚了!
就这样吧🙋♂️🙋♂️🙋♂️