一、队列(Queue)
队列的概念:
① 队列只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。
② 入队列,进行插入操作的一端称为 队尾。出队列,进行删除操作的一端称为 队头。
③ 队列中的元素遵循先进先出的原则,即 FIFO 原则(First In First Out)
队列的结构:
二、队列的定义
链式队列
typedef int QueueDataType; //队列类型
typedef struct QueueNode
{
struct QueueNode* next; //指向下一个节点
QueueDataType data; //数据
} QueueNode;
typedef struct Queue
{
QueueNode* pHead; //头指针
QueueNode* pTail; //尾指针
} Queue;
和栈不一样的是,栈使用数组和链表差别不大,只是数组更优。
队列使用数组就很麻烦了,只能使用链表。
为什么定义两个指针?
单链表我们只定义了一个指针指向头,没有定义尾指针。因为定义尾指针解决不了问题,比如尾删和任意位置的插入删除。(因为即使定义了也找不到尾指针的前一个)所以我们没有必要定义一个结构体把他们封到一起。这里我们再定义一个头指针 head 一个尾指针 tail ,这两个指针才有意义。因为根据队列的性质,我们只会在队尾插入,在队头删除。所以这个尾指针的价值就得到了完美的体现,实际中定义几个指针是看你的需求确定的。
另外扩展了解一下,实际中我们有时还会使用一种队列叫循环队列。
如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。
环形队列可以使用数组实现,也可以使用循环链表实现。
下面的讲解的力扣OJ力扣622题就是设计一种循环队列
三、队列的实现(完整代码)
Queue.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QueueDataType; //队列类型
typedef struct QueueNode
{
struct QueueNode* next; //指向下一个节点
QueueDataType data; //数据
} QueueNode;
typedef struct Queue//如果不设置成结构体就需要传二级指针,还要传两个参数
{
QueueNode* pHead;
QueueNode* pTail;
} Queue;
void QueueInit(Queue* pQ);//初始化队列
void QueueDestroy(Queue* pQ);//销毁队列
bool QueueEmpty(Queue* pQ);//判断队列是否为空
void QueuePush(Queue* pQ, QueueDataType x);//入队
void QueuePop(Queue* pQ);//出队
QueueDataType QueueFront(Queue* pQ);//返回队头数据
QueueDataType QueueBack(Queue* pQ);//返回队尾数据
int QueueSize(Queue* pQ);//返回队列大小
Queue.c
#include "Queue.h"
void QueueInit(Queue* pQ)
{
assert(pQ);
pQ->pHead = pQ->pTail = NULL;
}
void QueueDestroy(Queue* pQ)
{
assert(pQ);
QueueNode* cur = pQ->pHead;
while (cur != NULL)
{
QueueNode* curNext = cur->next; //防止释放cur后找不到其下一个节点
free(cur);
cur = curNext;
}
pQ->pHead = pQ->pTail = NULL;
}
bool QueueEmpty(Queue* pQ)
{
assert(pQ);
return pQ->pHead == NULL;//如果成立则为True,不成立则为False
}
//入队:队尾插入数据,队头出(删)数据。如果是第一个入队的(队列为空)则既要当头又当尾
void QueuePush(Queue* pQ, QueueDataType x)
{
assert(pQ);
QueueNode* new_node = (QueueNode*)malloc(sizeof(QueueNode));
if (new_node == NULL)
{
printf("malloc failed!\n");
exit(-1);
}
new_node->data = x; //待插入的数据
new_node->next = NULL; //新的数据指向空
if (pQ->pHead == NULL)//情况1: 队列为空
{
pQ->pHead = pQ->pTail = new_node;
}
else //情况2: 队列不为空 队尾入数据
{
pQ->pTail->next = new_node; //在现有尾的后一个节点放置new_node
pQ->pTail = new_node; //更新pTail,使它指向新的尾
}
}
void QueuePop(Queue* pQ) // 出队:队尾入数据,队头出(删)数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
QueueNode* headNext = pQ->pHead->next; //队头出数据,防止释放pHead后找不到其下一个节点
free(pQ->pHead);
pQ->pHead = headNext; //更新头
if (pQ->pHead == NULL)//删完之后(队列空了)pHead == NULL 了 pTail 还是野指针
{
pQ->pTail = NULL;//处理一下尾指针,将尾指针置空
}
}
QueueDataType QueueFront(Queue* pQ) //返回队头数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
return pQ->pHead->data;
}
QueueDataType QueueBack(Queue* pQ) //返回队尾数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
return pQ->pTail->data;
}
int QueueSize(Queue* pQ) //返回队列大小
{
assert(pQ);
int count = 0;
QueueNode* cur = pQ->pHead;
while (cur != NULL)
{
count++;
cur = cur->next;
}
return count;
}
Test.c
#include "Queue.h"
void TestQueue1()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
//QueuePop(&q);
QueueDestroy(&q);
}
void TestQueue2()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
//QueueDataType front = QueueFront(&q);
//printf("%d ", front);
//QueuePop(&q); //pop掉去下一个
QueuePush(&q, 3);
QueuePush(&q, 4);
//假设先入了1 2,让1出来,再继续入,它的顺序还是不会变。永远保持先进先出
while (!QueueEmpty(&q))
{
QueueDataType front = QueueFront(&q);
printf("%d ", front);
QueuePop(&q); //pop掉去下一个
}
printf("\n");
QueueDestroy(&q);
}
int main(void)
{
//TestQueue1();
TestQueue2();
return 0;
}
四、力扣OJ题
力扣链接:225. 用队列实现栈
难度简单
请你仅使用两个队列实现一个后入先出(LIFO)的栈,
并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:
你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和
is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (链表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
提示:
1 <= x <= 9
最多调用100 次 push、pop、top 和 empty
每次调用 pop 和 top 都保证栈不为空
(选C给的代码:)
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);
*/
解析代码:
核心思路:
(先入先出转为先入后出)
1.入数据,往不为空的队列入,保持另一个队列为空
2.出数据,依次出队头的数据,转移到另一个队列保存,只剩最后一个时Pop掉
和上一篇栈的OJ题一样,用C++写就很好写,但用C写就要自己创建 栈/队列
把我们刚才写的Queue.h和Queue.c复制粘贴过去后删掉头文件就行了
typedef int QueueDataType; //队列类型
typedef struct QueueNode
{
struct QueueNode* next; //指向下一个节点
QueueDataType data; //数据
} QueueNode;
typedef struct Queue//如果不设置成结构体就需要传二级指针,还要传两个参数
{
QueueNode* pHead;
QueueNode* pTail;
} Queue;
void QueueInit(Queue* pQ);//初始化队列
void QueueDestroy(Queue* pQ);//销毁队列
bool QueueEmpty(Queue* pQ);//判断队列是否为空
void QueuePush(Queue* pQ, QueueDataType x);//入队
void QueuePop(Queue* pQ);//出队(删数据)
QueueDataType QueueFront(Queue* pQ);//返回队头数据
QueueDataType QueueBack(Queue* pQ);//返回队尾数据
int QueueSize(Queue* pQ);//返回队列大小
void QueueInit(Queue* pQ)
{
assert(pQ);
pQ->pHead = pQ->pTail = NULL;
}
void QueueDestroy(Queue* pQ)
{
assert(pQ);
QueueNode* cur = pQ->pHead;
while (cur != NULL)
{
QueueNode* curNext = cur->next; //防止释放cur后找不到其下一个节点
free(cur);
cur = curNext;
}
pQ->pHead = pQ->pTail = NULL;
}
bool QueueEmpty(Queue* pQ)
{
assert(pQ);
return pQ->pHead == NULL;//如果成立则为True,不成立则为False
}
//入队:队尾插入数据,队头出(删)数据。如果是第一个入队的(队列为空)则既要当头又当尾
void QueuePush(Queue* pQ, QueueDataType x)
{
assert(pQ);
QueueNode* new_node = (QueueNode*)malloc(sizeof(QueueNode));
if (new_node == NULL)
{
printf("malloc failed!\n");
exit(-1);
}
new_node->data = x; //待插入的数据
new_node->next = NULL; //新的数据指向空
if (pQ->pHead == NULL)//情况1: 队列为空
{
pQ->pHead = pQ->pTail = new_node;
}
else //情况2: 队列不为空 队尾入数据
{
pQ->pTail->next = new_node; //在现有尾的后一个节点放置new_node
pQ->pTail = new_node; //更新pTail,使它指向新的尾
}
}
void QueuePop(Queue* pQ) // 出队:队尾入数据,队头出(删)数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
QueueNode* headNext = pQ->pHead->next; //队头出数据,防止释放pHead后找不到其下一个节点
free(pQ->pHead);
pQ->pHead = headNext; //更新头
if (pQ->pHead == NULL)//删完之后(队列空了)pHead == NULL 了 pTail 还是野指针
{
pQ->pTail = NULL;//处理一下尾指针,将尾指针置空
}
}
QueueDataType QueueFront(Queue* pQ) //返回队头数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
return pQ->pHead->data;
}
QueueDataType QueueBack(Queue* pQ) //返回队尾数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
return pQ->pTail->data;
}
int QueueSize(Queue* pQ) //返回队列大小
{
assert(pQ);
int count = 0;
QueueNode* cur = pQ->pHead;
while (cur != NULL)
{
count++;
cur = cur->next;
}
return count;
}
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* st = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&st->q1);//q1是结构体,还要取地址
QueueInit(&st->q2);//q2是结构体,还要取地址
return st;
}
void myStackPush(MyStack* obj, int x) {
if (!QueueEmpty(&obj->q1))//q1不为空(入到q1)
{
QueuePush(&obj->q1, x);
}
else//q2不为空(入到q2),或者两个都为空(入到哪都行)
{
QueuePush(&obj->q2, x);
}
}
int myStackPop(MyStack* obj) {
//题目要求int pop() 移除并返回栈顶元素。
//法一:
//if (!QueueEmpty(&obj->q1))//q1不为空,转移到q2保存,只剩最后一个时Pop掉
//{
// while (QueueSize(&obj->q1) > 1)
// {
// QueuePush(&obj->q2, QueueFront(&obj->q1));//从q1取数据Push到q2
// QueuePop(&obj->q1);
// }
// int ret = QueueFront(&obj->q1);
// QueuePop(&obj->q1);//题目要求int pop() 移除并返回栈顶元素。
// return ret;
//}
//else//q2不为空,转移到q1保存,只剩最后一个时Pop掉
//{
// while (QueueSize(&obj->q2) > 1)
// {
// QueuePush(&obj->q1, QueueFront(&obj->q2));//从q2取数据Push到q1
// QueuePop(&obj->q2);
// }
// int ret = QueueFront(&obj->q2);
// QueuePop(&obj->q2);
// return ret;
//}
//法二:
Queue* emptyQ = &obj->q1;//假设q1为空 q2非空
Queue* nonemptyQ = &obj->q2;
if (!QueueEmpty(&obj->q1))//假设失败,倒过来
{
emptyQ = &obj->q2;
nonemptyQ = &obj->q1;
}
while (QueueSize(nonemptyQ) > 1)
{
QueuePush(emptyQ, QueueFront(nonemptyQ));//从非空队列取数据Push到空队列
QueuePop(nonemptyQ);
}
int ret = QueueFront(nonemptyQ);
QueuePop(nonemptyQ);
return ret;
}
int myStackTop(MyStack* obj) {
//题目要求int top() 返回栈顶元素。(队列尾)队列不能删尾,能取尾
if (!QueueEmpty(&obj->q1))//q1不为空
{
return QueueBack(&obj->q1);
}
else//q2不为空
{
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);
}
解析代码:(看题解的思路)(两个队列)
用C++写后看题解,然后用C语言写,穿越回来贴一下:
typedef int QueueDataType; //队列类型
typedef struct QueueNode
{
struct QueueNode* next; //指向下一个节点
QueueDataType data; //数据
} QueueNode;
typedef struct Queue//如果不设置成结构体就需要传二级指针,还要传两个参数
{
QueueNode* pHead;
QueueNode* pTail;
} Queue;
void QueueInit(Queue* pQ);//初始化队列
void QueueDestroy(Queue* pQ);//销毁队列
bool QueueEmpty(Queue* pQ);//判断队列是否为空
void QueuePush(Queue* pQ, QueueDataType x);//入队
void QueuePop(Queue* pQ);//出队(删数据)
QueueDataType QueueFront(Queue* pQ);//返回队头数据
QueueDataType QueueBack(Queue* pQ);//返回队尾数据
int QueueSize(Queue* pQ);//返回队列大小
void QueueInit(Queue* pQ)
{
assert(pQ);
pQ->pHead = pQ->pTail = NULL;
}
void QueueDestroy(Queue* pQ)
{
assert(pQ);
QueueNode* cur = pQ->pHead;
while (cur != NULL)
{
QueueNode* curNext = cur->next; //防止释放cur后找不到其下一个节点
free(cur);
cur = curNext;
}
pQ->pHead = pQ->pTail = NULL;
}
bool QueueEmpty(Queue* pQ)
{
assert(pQ);
return pQ->pHead == NULL;//如果成立则为True,不成立则为False
}
//入队:队尾入数据,队头出(删)数据。如果是第一个入队的(队列为空)则既要当头又当尾
void QueuePush(Queue* pQ, QueueDataType x)
{
assert(pQ);
QueueNode* new_node = (QueueNode*)malloc(sizeof(QueueNode));
if (new_node == NULL)
{
printf("malloc failed!\n");
exit(-1);
}
new_node->data = x; //待插入的数据
new_node->next = NULL; //新的数据指向空
if (pQ->pHead == NULL)//情况1: 队列为空
{
pQ->pHead = pQ->pTail = new_node;
}
else //情况2: 队列不为空 队尾入数据
{
pQ->pTail->next = new_node; //在现有尾的后一个节点放置new_node
pQ->pTail = new_node; //更新pTail,使它指向新的尾
}
}
void QueuePop(Queue* pQ) // 出队:队尾入数据,队头出(删)数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
QueueNode* headNext = pQ->pHead->next; //队头出数据,防止释放pHead后找不到其下一个节点
free(pQ->pHead);
pQ->pHead = headNext; //更新头
if (pQ->pHead == NULL)//删完之后 pHead == NULL 了 pTail 还是野指针
{
pQ->pTail = NULL;//处理一下尾指针,将尾指针置空
}
}
QueueDataType QueueFront(Queue* pQ) //返回队头数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
return pQ->pHead->data;
}
QueueDataType QueueBack(Queue* pQ) //返回队尾数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
return pQ->pTail->data;
}
int QueueSize(Queue* pQ) //返回队列大小
{
assert(pQ);
int count = 0;
QueueNode* cur = pQ->pHead;
while (cur != NULL)
{
count++;
cur = cur->next;
}
return count;
}
typedef struct { //C++看题解的思路 push麻烦了点,但其它都简单了
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* st = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&st->q1);//q1是结构体,还要取地址
QueueInit(&st->q2);//q2是结构体,还要取地址
return st;
}
void myStackPush(MyStack* obj, int x) {
/*if (!QueueEmpty(&obj->q1))//q1不为空(入到q1)
{
QueuePush(&obj->q1, x);
}
else//q2不为空(入到q2),或者两个都为空(入到哪都行)
{
QueuePush(&obj->q2, x);
}*/
//C++看题解的思路
QueuePush(&obj->q2, x); // q2入队列
while (!QueueEmpty(&obj->q1)) // 把q1的元素全入到q2
{
QueuePush(&obj->q2, QueueFront(&obj->q1));
QueuePop(&obj->q1);
}
//swap(q1, q2); // 交换q1和q2,保证q1任何时候都是栈的属性
//没有交换函数,此时q1为空,把q2元素push到q1,然后pop掉q2即可
while(!QueueEmpty(&obj->q2))
{
QueuePush(&obj->q1, QueueFront(&obj->q2));
QueuePop(&obj->q2);
}
}
int myStackPop(MyStack* obj) {
//题目要求int pop() 移除并返回栈顶元素。
//法一:
//if (!QueueEmpty(&obj->q1))//q1不为空,转移到q2保存,只剩最后一个时Pop掉
//{
// while (QueueSize(&obj->q1) > 1)
// {
// QueuePush(&obj->q2, QueueFront(&obj->q1));//从q1取数据Push到q2
// QueuePop(&obj->q1);
// }
// int ret = QueueFront(&obj->q1);
// QueuePop(&obj->q1);//题目要求int pop() 移除并返回栈顶元素。
// return ret;
//}
//else//q2不为空,转移到q1保存,只剩最后一个时Pop掉
//{
// while (QueueSize(&obj->q2) > 1)
// {
// QueuePush(&obj->q1, QueueFront(&obj->q2));//从q2取数据Push到q1
// QueuePop(&obj->q2);
// }
// int ret = QueueFront(&obj->q2);
// QueuePop(&obj->q2);
// return ret;
//}
//法二:
/*Queue* emptyQ = &obj->q1;//假设q1为空 q2非空
Queue* nonemptyQ = &obj->q2;
if (!QueueEmpty(&obj->q1))//假设失败,倒过来
{
emptyQ = &obj->q2;
nonemptyQ = &obj->q1;
}
while (QueueSize(nonemptyQ) > 1)
{
QueuePush(emptyQ, QueueFront(nonemptyQ));//从非空队列取数据Push到空队列
QueuePop(nonemptyQ);
}
int ret = QueueFront(nonemptyQ);
QueuePop(nonemptyQ);
return ret;*/
//C++看题解的思路
int r = QueueFront(&obj->q1);
QueuePop(&obj->q1);
return r;
}
int myStackTop(MyStack* obj) {
//题目要求int top() 返回栈顶元素。(队列尾)队列不能删尾,能取尾
/*if (!QueueEmpty(&obj->q1))//q1不为空
{
return QueueBack(&obj->q1);
}
else//q2不为空
{
return QueueBack(&obj->q2);
}*/
//C++看题解的思路
return QueueFront(&obj->q1);
}
bool myStackEmpty(MyStack* obj) {
//return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
//C++看题解的思路
return QueueEmpty(&obj->q1);
}
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
解析代码:(看题解的思路)(一个队列)
使用一个队列时,为了满足栈的特性,即最后入栈的元素最先出栈,同样需要满足队列前端的元素是最后入栈的元素。
入栈操作时,首先获得入栈前的元素个数 n,然后将元素入队到队列,再将队列中的前 n 个元素(即除了新入栈的元素之外的全部元素)依次出队并入队到队列,此时队列的前端的元素即为新入栈的元素,且队列的前端和后端分别对应栈顶和栈底。
由于每次入栈操作都确保队列的前端元素为栈顶元素,因此出栈操作和获得栈顶元素操作都可以简单实现。出栈操作只需要移除队列的前端元素并返回即可,获得栈顶元素操作只需要获得队列的前端元素并返回即可(不移除元素)。
由于队列用于存储栈内的元素,判断栈是否为空时,只需要判断队列是否为空即可。
typedef int QueueDataType; //队列类型
typedef struct QueueNode
{
struct QueueNode* next; //指向下一个节点
QueueDataType data; //数据
} QueueNode;
typedef struct Queue//如果不设置成结构体就需要传二级指针,还要传两个参数
{
QueueNode* pHead;
QueueNode* pTail;
} Queue;
void QueueInit(Queue* pQ);//初始化队列
void QueueDestroy(Queue* pQ);//销毁队列
bool QueueEmpty(Queue* pQ);//判断队列是否为空
void QueuePush(Queue* pQ, QueueDataType x);//入队
void QueuePop(Queue* pQ);//出队(删数据)
QueueDataType QueueFront(Queue* pQ);//返回队头数据
QueueDataType QueueBack(Queue* pQ);//返回队尾数据
int QueueSize(Queue* pQ);//返回队列大小
void QueueInit(Queue* pQ)
{
assert(pQ);
pQ->pHead = pQ->pTail = NULL;
}
void QueueDestroy(Queue* pQ)
{
assert(pQ);
QueueNode* cur = pQ->pHead;
while (cur != NULL)
{
QueueNode* curNext = cur->next; //防止释放cur后找不到其下一个节点
free(cur);
cur = curNext;
}
pQ->pHead = pQ->pTail = NULL;
}
bool QueueEmpty(Queue* pQ)
{
assert(pQ);
return pQ->pHead == NULL;//如果成立则为True,不成立则为False
}
//入队:队尾入数据,队头出(删)数据。如果是第一个入队的(队列为空)则既要当头又当尾
void QueuePush(Queue* pQ, QueueDataType x)
{
assert(pQ);
QueueNode* new_node = (QueueNode*)malloc(sizeof(QueueNode));
if (new_node == NULL)
{
printf("malloc failed!\n");
exit(-1);
}
new_node->data = x; //待插入的数据
new_node->next = NULL; //新的数据指向空
if (pQ->pHead == NULL)//情况1: 队列为空
{
pQ->pHead = pQ->pTail = new_node;
}
else //情况2: 队列不为空 队尾入数据
{
pQ->pTail->next = new_node; //在现有尾的后一个节点放置new_node
pQ->pTail = new_node; //更新pTail,使它指向新的尾
}
}
void QueuePop(Queue* pQ) // 出队:队尾入数据,队头出(删)数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
QueueNode* headNext = pQ->pHead->next; //队头出数据,防止释放pHead后找不到其下一个节点
free(pQ->pHead);
pQ->pHead = headNext; //更新头
if (pQ->pHead == NULL)//删完之后 pHead == NULL 了 pTail 还是野指针
{
pQ->pTail = NULL;//处理一下尾指针,将尾指针置空
}
}
QueueDataType QueueFront(Queue* pQ) //返回队头数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
return pQ->pHead->data;
}
QueueDataType QueueBack(Queue* pQ) //返回队尾数据
{
assert(pQ);
assert(!QueueEmpty(pQ));
return pQ->pTail->data;
}
int QueueSize(Queue* pQ) //返回队列大小
{
assert(pQ);
int count = 0;
QueueNode* cur = pQ->pHead;
while (cur != NULL)
{
count++;
cur = cur->next;
}
return count;
}
typedef struct { //C++看题解的思路(一个队列) push麻烦了点,但其它都简单了
Queue q1;
} MyStack;
MyStack* myStackCreate() {
MyStack* st = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&st->q1);//q1是结构体,还要取地址
return st;
}
void myStackPush(MyStack* obj, int x) {
/*if (!QueueEmpty(&obj->q1))//q1不为空(入到q1)
{
QueuePush(&obj->q1, x);
}
else//q2不为空(入到q2),或者两个都为空(入到哪都行)
{
QueuePush(&obj->q2, x);
}*/
//C++看题解的思路
/*QueuePush(&obj->q2, x); // q2入队列
while (!QueueEmpty(&obj->q1)) // 把q1的元素全入到q2
{
QueuePush(&obj->q2, QueueFront(&obj->q1));
QueuePop(&obj->q1);
}
//swap(q1, q2); // 交换q1和q2,保证q1任何时候都是栈的属性
//没有交换函数,此时q1为空,把q2元素push到q1,然后pop掉q2即可
while(!QueueEmpty(&obj->q2))
{
QueuePush(&obj->q1, QueueFront(&obj->q2));
QueuePop(&obj->q2);
}*/
//一个队列:
QueuePush(&obj->q1, x);
int n = QueueSize(&obj->q1) - 1;
while(n--)
{
QueuePush(&obj->q1, QueueFront(&obj->q1));
QueuePop(&obj->q1);
}
}
int myStackPop(MyStack* obj) {
//题目要求int pop() 移除并返回栈顶元素。
//法一:
//if (!QueueEmpty(&obj->q1))//q1不为空,转移到q2保存,只剩最后一个时Pop掉
//{
// while (QueueSize(&obj->q1) > 1)
// {
// QueuePush(&obj->q2, QueueFront(&obj->q1));//从q1取数据Push到q2
// QueuePop(&obj->q1);
// }
// int ret = QueueFront(&obj->q1);
// QueuePop(&obj->q1);//题目要求int pop() 移除并返回栈顶元素。
// return ret;
//}
//else//q2不为空,转移到q1保存,只剩最后一个时Pop掉
//{
// while (QueueSize(&obj->q2) > 1)
// {
// QueuePush(&obj->q1, QueueFront(&obj->q2));//从q2取数据Push到q1
// QueuePop(&obj->q2);
// }
// int ret = QueueFront(&obj->q2);
// QueuePop(&obj->q2);
// return ret;
//}
//法二:
/*Queue* emptyQ = &obj->q1;//假设q1为空 q2非空
Queue* nonemptyQ = &obj->q2;
if (!QueueEmpty(&obj->q1))//假设失败,倒过来
{
emptyQ = &obj->q2;
nonemptyQ = &obj->q1;
}
while (QueueSize(nonemptyQ) > 1)
{
QueuePush(emptyQ, QueueFront(nonemptyQ));//从非空队列取数据Push到空队列
QueuePop(nonemptyQ);
}
int ret = QueueFront(nonemptyQ);
QueuePop(nonemptyQ);
return ret;*/
//C++看题解的思路 //两个队列一个队列适用
int r = QueueFront(&obj->q1);
QueuePop(&obj->q1);
return r;
}
int myStackTop(MyStack* obj) {
//题目要求int top() 返回栈顶元素。(队列尾)队列不能删尾,能取尾
/*if (!QueueEmpty(&obj->q1))//q1不为空
{
return QueueBack(&obj->q1);
}
else//q2不为空
{
return QueueBack(&obj->q2);
}*/
//C++看题解的思路 //两个队列一个队列适用
return QueueFront(&obj->q1);
}
bool myStackEmpty(MyStack* obj) {
//return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
//C++看题解的思路 //两个队列一个队列适用
return QueueEmpty(&obj->q1);
}
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
free(obj);
}
力扣链接:232. 用栈实现队列
难度简单
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作
(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例 1:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
最多调用 100 次 push、pop、peek 和 empty
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)
进阶:
你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。
typedef struct {
} MyQueue;
MyQueue* myQueueCreate() {
}
void myQueuePush(MyQueue* obj, int x) {
}
int myQueuePop(MyQueue* obj) {
}
int myQueuePeek(MyQueue* obj) {
}
bool myQueueEmpty(MyQueue* obj) {
}
void myQueueFree(MyQueue* obj) {
}
/**
* Your MyQueue struct will be instantiated and called as such:
* MyQueue* obj = myQueueCreate();
* myQueuePush(obj, x);
* int param_2 = myQueuePop(obj);
* int param_3 = myQueuePeek(obj);
* bool param_4 = myQueueEmpty(obj);
* myQueueFree(obj);
*/
解析代码:
我们知道,栈与队列的原理刚好相反,对于栈是【FILO】,对于队列是【FIFO】。
这就需要我们灵活地去使用这两种数据结构进行解题。对于本题, 因为需要使用栈来实现队列,
那还需要像我们上一题那样将两个队列倒来倒去实现一个出栈的操作吗?
答案是:不需要,对于这一题而言,我们需要有一个明确的思路,
也是去定义两个栈,一个栈专门入数据,一个栈专门出数据,(专门入数据的栈的数据转移到专门出数据的栈,此时专门出数据的栈出数据就符合题意了,专门出数据的栈空后,再继续转移)(可以画图理解)具体的实现在下面
(和上题本质是一样的,但最优形态也有不一样的)(先入后出改为先入先出)
先把上篇写的栈复制粘贴过来(char改回int)链接:
数据结构与算法⑧(第三章_上)栈的概念和实现(力扣:20. 有效的括号)_GR C的博客-CSDN博客
typedef int StackDataType;
typedef struct Stack
{
StackDataType* array; //数组
int top; //栈顶
int capacity; //容量
} Stack;
void StackInit(Stack* ps);
void StackDestroy(Stack* ps);
void StackPush(Stack* ps, StackDataType x);
bool StackEmpty(Stack* ps);
void StackPop(Stack* ps);
StackDataType StackTop(Stack* ps);
int StackSize(Stack* ps);
void StackInit(Stack* ps)//初始化
{
assert(ps);
ps->array = NULL;
ps->top = 0; // ps->top = -1
ps->capacity = 0;
}
void StackDestroy(Stack* ps)//销毁
{
assert(ps);
free(ps->array);
ps->array = NULL;
ps->capacity = ps->top = 0;
}
void StackPush(Stack* ps, StackDataType x)//进栈
{
assert(ps);
if (ps->top == ps->capacity)
{
int new_capacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
StackDataType* tmp_arr =(StackDataType *) realloc(ps->array, sizeof(StackDataType) * new_capacity);
if (tmp_arr == NULL)
{
printf("realloc failed!\n");
exit(-1);
}
// 更新
ps->array = tmp_arr;
ps->capacity = new_capacity;
}
ps->array[ps->top] = x;// 填入数据
ps->top++;
}
bool StackEmpty(Stack* ps)//判断栈是否为空
{
assert(ps);
return ps->top == 0; //等于0就是空,就是真
}
void StackPop(Stack* ps)// 出栈
{
assert(ps);
//assert(ps->top > 0); //防止top为空
assert(!StackEmpty(ps));
ps->top--;
}
StackDataType StackTop(Stack* ps)//返回栈顶数据
{
assert(ps);
//assert(ps->top > 0); //防止top为空
assert(!StackEmpty(ps));
return ps->array[ps->top - 1];
}
int StackSize(Stack* ps) //计算栈的大小
{
assert(ps);
return ps->top;// 因为我们设定top是指向栈顶的下一个,所以top就是size
}
typedef struct {
Stack pushST;
Stack popST;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&q->pushST);
StackInit(&q->popST);
return q;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->pushST, x);
}
int myQueuePop(MyQueue* obj) {
//int pop() 从队列的开头移除并返回元素
//如果popST中没有数据,就把pushST的数据拿过去
//这样popST的数据就符合先进先出了
if (StackEmpty(&obj->popST))
{
while (!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST, StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
//有数据就直接出(删后返回)
int ret = StackTop(&obj->popST);
StackPop(&obj->popST);
return ret;
}
int myQueuePeek(MyQueue* obj) {
//int peek() 返回队列开头的元素
//如果popST中没有数据,就把pushST的数据拿过去
if (StackEmpty(&obj->popST))
{
while (!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST, StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
return StackTop(&obj->popST);//不删,直接取
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->pushST) && StackEmpty(&obj->popST);
}
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->pushST);
StackDestroy(&obj->popST);
free(obj);
}
/**
* Your MyQueue struct will be instantiated and called as such:
* MyQueue* obj = myQueueCreate();
* myQueuePush(obj, x);
* int param_2 = myQueuePop(obj);
* int param_3 = myQueuePeek(obj);
* bool param_4 = myQueueEmpty(obj);
* myQueueFree(obj);
*/
力扣链接:622. 设计循环队列
难度中等
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
示例:
MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3
circularQueue.enQueue(1); // 返回 true
circularQueue.enQueue(2); // 返回 true
circularQueue.enQueue(3); // 返回 true
circularQueue.enQueue(4); // 返回 false,队列已满
circularQueue.Rear(); // 返回 3
circularQueue.isFull(); // 返回 true
circularQueue.deQueue(); // 返回 true
circularQueue.enQueue(4); // 返回 true
circularQueue.Rear(); // 返回 4
提示:
所有的值都在 0 至 1000 的范围内;
操作数将在 1 至 1000 的范围内;
请不要使用内置的队列库。
typedef struct {
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
}
int myCircularQueueFront(MyCircularQueue* obj) {
}
int myCircularQueueRear(MyCircularQueue* obj) {
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
}
void myCircularQueueFree(MyCircularQueue* obj) {
}
/**
* Your MyCircularQueue struct will be instantiated and called as such:
* MyCircularQueue* obj = myCircularQueueCreate(k);
* bool param_1 = myCircularQueueEnQueue(obj, value);
* bool param_2 = myCircularQueueDeQueue(obj);
* int param_3 = myCircularQueueFront(obj);
* int param_4 = myCircularQueueRear(obj);
* bool param_5 = myCircularQueueIsEmpty(obj);
* bool param_6 = myCircularQueueIsFull(obj);
* myCircularQueueFree(obj);
*/
解析:
这道题用数组和链表都能实现,各有优劣
要注意下面的判空和判满问题:
判空和判满问题可以使用添加一个size解决,但比较官方是多开一个空间:
【0】 【1】 【2】 【3】 【4】
一般情况tail+1等于front 如上面tail 是【1】,front是【2】(1+1)%(4+1)==2也是满
但是如果tail是【4】,front是【0】,tail+1等于front就不能判断
用上面的公式(4+1)%(4+1)==0 就是满
链表空的情况和上面一样,满的情况是这样:
数组实现 函数bool myCircularQueueDeQueue(MyCircularQueue* obj)的图:
数组实现的代码:(有空可以写写链表实现的)
typedef struct {
//匿名结构体
int* arr;
int front;
int tail;
int k;
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);//可以先实现这两个 声明后下面可以用
bool myCircularQueueIsFull(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//OJ题一般都会开辟空间成功,不用判空了
cq->arr = (int*)malloc(sizeof(int) * (k + 1));//初始化 开k+1个空间
cq->front = cq->tail = 0;
cq->k = k;
return cq;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
//(失败返回假)满了就失败
//先滑到下面实现myCircularQueueIsFull和myCircularQueueIsEmpty,记得在上面声明
if (myCircularQueueIsFull(obj))
{
return false;
}
obj->arr[obj->tail] = value;
obj->tail++;
obj->tail %= (obj->k + 1);//超出k+1就模到0从头开始,没超,模和没模一样
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
if (myCircularQueueIsEmpty(obj))
{
return false;
}
obj->front++; //不理解可以看上面的图
obj->front %= (obj->k + 1); //超出k+1就模到0从头开始,没超,模和没模一样
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
//Front: 从队首获取元素。如果队列为空,返回 -1 。
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->arr[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
//Rear: 获取队尾元素。如果队列为空,返回 -1 。
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
if (obj->tail == 0)//如果tail在0上,返回tail的上一个就是k
{
return obj->arr[obj->k];
}
else
{
return obj->arr[obj->tail - 1];
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->tail; //看上面图的判空和判满条件
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->tail + 1) % (obj->k + 1) == obj->front;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->arr);
free(obj); //有两层 结构体里面有个数组
}
五、概念选择题
1.下列关于队列的叙述错误的是( )
A.队列可以使用链表实现
B.队列是一种“先入先出”的数据结构
C.数据出队列时一定只影响尾指针
D.数据入队列时一定从尾部插入
2.用无头单链表存储队列,其头指针指向队头结点,尾指针指向队尾结点,则在进行出队操作时( )
A.仅修改队头指针
B.队头、队尾指针都要修改
C.队头、队尾指针都可能要修改
D.仅修改队尾指针
3.下列关于栈的叙述正确的是( )
A.栈是一种“先进先出”的数据结构
B.栈可以使用链表或顺序表来实现
C.栈只能在栈底插入数据
D.栈不能删除数据
4.一个栈的入栈序列为ABCDE,则不可能的出栈序列为( )
A.ABCDE
B.EDCBA
C.DCEBA
D.ECDBA
5.链栈与顺序栈相比,比较明显的优点是( )
A.插入操作更加方便
B.删除操作更加方便
C.入栈时不需要扩容
6.以下不是队列的基本运算的是( )
A.从队尾插入一个新元素
B.从队列中删除队尾元素
C.判断一个队列是否为空
D.读取队头元素的值
7.(多选)下面关于栈和队列的说法中错误的是( )
A.队列和栈通常都使用链表实现
B.队列和栈都只能从两端插入、删除数据
C.队列和栈都不支持随机访问和随机插入
D.队列是“先入先出”,栈是“先入后出”
8.下列关于用栈实现队列的说法中错误的是( )
A.用栈模拟实现队列可以使用两个栈,一个栈模拟入队列,一个栈模拟出队列
B.每次出队列时,都需要将一个栈中的全部元素导入到另一个栈中,然后出栈即可
C.入队列时,将元素直接往模拟入队列的栈中存放即可
D.入队列操作时间复杂度为O(1)
9.下列关于顺序结构实现循环队列的说法,正确的是( )
A.循环队列的长度通常都不固定
B.直接用队头和队尾在同一个位置可以判断循环队列是否为满
C.通过设置计数的方式可以判断队列空或者满
D.循环队列是一种非线性数据结构
10.现有一循环队列,其队头为front,队尾为rear,循环队列长度为N,最多存储N-1个数据。其队内有效长度为( )
A.(rear - front + N) % N + 1
B.(rear - front + N) % N
C.(rear - front) % (N + 1)
D.(rear - front + N) % (N - 1)
答案:
1答案:C
出队操作,一定会影响头指针,如果出队之后,队列为空,会影响尾指针。
2.答案:C
出队操作,一定会修改头指针,如果出队之后,队列为空,需要修改尾指针。
3.答案:B
A错误:栈是一种后进先出的数据结构,队列是先进先出的
B正确:顺序表和链表都可以用来实现栈,不过一般都使用顺序表,因为栈想当于是阉割版的顺序表,只用到了顺序表的尾插和尾删操作,顺序表的尾插和尾删不需要搬移元素效率非常高,故一般都是使用顺序表实现
C错误:栈只能在栈顶进行输入的插入和删除操作
D错误:栈是有入栈和出栈操作,出栈就是从栈中删除一个元素
4.答案:D
此题在校招选择题中出现较频繁,稳妥的做法是画图逐个选项检测,大概率是不会出错的
如果是E先出,说明ABCDE都已经全部入栈,E出栈之后,此时栈顶元素是D,如果再要出栈应该是D,而不应该是C。
5.答案:C
A错误,如果是链栈,一般需要进行头插或者头删操作,而顺序栈一般进行尾插和尾删操作,链表的操作比顺序表复杂,因此使用顺序结构实现栈更简单
B错误,原因参考A
C正确,链式结构实现栈时,每次入栈相当于链表中头插一个节点,没有扩容一说
6.答案:B
队列只能从队头删除元素。
7.答案:AB
A错误:栈是尾部插入和删除,一般使用顺序表实现,队列是头部删除尾部插入,一般使用链表实现
B错误:栈是后进先出,尾部插入和删除;队列是先进先出,尾部插入头部删除
C正确:栈只能访问栈顶元素,不支持随机访问,队列也不支持
D正确:栈和队列的特性
8.答案:B
选项B中,一个栈模拟入队列,一个栈模拟出队列,出队列时直接弹出模拟出队列栈的栈顶元素,当该栈为空时,将模拟入队列栈中所有元素导入即可,不是每次都需要导入元素,故错误
选项A中,栈和队列的特性是相反的,一个栈实现不了队列
选项C中,一个栈模拟入队列,一个栈模拟出队列,入队列时,将元素直接往模拟入队列的栈中存放
选项D中,入队列就是将元素放到栈中,因此时间复杂度就是O(1)
9.答案:C
队列适合使用链表实现,使用顺序结构(即固定的连续空间)实现时会出现假溢出的问题,因此大佬们设计出了循环队列,循环队列就是为了解决顺序结构实现队列假溢出问题的
A错误:循环队列的长度都是固定的
B错误:队头和队尾在同一个位置时 队列可能是空的,也可能是满的,因此无法判断
C正确:设置计数即添加一个字段来记录队列中有效元素的个数,如果队列中有效元素个数等于空间总大小时队列满,如果队列中有效元素个数为0时队列空
D错误:循环队列也是队列的一种,是一种特殊的线性数据结构
10答案:B
有效长度一般是rear-front, 但是循环队列中rear有可能小于front,减完之后可能是负数,所以需要+N,此时结果刚好是队列中有效元素个数,但如果rear大于front,减完之后就是有效元素个数了,再加N后有效长度会超过N,故需要%N。
第三章完。(附下篇链接)
这一章大作业就是自己实现一个栈和队列。
下一章:第四章:树和二叉树
穿越回来复习顺便贴个链接: