初阶数据结构【栈及其接口的实现】

目录


前言

前面我们学习并实现了顺序表与链表的接口,顺序表与链表都是线性表的一种,即在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的;这期我们继续来介绍一种特殊的线性表------


一、栈的概念及结构

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

  • 压栈 :栈的插入操作叫做进栈/压栈/入栈入数据在栈顶
  • 出栈 :栈的删除操作叫做出栈出数据也在栈顶


二、栈的实现方式

栈的实现一般可以使用数组 或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小;所以这里我们用数组的方式来实现;

  • 数组的方式 实现:
    • 数组的起始作为栈底,栈底在数组的右边;
  • 链表的方式 来实现
    • 如果是双向链表:链表的头和尾都可以作为栈顶:
  • 如果用单链表:由于在出栈的时候我们要寻找下一个栈顶比较麻烦,所以我们只能将栈顶设置在头结点:

这里我们用数组的方式实现:

三、栈的实现

3.1 基本结构

我们用数组的方式实现栈还分为以下两种:

  • 定长的静态栈
  • 可以动态增长的动态栈

在实际项目应用中,当我们知道这个项目一定不会超过一个固定的内存大小(如1000个整形空间),我们就可以使用这种结构:

c 复制代码
#define N 1000

//定长的静态栈
typedef struct Stack
{
	STDataType a[N];
	int _top; //栈顶
}Stack;

反之,我们用动态增长的栈:

c 复制代码
// 支持动态增长的栈
typedef struct Stack
{
	STDataType* _a;
	int _top;  //栈顶指针
	int _capacity; //动态容量
}Stack;

这里我们实现用动态增长的栈

3.2 栈的基本功能接口

栈的初始化

这个接口没什么好说的,不过我们要注意一下capacity的初始值,以便后面入栈的时候方便扩容:

c 复制代码
//栈的初始化
void StackInit(Stack* pst)
{
	assert(pst);
	//这样会给后面扩容造成麻烦
	/*pst->_a = NULL;
	pst->_top = 0;
	pst->_capacity = 0;*/

	pst->_a = (STDataType*)malloc(sizeof(STDataType) * 4);
	pst->_top = 0;
	pst->_capacity = 4;
}

栈的销毁

c 复制代码
//栈的销毁
void StackDestory(Stack* pst)
{
	assert(pst);
	free(pst->_a); //释放开辟的空间
	pst->_a = NULL;
	pst->_top = pst->_capacity = 0;
}

3.3 入栈接口

根据前面顺序表的插入操作我们也可以得知,在入栈之前我们需要进行扩容判断,再进行入栈操作:

c 复制代码
void StackPush(Stack* pst, STDataType x)
{
    //检查是否需要扩容
	if (pst->_top == pst->_capacity)
	{
		pst->_capacity *= 2;
		STDataType* tmp = (STDataType*)realloc(pst->_a, sizeof(STDataType) * pst->_capacity);
		if (tmp == NULL)
		{
			perror("relloc");
			exit(-1);
		}
		else
		{
			pst->_a = tmp;
		}
	}

	pst->_a[pst->_top] = x;
	pst->_top++;
}

这时候有聪明的同学有个问题了:为什么不将这个扩容代码写出一个函数接口呢?其实你实现完整个栈就会发现,只有这会用到空间的扩容的;所以我们不再将其单独开辟一个接口;

3.4 出栈接口

c 复制代码
void StackPop(Stack* pst)
{
    assert(pst);
    assert(pst->_top > 0); //判断一下是否为空栈
	//只需要将top一走,可以不用初始化出栈的值
	//pst->_a[pst->_top] = 0;
	pst->_top--;
}

3.5 栈的其它接口

获取数据的个数接口

我们要清楚top的值就是数据的个数

c 复制代码
//获取数据的个数
int StackSize(Stack* pst)   //考虑到内存和统一性问题我们这里也传一级指针
{
	assert(pst);
	return pst->_top;
}

栈判断是否为空接口

c 复制代码
//判空,1是空,0是非空
int StackEmpty(Stack* pst)
{
	assert(pst);
	//return pst->_top == 0 ? 1 : 0;
	return !pst->_top;     //巧写,更加简便
}

获取栈顶数据接口

c 复制代码
//获取栈顶数据
STDataType StackTop(Stack* pst)
{
	assert(pst);
	assert(pst->_top);

	return pst->_a[pst->_top-1];
}

注:为什么要实现这些简单的接口,直接调用结构体不好吗?

我们要知道每个人实现相同的结构并不是完全相同的,就像这个栈的实现:

我们在初始化的时候默认pst->_top=0,这就导致我们访问栈顶数据是pst->_a[pst->_top-1];

如果有人在初始化的时候默认pst->_top=-1(也是允许的),那我们访问栈顶数据是pst->_a[pst->_top];

在实际开发中就找成许多麻烦了;

3.6 测试

注:细心的同学会发现我们并没有在前面写栈的打印接口,这是因为栈必须满足先进后出的规律,我们在读取栈顶元素候必须要经过出栈才能继续向下读取;

所以我们一般这样打印出栈里的元素:

c 复制代码
while (!StackEmpty(st))
{
	printf("%d ", StackTop(st));
	StackPop(st);   //出栈
}
printf("\n");

我们写出测试函数:

c 复制代码
void Test1() {
	Stack st;
	StackInit(&st); //初始化
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);

	//遍历
	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));
		StackPop(&st);   //出栈
	}
	printf("\n");

	printf("%d ", StackSize(&st)); //打印大小,已经全部出栈,size为0;
	StackDestory(&st);  //销毁
}

运行Test1函数结果:

符合预想结果,测试通过。


总结

这期我们用C语言实现了栈的几个基本接口,我们有了前面的基础,我们发现这并不是很难;下期见!

26考研加油!


相关推荐
YYHYJX几秒前
C#学习笔记 --- 简单应用
开发语言·学习·c#
Clockwiseee4 分钟前
JAVA多线程学习
java·开发语言·学习
Nobita Chen5 分钟前
Python实现windows自动关机
开发语言·windows·python
码路刺客5 分钟前
一学就废|Python基础碎片,OS模块
开发语言·python
z千鑫13 分钟前
【Python】Python之Selenium基础教程+实战demo:提升你的测试+测试数据构造的效率!
开发语言·python·selenium
aiee19 分钟前
GO通过SMTP协议发送邮件
开发语言·后端·golang
w(゚Д゚)w吓洗宝宝了25 分钟前
List详解 - 双向链表的操作
数据结构·c++·链表·list
云浩舟30 分钟前
Golang并发读取json文件数据并写入oracle数据库的项目实践
开发语言·数据库·golang
walkskyer36 分钟前
Golang strconv包详解:高效类型转换实战
android·开发语言·golang
我命由我1234539 分钟前
Android Room 构建问题:There are multiple good constructors
android·开发语言·java-ee·android studio·android jetpack·android-studio·android runtime