目录
[1.1 概念与结构](#1.1 概念与结构)
[1.2 栈的实现](#1.2 栈的实现)
[1.2.1 创建一个栈](#1.2.1 创建一个栈)
[1.2.2 栈的初始化](#1.2.2 栈的初始化)
[1.2.3 栈的销毁](#1.2.3 栈的销毁)
[1.2.4 入栈--栈顶操作](#1.2.4 入栈--栈顶操作)
[1.2.5 判断栈是否为空](#1.2.5 判断栈是否为空)
[1.2.6 出栈--栈顶操作](#1.2.6 出栈--栈顶操作)
[1.2.7 取栈顶元素](#1.2.7 取栈顶元素)
[1.2.8 获取栈中元素的个数](#1.2.8 获取栈中元素的个数)
[1.2.9 全部代码](#1.2.9 全部代码)
[1.3 算法题:有效的括号](#1.3 算法题:有效的括号)
[2. 队列](#2. 队列)
[2.1 概念与结构](#2.1 概念与结构)
[2.2 队列的实现](#2.2 队列的实现)
[2.2.1 定义队列的结构](#2.2.1 定义队列的结构)
[2.2.2 队列的初始化](#2.2.2 队列的初始化)
[2.2.3 入队--队尾](#2.2.3 入队--队尾)
[2.2.4 出队--对头](#2.2.4 出队--对头)
[2.2.5 取对头元素、取队尾元素](#2.2.5 取对头元素、取队尾元素)
[2.2.6 销毁队列](#2.2.6 销毁队列)
[2.2.7 全部代码](#2.2.7 全部代码)
[2.3 算法题](#2.3 算法题)
[2.3.1 用队列实现栈](#2.3.1 用队列实现栈)
[3. 创建栈,并完成初始化操作](#3. 创建栈,并完成初始化操作)
[4. 实现栈的圧栈操作](#4. 实现栈的圧栈操作)
[5. 实现栈的出栈操作](#5. 实现栈的出栈操作)
[6. 实现取栈顶元素操作](#6. 实现取栈顶元素操作)
[7. 实现栈的判空操作](#7. 实现栈的判空操作)
[8. 实现栈的销毁操作](#8. 实现栈的销毁操作)
[9. 测试是否通过](#9. 测试是否通过)
[10. 全部的代码](#10. 全部的代码)
[2.3.2 用栈实现队列](#2.3.2 用栈实现队列)
[1. 审题,明确思路](#1. 审题,明确思路)
[2. 使用两个栈完成队列结构的定义](#2. 使用两个栈完成队列结构的定义)
[3. 创建队列,并完成初始化操作](#3. 创建队列,并完成初始化操作)
[4. 实现入队列操作](#4. 实现入队列操作)
[5. 实现出队列操作](#5. 实现出队列操作)
[6. 实现取对头数据操作](#6. 实现取对头数据操作)
[7. 实现队列的判空操作](#7. 实现队列的判空操作)
[8. 实现队列的销毁操作](#8. 实现队列的销毁操作)
[9. 测试是否通过](#9. 测试是否通过)
[10. 全部代码](#10. 全部代码)
1.栈
1.1 概念与结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素的操作。进行数据插入和删除操作的是栈顶,另一端为栈底。栈中的数据元素遵守先进后出的原则。
圧栈:栈的插入操作叫做圧栈或者入栈或者进栈,入数据在栈顶
出栈:栈的删除操作叫做出栈。出数据也在栈顶
我们可以画个图来清晰了解这一概念。

我们了解了栈的概念,现在我们来看一下栈的结构。
栈的实现我们使用数组来实现还是链表来实现呢?
我先把答案说一下:数组。
原因看下图:

虽然二者的空间复杂度是相同的,但是数组的空间复杂度比链表的空间复杂度低,因此使用数组更好。
1.2 栈的实现
还是老样子,我们创建一个空项目,并创建三个文件,Stack.h 头文件 Stack.c 实现源码, test.c 测试文件。

1.2.1 创建一个栈
要想实现一个栈,我们首先得定义栈的结构。在前面我们已经搞明白了栈的底层构建需要用到数组,所以我们直接在栈内定义一个数组类型,同时我们要有栈中有效数据的个数,方便我们寻找数据所在位置,我们的栈也是需要空间的,所以栈的第三个元素就是栈空间大小。
我们定义以上所说的三个元素,栈的结构就定义完成了,也就是空栈。
// 定义栈的结构
typedef int STDataType; // 因为数据不是只有int类型。我们重命名一下。
typedef struct Stack
{
STDataType* arr;
int top; // 栈中有效数据的个数
int capacity; // 栈的空间大小
}ST; // 不想多次写struct,命名为ST
1.2.2 栈的初始化
初始化就特别简单了,我们只需要把栈中元素置为空,以及初始化为0即可。
// 栈的初始化
void STInit(ST* ps)
{
ps->arr = NULL;
ps->top = ps->capacity = 0;
}
我们养成良好的习惯。代码段写完后我们就进行测试。以下是通过调试的监视窗口来观察代码运行正确与否。

不难看出,我们的代码初始化成功了。
1.2.3 栈的销毁
栈的销毁操作和初始化操作差不多,就是多了一个判断栈是否为空的操作,毕竟栈本来就是空的,也是不造成什么影响的。
// 栈的销毁
void STDestroy(ST* ps)
{
if (ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->top = ps->capacity = 0;
}
1.2.4 入栈--栈顶操作
前面我们已经有了一个空栈,现在我们往栈里插入数据,也就是入栈操作。
这里的入栈操作和 顺序表的插入数据没什么两样,所以就不进行画图,我们直接写代码。
// 入栈--栈顶
void STPush(ST* ps, STDataType x)
{
// 判断空间是否足够
if (ps->top == ps->capacity)
{
// 增容
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
// 空间足够
ps->arr[ps->top++] = x;
}
现在我们来进行调试测试。

可以看出我们插入的4个数据都是成功的,那么我们现在来试一试插入第5个数据。

成功插入,也就是说明我们的代码是正确的。这里同样使用的是2倍增容法。
1.2.5 判断栈是否为空
我们入栈操作写完,本应该继续写出栈操作,那为什么要写判空操作呢?
因为我们要进行出栈操作,必须保证它这个栈里要有数据,空栈压根儿不需要有任何操作。所以我们需要先写判空操作。
这样子我们就可以看看前面入栈操作,传过来的地址是不可能为空的,所以我们要加上assert断言报错。
// 判空操作
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
这里我们使用布尔类型,要在头文件上包含stdbool.h哟。
其逻辑也是非常简单,首先我们判断传过来的是有效的地址,这一步过后,如果栈顶的数据个数恰好为0,就说明栈是空的。
1.2.6 出栈--栈顶操作
有了上面的判空操作我们很容易就写出了出栈操作。
// 出栈--栈顶
void STPop(ST* ps)
{
assert(!STEmpty(ps));
ps->top--;
}
即使代码再简单我们也要谨慎,测试一下:

三个有效的数据,也就是 1 2 3.
1.2.7 取栈顶元素
// 取栈顶元素
STDataType STTop(ST* ps)
{
assert(!STEmpty(ps));
return ps->arr[ps->top - 1];
}
同样的,要保证栈顶是有元素的。然后直接返回栈顶元素即可。
1.2.8 获取栈中元素的个数
// 获取栈中元素的个数
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
这就是实现栈的代码了。
1.2.9 全部代码
// 使用库函数头文件的包含
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
// 定义栈的结构
typedef int STDataType; // 因为数据不是只有int类型。我们重命名一下。
typedef struct Stack
{
STDataType* arr;
int top; // 栈中有效数据的个数
int capacity; // 栈的空间大小
}ST; // 不想多次写struct,命名为ST
// 栈的初始化
void STInit(ST* ps);
// 栈的销毁
void STDestroy(ST* ps);
// 入栈--栈顶
void STPush(ST* ps, STDataType x);
// 判空操作
bool STEmpty(ST* ps);
// 出栈--栈顶
void STPop(ST* ps);
// 取栈顶元素
STDataType STTop(ST* ps);
// 获取栈中元素的个数
int STSize(ST* ps);
// Stack.c
#include "Stack.h" // 包含头文件
// 栈的初始化
void STInit(ST* ps)
{
ps->arr = NULL;
ps->top = ps->capacity = 0;
}
// 栈的销毁
void STDestroy(ST* ps)
{
if (ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->top = ps->capacity = 0;
}
// 入栈--栈顶
void STPush(ST* ps, STDataType x)
{
// 判断空间是否足够
if (ps->top == ps->capacity)
{
// 增容
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
// 空间足够
ps->arr[ps->top++] = x;
}
// 判空操作
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
// 出栈--栈顶
void STPop(ST* ps)
{
assert(!STEmpty(ps));
ps->top--;
}
// 取栈顶元素
STDataType STTop(ST* ps)
{
assert(!STEmpty(ps));
return ps->arr[ps->top - 1];
}
// 获取栈中元素的个数
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
#include "Stack.h"
void test01()
{
ST st;
STInit(&st);
STPush(&st, 1);
STPush(&st, 2);
STPush(&st, 3);
STPush(&st, 4);
//STPop(&st);
}
int main()
{
test01();
return 0;
}
1.3 算法题:有效的括号
点击蓝色字体可以进入题目网页。
老样子,我们先阅读题目,并画图找到思路:

现在我们开始写代码:由于题目中只给出了函数,并未实现栈,所以我们先把栈实现的代码复制过去,实现一个栈,然后再进行后续操作。

这里就是完整的代码,前面的数据类型记得改为char类型,因为这里使用的是字符类型的数据而不是int类型的数据。其他的我们在思路中已经明了。
总而言之,这里的代码看似非常长,其实是在栈的结构和使用方法比较长而已,实际上实现这个方法的代码是很少的,其次就是这道题的解题思路非常简单,所以代码的实现就是非常简单的,唯一要注意的就是,当我们进行取栈顶元素的时候需要判断是否是空栈。
2. 队列
2.1 概念与结构
概念:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的性质
入队列:在队尾进行插入数据的操作
出队列:在对头进行删除数据的操作
以下是队列的结构示意图:

队列底层结构的选择:链表
首先我们来看一下数组:

然后是链表:

目前看时间复杂度是差不多的,但是我们可以优化一下,数组这边是不好优化的,但是链表是可以优化的,我们可以定义一个始终指向尾部的指针,然后进行尾插,时间复杂度就为O(1),省去了寻找尾部的循环,至于中间节点我我们是不关注的。
总而言之,我们选择链表。
2.2 队列的实现
2.2.1 定义队列的结构
由于我们在队列中要实现头指针和尾指针,同时他俩都是指向结点的指针,所以我们还需要定义一个结点的结构,命名为QueueNode,同时结点的数据类型不是一成不变的,所以我们把数据类型重新命名为QDataType。然后需要一个指向下一个结点的指针。以上是结点结构的定义,其中多次使用typedef这个关键字。
现在是队列的结构,只需要定义两个指针,一个是指向头结点的指针,另一个是指向尾结点的指针。这两个指针的数据类型是QueueNode。
以下是队列结构的代码:
typedef int QDataType;
// 定义结点的结构
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
// 定义队列的结构
typedef struct Queue
{
QueueNode* phead; // 指向对头结点的指针
QueueNode* ptail; // 指向队尾结点的指针
int size; // 队列中有效数据的个数
}Queue;
2.2.2 队列的初始化
队列的初始化就特别简单了,只有两个指针,分别置为空就行。
代码如下:
// 队列的初始化
void QueueInit(Queue* pq)
{
// 判断传入是否为空
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
虽然代码简单,但是我们还是需要测试一下。

2.2.3 入队--队尾
接下来 我们进行入队列操作。
首先我们要申请一个结点大小的空间。记住这是结点大小的空间,我们有两个结构体,一定不要搞错。我们通过malloc这一关键字来进行申请空间的操作。申请完空间我们进行判断,空间是否申请成功,如果申请失败我们就使用perror这一步关键字打印申请失败,然后代码直接退出。申请成功后,我们对结点的内容进行初始化,data初始化为x,next指针指向空。
以上是申请结点大小的空间的操作,默认成功后,我们对队列进行数据的插入操作,这里有两种情况,如果队列为空的话,我们让对头和队尾都指向新的结点。如果队列非空,我们使队尾的next指针指向申请的结点,然后再使队尾指针指向队尾的next指针。
代码如下:
// 入队--对头
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
// 申请一个结点大小的空间
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
// 如果队列为空
if (pq->phead == NULL)
{
pq->phead = pq->ptail = newnode;
}
else // 队列非空
{
pq->ptail->next = newnode;
pq->ptail = pq->ptail->next;
}
pq->size++;
}
测试结果如下:

2.2.4 出队--对头
由于我们删除数据的时候需要判断是否为空列表,所以我们先写一个判断队列是否为空的函数,这个函数的基本逻辑非常简单,首先我们通过assert来提示,最后返回头结点为空的情况。
接下来是出队列操作:我们先判断是否为空,然后分析一下是否为一个结点,因为一个结点的情况会使尾指向结点的指针变为野指针,我们需要单独列出来。多个结点我们就是正常的释放即可。我们先创建一个临时结点来储存头结点的下一个结点,然后释放头结点,让指向头结点的指针,指向刚刚定义的新结点。
// 判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
// 出队--对头
void QueuePop(Queue* pq)
{
assert(!QueueEmpty(pq));
// 如果只有一个结点
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else // 多个结点
{
QueueNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
测试结构如下:

我们第一个插入的值已经被释放掉了。
2.2.5 取对头元素、取队尾元素
这个特别简单,就只放代码。
//取队头数据
QDataType QueueFront(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
2.2.6 销毁队列
这和单链表的销毁是差不多的,也不进行解释。
// 销毁队列
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->phead = pq->ptail = NULL;
}
2.2.7 全部代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType;
// 定义结点的结构
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
// 定义队列的结构
typedef struct Queue
{
QueueNode* phead; // 指向对头结点的指针
QueueNode* ptail; // 指向队尾结点的指针
int size; // 队列中有效数据的个数
}Queue;
// 队列的初始化
void QueueInit(Queue* pq);
// 入队--对头
void QueuePush(Queue* pq, QDataType x);
// 出队--对头
void QueuePop(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
// 获取队列中数据的个数
int QueueSize(Queue* pq);
// 销毁队列
void QueueDestory(Queue* pq);
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
// 队列的初始化
void QueueInit(Queue* pq)
{
// 判断传入是否为空
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
// 入队--对头
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
// 申请一个结点大小的空间
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
// 如果队列为空
if (pq->phead == NULL)
{
pq->phead = pq->ptail = newnode;
}
else // 队列非空
{
pq->ptail->next = newnode;
pq->ptail = pq->ptail->next;
}
pq->size++;
}
// 判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
// 出队--对头
void QueuePop(Queue* pq)
{
assert(!QueueEmpty(pq));
// 如果只有一个结点
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else // 多个结点
{
QueueNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
//取队头数据
QDataType QueueFront(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
// 获取队列中数据的个数
int QueueSize(Queue* pq)
{
assert(!QueueEmpty(pq))
reurn pq->size;
}
// 销毁队列
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->phead = pq->ptail = NULL;
}
#include "Queue.h"
void test01()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
}
int main()
{
test01();
return 0;
}
2.3 算法题
2.3.1 用队列实现栈
1.审题,并明确思路。

这就是实现栈的基本操作的思路。

2.使用两个队列完成栈结构的定义
由于题目中代码并未给出队列的一系列操作,所以我们直接把上面的代码复制粘贴在题目代码区开头,然后创建两个队列q1、q2。

这样我们就实现了栈结构的定义。
3. 创建栈,并完成初始化操作
我们首先是申请一个Mystack大小的空间,然后进行初始化操作,我们之前写过队列初始化 操作,所以我们直接使用对应函数,进行初始化操作。

4. 实现栈的圧栈操作
根据我们的思路,圧栈操作就是往不为空的队列中插入数据,所以我们直接判断,然后进行入队列操作。

5. 实现栈的出栈操作
这里我们用三步来完成。
首先是找到不为空的队列。我们先假定q1是空队列,q2为非空队列,然后对q2进行条件选择,如果q2为空队列,那么就把q2变为空队列,q1变为非空队列。
其次是把不为空队列中前size-1个数据挪到空队列中,这里我们需要使用循环,一个一个挪,我们取非空队列对头数据,然后入队列进入空队列,循环到非空队列的最后一个数据,跳出循环。
最后是取对头数据储存在临时变量中,然后出队列操作,返回临时变量。

6. 实现取栈顶元素操作
根据我们的思路,我们知道取栈顶就是返回不为空队列的队尾元素。所以我们直接使用if语句就可以实现取栈顶元素操作。

7. 实现栈的判空操作
这个我们直接使用"与",两个队列都为空,则栈就是空栈。

8. 实现栈的销毁操作
销毁操作就是把我们申请的空间释放掉,我们这里一共进行了三次空间的申请,我们一一释放。两个队列的释放我们有现成的代码,直接调用对应函数就行,然后我们释放pst(申请的栈的空间)。

9. 测试是否通过

通过了。其实刚开始测试的时候问题贼多嘞,一步一步改掉错误的代码,才会得到正确的代码。
10. 全部的代码
typedef int QDataType;
// 定义结点的结构
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
// 定义队列的结构
typedef struct Queue
{
QueueNode* phead; // 指向对头结点的指针
QueueNode* ptail; // 指向队尾结点的指针
int size; // 队列中有效数据的个数
}Queue;
// 队列的初始化
void QueueInit(Queue* pq)
{
// 判断传入是否为空
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
// 入队--对头
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
// 申请一个结点大小的空间
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
// 如果队列为空
if (pq->phead == NULL)
{
pq->phead = pq->ptail = newnode;
}
else // 队列非空
{
pq->ptail->next = newnode;
pq->ptail = pq->ptail->next;
}
pq->size++;
}
// 判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
// 出队--对头
void QueuePop(Queue* pq)
{
assert(!QueueEmpty(pq));
// 如果只有一个结点
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else // 多个结点
{
QueueNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
//取队头数据
QDataType QueueFront(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
// 销毁队列
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->phead = pq->ptail = NULL;
}
// 获取队列中数据的个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
// -----------以上是队列的常见的方法---------------
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
// 申请一个Mystack大小的空间
MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
if(pst == NULL)
{
perror("malloc fail!");
exit(1);
}
QueueInit(&pst->q1);
QueueInit(&pst->q2);
return pst;
}
void myStackPush(MyStack* obj, int x) {
// 往不为空的队列中插入数据
if(QueueEmpty(&obj->q1))
{
QueuePush(&obj->q2,x);
}
else
{
QueuePush(&obj->q1,x);
}
}
int myStackPop(MyStack* obj) {
// 找到不为空的队列
Queue* emp = &obj->q1;
Queue* nonemp = &obj->q2;
if(QueueEmpty(&obj->q2))
{
emp = &obj->q2;
nonemp = &obj->q1;
}
// 把不为空队列中前size-1个数据挪到空队列中
while(QueueSize(nonemp) > 1)
{
// 取对头入另一个队列
QueuePush(emp, QueueFront(nonemp));
// 出队列
QueuePop(nonemp);
}
int pop = QueueFront(nonemp);
QueuePop(nonemp);
return pop;
}
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) {
QueueDestory(&obj->q1);
QueueDestory(&obj->q2);
free(obj);
obj = NULL;
}
2.3.2 用栈实现队列
1. 审题,明确思路

这个是我们的思路结论。

为什么会有这样的思路?我们来具体看一下:

2. 使用两个栈完成队列结构的定义
由于题目中没有给出栈的一系列代码,我们直接复制上面的栈实现代码。

我们创建两个栈,一个用来入数据,一个用来出数据。
3. 创建队列,并完成初始化操作
我们先申请一个MyQueue大小的空间,然后对两个栈进行初始化操作。

4. 实现入队列操作
我们直接往pushST这个栈中插入数据即可。

5. 实现出队列操作
如果popST为空,我们就把pushST中的数据导入到popST中,也就是pushST取栈顶数据,放入到popST中,然后在出栈,循环直到pushST为空

6. 实现取对头数据操作
这个和出队列操作是一样的,只是不进行出栈操作。

7. 实现队列的判空操作
两个栈都是空的即可。

8. 实现队列的销毁操作

9. 测试是否通过

10. 全部代码
// 定义栈的结构
typedef int STDataType; // 因为数据不是只有int类型。我们重命名一下。
typedef struct Stack
{
STDataType* arr;
int top; // 栈中有效数据的个数
int capacity; // 栈的空间大小
}ST; // 不想多次写struct,命名为ST
// 栈的初始化
void STInit(ST* ps)
{
ps->arr = NULL;
ps->top = ps->capacity = 0;
}
// 栈的销毁
void STDestroy(ST* ps)
{
if (ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->top = ps->capacity = 0;
}
// 入栈--栈顶
void STPush(ST* ps, STDataType x)
{
// 判断空间是否足够
if (ps->top == ps->capacity)
{
// 增容
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
// 空间足够
ps->arr[ps->top++] = x;
}
// 判空操作
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
// 出栈--栈顶
void STPop(ST* ps)
{
assert(!STEmpty(ps));
ps->top--;
}
// 取栈顶元素
STDataType STTop(ST* ps)
{
assert(!STEmpty(ps));
return ps->arr[ps->top - 1];
}
// 获取栈中元素的个数
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
//-------------以上是栈的一系列代码-------------------
typedef struct {
ST pushST;
ST popST;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* pq = (MyQueue*)malloc(sizeof(MyQueue));
STInit(&pq->pushST);
STInit(&pq->popST);
return pq;
}
void myQueuePush(MyQueue* obj, int x) {
// 往pushST这个栈中插入数据
STPush(&obj->pushST, x);
}
int myQueuePop(MyQueue* obj) {
// 如果popST为空,就把pushST中的数据导入到popST中
if(STEmpty(&obj->popST))
{
while(!STEmpty(&obj->pushST))
{
STPush(&obj->popST, STTop(&obj->pushST));
STPop(&obj->pushST);
}
}
// 如果popST不为空
int top = STTop(&obj->popST);
STPop(&obj->popST);
return top;
}
int myQueuePeek(MyQueue* obj) {
// 如果popST为空,就把pushST中的数据导入到popST中
if(STEmpty(&obj->popST))
{
while(!STEmpty(&obj->pushST))
{
STPush(&obj->popST, STTop(&obj->pushST));
STPop(&obj->pushST);
}
}
// 如果popST不为空
int top = STTop(&obj->popST);
return top;
}
bool myQueueEmpty(MyQueue* obj) {
return STEmpty(&obj->pushST) && STEmpty(&obj->popST);
}
void myQueueFree(MyQueue* obj) {
STDestroy(&obj->pushST);
STDestroy(&obj->popST);
free(obj);
obj = NULL;
}
/**
* 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);
*/
以上就是本小节的全部内容。当然,算法题远不止这几道,感兴趣的可以在刷题网站上进行解题。其实这个过程中,还有很多大神的理解在评论区,相信大家会受益很多在各大刷题网站的解题过程中。