第三章 数据结构与算法——栈和队列

目录

栈和队列被称为插入和删除受限制的线性表。

🍁一、栈的基本概念

🌕(一)栈的概念:

🍁二、栈的顺序存储结构(栈的实现)

🌕(一)、特点

🌕(二)、静态实现

🌕(三)、动态实现(优先)

⭐️ (三.1)、定义

⭐️(三.2)、初始化

⭐️(三.3)、销毁

⭐️(三.4)、入栈(插入元素)

⭐️(三.5)、出栈

⭐️(三.6)、获取栈的元素个数

⭐️(三.7)、判断是否为栈空

⭐️(三.8)、获取栈顶元素

⭐️(三.9)、遍历栈

🍁三、经典例题——有效的括号

(一)、题目

(二)、解答

(三)、代码实现



栈和队列被称为插入和删除受限制的线性表。

🍁一、栈的基本概念

🌕(一)栈的概念:

①:栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。 栈中的数据元素遵守后进先出 LIFO Last In First Out )的原则。
②:压栈:栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶
③:出栈:栈的删除操作叫做出栈。 出数据也在栈顶

🍁二、栈的顺序存储结构(栈的实现)

注意:栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

🌕(一)、特点

①:栈的顺序存储结构简称“顺序栈”;

②:顺序栈利用一组连续地址的存储单元(数组)依次存放从栈低到栈顶的数据元素,通常用一维数组存放栈的元素,同时设top“箭头”(有些地方称top为“指针”,但不是C语言中的指针,top是一个整型变量,所以为了避免混淆,小编称其为“箭头”)指示栈顶元素的当前位置,空栈时top值为0。

🌕(二)、静态实现

因为栈是特殊的线性表,所以栈的实现和线性表差不多,只是多了一个top“箭头”;

#define N 10

typedef struct Stack
{
	int data[N];
	int top;
}Stack;

静态实现有一个很大的缺点就是扩容不方便,所以具体实现我们会使用动态实现;

🌕(三)、动态实现(优先)

因为线性表在之前的文章已经实现过了 ,而栈的实现和线性表差不多,所以小编就快速实现;具体操作可以参考小编之前的文章;

⭐️ (三.1)、定义

在原基础上多了一个top箭头用于指向栈顶位置;

typedef int DataType;
typedef struct Stack
{
	DataType* head;
	int top;//指向栈顶
	int catacity;//现有最大空间数
}ST;
⭐️(三.2)、初始化
//初始化
void STinit(ST* ps)
{
	assert(ps);
	//刚开始没有元素,所以top指向0
	ps->top = 0;
	ps->catacity = 0;
	ps->a = NULL;
}
⭐️(三.3)、销毁
//销毁
void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->top = 0;
	ps->catacity = 0;
}
⭐️(三.4)、入栈(插入元素)

因为栈是只能在栈顶进行操作,所以不存在头插尾插的概念,插入元素就叫“入栈”。

而且也不会说在某个位置插入元素,所以我们扩容不需要单独创建一个函数,这里只有入栈一个操作涉及扩容。

//入栈
void STPush(ST* ps, DataType x)
{
	assert(ps);
	//空间满了进行增容
	if (ps->top == ps->catacity)
	{
		//第一次catacity值为0,所以判断一下给予赋值
		int newCatacity = (ps->catacity == 0 ? 4 : ps->catacity * 2);
		//使用realloc函数进行增容,刚开始a为NULL的话realloc函数的作用和malloc相同
		DataType* tmp = realloc(ps->a, sizeof(DataType) * newCatacity);
		//检查是否增容成功
		if (tmp == NULL)
		{
			perror("realloc");
			return;
		}
		ps->a = tmp;
		ps->catacity = newCatacity;
	}
	//插入
	ps->a[ps->top] = x;
	ps->top++;
}

⭐️(三.5)、出栈

出栈非常简单,只需要将top值减1即可;

因为top值减1,则下次就不会访问到出栈的元素,而之后入栈也会将其覆盖,所以只需要将top值减1,但是,首先我们要检查一下是否为空栈,如果top==0,则为空栈,此时需要提示出栈失败;

//出栈
void SLPop(ST* ps)
{
	assert(ps);
	//ps->top==0时为空栈
	if (0 == ps->top)
	{
		printf("栈为空,出栈失败!\n");
		return;
	}
	//出栈
	--ps->top;
}

⭐️(三.6)、获取栈的元素个数
//获取栈的元素个数
int STSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

⭐️(三.7)、判断是否为栈空
//判断是否为栈空
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

注意:后面(三.6)和(三.7)这两个操作只有一句代码,有的小伙伴就有疑问,为什么不直接使用,而是要去封装成一个函数?

答:对于数据结构,我们尽量不要自己去访问结构的数据,尽量提供这些接口函数去使用,因为对于我们程序员才知道top指向栈顶,所以可以使用top,但对于用户角度,他根本不知道top代表什么,他只知道通过这些函数接口去实现操作。

⭐️(三.8)、获取栈顶元素
//获取栈顶元素
DataType STTop(ST* ps)
{
	assert(ps);

	if (0 == ps->top)
	{
		printf("栈为空,获取失败!\n");
		return NULL;
	}
    return ps->a[ps->top - 1];
}
⭐️(三.9)、遍历栈

值得注意的是,遍历栈时,为了体现栈的后进先出的特性,我们不会对遍历操作重新封装成函数,而是边遍历边出栈,遍历完后,栈变为空栈,这和后期栈的使用场景有着特别的联系。

🍁三、经典例题——有效的括号

(一)、题目

(二)、解答

首先我们要知道这道题必须要确定:

1.括号的数量匹配

2.括号的顺序要匹配

所以我们有以下思路:
①:遇到左括号我们就入栈

②:遇到右括号,我们就出栈,将栈顶的左括号拿出来进行匹配;

如果可以匹配,则继续判断下一个符号,是左括号就进栈,是右括号就出栈匹配;

如果不匹配,则返回false。

好处:

①:这样可以确保每一个左括号都是与其相邻的最近的右括号相匹配;

②:到最后,

        如果栈为空时,外面还有右括号,则数量不匹配;

        如果栈不为空,但外面的符号为空,则数量不匹配,返回false;

(三)、代码实现

bool isValid(char* s)
{
	ST st;
	//初始化
	STinit(&st);

	//匹配
	while (*s)
	{
		switch (*s)
		{
			//左括号入栈
			case '(':
			case '[':
			case '{':
				STPush(&st, *s);
				break;
			//右括号出栈进行匹配
			//我们只需要判断不匹配的情况,因为匹配的时候默认继续,不匹配则直接返回false
			case ')':
			case ']':
			case '}':
				//如果有右括号,但此时栈为空栈,所以此时数量不匹配
				if (STEmpty(&st))
				{
					STDestroy(&st);
					return false;
				}
				//出栈匹配
				DataType topval = STTop(&st);
				STPop(&st);
				//不匹配
				if ((*s == '(' && topval != '(') ||
					(*s == '[' && topval != '[') ||
					(*s == '{' && topval != '{')  )
				{
					STDestroy(&st);
					return false;
				}
				break;
		}
		s++;
	}
	//当外面的符号匹配完后,看栈是否为空,不为空则数量不匹配。
	//布尔值:0表示false,非0表示true
	bool ret = STEmpty(&st);
	STDestroy(&st);
	return ret;
}

🍁四、顺序栈测试源代码

(一)、main.c

#include "stack.h"
void test1()
{
	ST st;
	STinit(&st);
	STPush(&st, 1);
	STPush(&st, 1);
	STPush(&st, 1);
	STPush(&st, 1);
	STPush(&st, 1);
	STPush(&st, 1);

	while (!STEmpty(&st))
	{
		printf("%d ", st.a[st.top-1]);
		STPop(&st);
	}
	printf("\n");
	STDestroy(&st);
}

bool isValid(char* s)
{
	ST st;
	//初始化
	STinit(&st);

	//匹配
	while (*s)
	{
		switch (*s)
		{
			//左括号入栈
			case '(':
			case '[':
			case '{':
				STPush(&st, *s);
				break;
			//右括号出栈进行匹配
			//我们只需要判断不匹配的情况,因为匹配的时候默认继续,不匹配则直接返回false
			case ')':
			case ']':
			case '}':
				//如果有右括号,但此时栈为空栈,所以此时数量不匹配
				if (STEmpty(&st))
				{
					STDestroy(&st);
					return false;
				}
				//出栈匹配
				DataType topval = STTop(&st);
				STPop(&st);
				//不匹配
				if ((*s == '(' && topval != '(') ||
					(*s == '[' && topval != '[') ||
					(*s == '{' && topval != '{')  )
				{
					STDestroy(&st);
					return false;
				}
				break;
		}
		s++;
	}
	//当外面的符号匹配完后,看栈是否为空,不为空则数量不匹配。
	//布尔值:0表示false,非0表示true
	bool ret = STEmpty(&st);
	STDestroy(&st);
	return ret;
}

int main()
{
	test1();
	
	//例题"有效的括号"
	char* arr1 = "((([[{}]])))";
	char* arr2 = ")";
	char* arr3 = "(";
	bool is1 = isValid(arr1);
	bool is2 = isValid(arr2);
	bool is3 = isValid(arr3);
	//因为c语言中没有bool值的说法,所以打印时可以用一个条件运算符辅助
	printf("%s\n", (is1 ? "ture" : "false"));
	printf("%s\n", (is2 ? "ture" : "false"));
	printf("%s\n", (is3 ? "ture" : "false"));
	return 0;
}

(二)、Stack.c

#include "stack.h"
//初始化
void STinit(ST* ps)
{
	assert(ps);
	//刚开始没有元素,所以top指向0
	ps->top = 0;
	ps->catacity = 0;
	ps->a = NULL;
}

//销毁
void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->top = 0;
	ps->catacity = 0;
}

//入栈
void STPush(ST* ps, DataType x)
{
	assert(ps);
	//空间满了进行增容
	if (ps->top == ps->catacity)
	{
		//第一次catacity值为0,所以判断一下给予赋值
		int newCatacity = (ps->catacity == 0 ? 4 : ps->catacity * 2);
		//使用realloc函数进行增容,刚开始a为NULL的话realloc函数的作用和malloc相同
		DataType* tmp = realloc(ps->a, sizeof(DataType) * newCatacity);
		//检查是否增容成功
		if (tmp == NULL)
		{
			perror("realloc");
			return;
		}
		ps->a = tmp;
		ps->catacity = newCatacity;
	}
	//插入
	ps->a[ps->top] = x;
	ps->top++;
}

//出栈
void STPop(ST* ps)
{
	assert(ps);
	//ps->top==0时为空栈
	if (0 == ps->top)
	{
		printf("栈为空,出栈失败!\n");
		return;
	}
	//出栈
	--ps->top;
}

//获取栈的元素个数
int STSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

//判断是否为栈空
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

//获取栈顶元素
DataType STTop(ST* ps)
{
	assert(ps);

	if (0 == ps->top)
	{
		printf("栈为空,获取失败!\n");
		return ps->a[ps->top - 1];
	}
}

(三)、Stack.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

//typedef struct Stack
//{
//	int data[N];
//	int top;
//}Stack;

typedef int DataType;
typedef struct Stack
{
	DataType* a;
	int top;//指向栈顶
	int catacity;//现有空间大小
}ST;

//初始化
void STinit(ST*ps);

//销毁
void STDestroy(ST* ps);

//入栈
void STPush(ST* ps, DataType x);

//出栈
void STPop(ST* ps);

//获取栈的元素个数
int STSize(ST* ps);

//判断是否为栈空
bool STEmpty(ST* ps);

//获取栈顶元素
DataType STTop(ST* ps);

🍁五、栈的链式存储结构

1.栈的链式存储结构简称“链式栈”;

2.其组织形式与单链表相似,链表的尾部结点是栈底,链表的头部结点是栈顶;

3.由于只在链栈的头部进行操作,所以链栈没有必要设置头结点。

本次知识到此结束,希望对你有所帮助!

  • 38
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 66
    评论
评论 66
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成工小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值