【初阶数据结构】栈和队列

目录

1.栈

[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 用队列实现栈)

1.审题,并明确思路。

2.使用两个队列完成栈结构的定义

[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);
*/

以上就是本小节的全部内容。当然,算法题远不止这几道,感兴趣的可以在刷题网站上进行解题。其实这个过程中,还有很多大神的理解在评论区,相信大家会受益很多在各大刷题网站的解题过程中。

相关推荐
长安er3 小时前
LeetCode136/169/75/31/287 算法技巧题核心笔记
数据结构·算法·leetcode·链表·双指针
_w_z_j_3 小时前
最小栈(栈)
数据结构
fufu03114 小时前
Linux环境下的C语言编程(四十八)
数据结构·算法·排序算法
思成Codes5 小时前
数据结构:基础线段树——线段树系列(提供模板)
数据结构·算法
卜锦元7 小时前
Golang后端性能优化手册(第三章:代码层面性能优化)
开发语言·数据结构·后端·算法·性能优化·golang
2401_841495648 小时前
【LeetCode刷题】打家劫舍
数据结构·python·算法·leetcode·动态规划·数组·传统dp数组
冰西瓜6009 小时前
STL——vector
数据结构·c++·算法
Bdygsl9 小时前
数据结构 —— 双向循环链表
数据结构·链表
程序员阿鹏9 小时前
怎么理解削峰填谷?
java·开发语言·数据结构·spring·zookeeper·rabbitmq·rab