栈的基本用法

 在前面的文章中,我们阐述了单链表以及双向链表的具体用法,本篇文章将对数据结构中的另一成员——栈,进行系统性的介绍,相比于之前的链表以及单链表,栈相对简单,易于理解。

栈的概念及结构

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

栈的先进后出原理,类比来说就是和弹夹一样,先压进来的子弹最后打出去,最后压进来的子弹,先打出去。

原理如图所示

栈的实现

要实现栈的结构,我们目前有三种合适的做法,单链表,双向链表,数组。

这三种结构都能让我们实现栈的结构

那么哪一种才是最高效的呢

首先我们可以排除双向链表,因为既然单链表都可以实现栈结构,那么没必要再多创建一个指针去实现双向链表的栈结构

而单链表和数组都可以高效的实现栈结构,但是各有各的优点,

单链表没有容量的限制,可以做到随用随取

而数组的缓存利用率高

我们这里用数组进行实现

栈的基本结构

用数组实现栈,那么结构就和顺序表中的结构一致

我们需要数组的指针,并动态开辟内存

需要栈顶元素来确定栈顶的位置,并且需要容量变量,及时的开辟空间

typedef int STDataType;
typedef struct Stack
{
 STDataType* _a;
 int _top; // 栈顶
 int _capacity; // 容量
}Stack;

栈的基本函数

我们要实现的功能函数,如图所示

// 初始化栈
void StackInit(Stack* ps); 
// 入栈
void StackPush(Stack* ps, STDataType data); 
// 出栈
void StackPop(Stack* ps); 
// 获取栈顶元素
STDataType StackTop(Stack* ps); 
// 获取栈中有效元素个数
int StackSize(Stack* ps); 
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps); 
// 销毁栈
void StackDestroy(Stack* ps); 

我们的函数中每个都用assert判断ps是否为空,这是为了保障函数的安全,利用断言直接提示

接下来我们一一进行具体介绍

初始化栈

栈的初始化很好理解,只需要将数组指针置为NULL,将capacity置0

值得一提的是top的初始化

top的初始化分为两种,一种是-1,一种是0

初始化为-1则 top指向栈顶的数据

初始化为0 则 top指向栈顶的下一个数据

我的代码中使用top初始化为-1的情况

代码如下

void STInit(ST* pst)
{
	assert(pst);
	pst->a=(STDataType*)malloc(sizeof(STDataType) * 4);
	pst->capacity = 0;
	//top指向栈顶数据
	pst->top = -1;


	//top指向栈顶数据的下一个
	//pst->top = 0;
}
入栈

由于栈是先进后出型,则只有一种插入方式,即从栈顶插入

我们首先要考虑的就是容量问题,我们可以用capacity和top进行比较,由于我们top初始化为-1,则top就是总数据数量-1,所以如果capacity-1 与top 相等,则说明容量已满,需要扩容

而扩容我们也需要分为两种情况,一种是容量为0,即初始化状态,另一种就是真正容量满的情况

第一种我们直接给4个数据的大小,之后如果容量满了就按二倍进行扩容

这个用三目操作符即可完成扩容数量的确定

再利用realloc进行扩容

最后记得将top先自增再赋值

如果top初始化为0,则先赋值,再自增。

void STPush(ST* pst, STDataType x)
{
	assert(pst);
	if (pst->top + 1 == pst->capacity)
	{
		int newcapacity =(pst->capacity == 0 )?4: 2 * pst->capacity;
		STDataType* tmp = (STDataType*)realloc(pst->a,newcapacity*sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail\n");
			return;
		}
		pst->a = tmp;
		pst->capacity = newcapacity;
	}
	pst->top++;
	pst->a[pst->top] = x;
}
出栈

出栈极其简单,即将top自减1即可,这样将无法访问到删除的元素,或者会被下一个新元素所顶替

代码如下:

void STPop(ST* pst)
{
	assert(pst);
	pst->top--;
}
获得栈顶元素

首先我们需要判断,栈顶是否有元素,用断言进行判断

再返回栈顶元素即可

代码如下:

int STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > -1);
	return pst->a[pst->top];
}
获得有效元素个数

即top初始化为-1,则返回栈顶下标top+1

若top初始化为0,则直接返回top即可

代码如下:

int STSize(ST* pst)
{
	assert(pst);
	return pst->top + 1;
}
检测栈是否为空

直接用栈顶元素下标与初始化进行比较

相同则返回true,反正返回false

代码如下:

bool STEmpty(ST* pst)
{
	assert(pst);
	return pst->top == -1 ? true : false;
}
栈的销毁

利用free将其数组指针空间销毁即可,再将其他变量置为相应的空值

void STDestroy(ST* pst)
{
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->capacity = 0;
	pst->top = -1;
}

以上即栈函数的实现,比较简单,大家好好掌握

是否该将仅一句话的函数删除

这个问题其实也是我之前想提的问题,如果函数仅仅一句话,那么是不是不需要写函数呢,例如出栈函数,仅仅用top--就能实现,为什么还要用函数封装呢?

这个问题,不能站到自己的视角去看,比如top的初始化就有两种方法,如果我将其初始化为-1,而你当做初始化为0,那么就可能在使用过程中出现错误,如果封装在函数中,我们有assert断言函数帮我们进行保护,则不需要进行担心,因此尽管他只有一句话,但仍有可能引起歧义,所以我们将其封装为函数进行使用。

栈的打印

利用判断为空函数,出栈函数,利用while循环依次打印即可

代码如下

	while (!STEmpty(&s))
	{
		printf("%d\n", STTop(&s));
		STPop(&s);
	}

总结

总体来说,栈的实现是比较简单的,相比于之前的单链表和双向链表,由于栈是由顺序表实现,所以我们感觉更为简单,总而言之,如果你的顺序表掌握的不错,那么栈你一定可以理解,希望这篇文章对你有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值