【数据结构】栈及其实现

目录

前言

什么是栈?

栈的定义及初始化

栈的定义

栈的初始化

栈的判空

栈顶压栈

栈顶出栈

栈的数据个数

栈的销毁

完整代码

总结


前言

  • 学了相当长一段时间的链表,总算是跨过了一个阶段。从今天开始我们将进入栈和队列的学习,相比于链表可以说是有手就行的难度,所以各位老铁可以轻松一波啦,不必太担心。
  • 栈和队列我们分开来讲,本篇主要详解栈及其实现
  • 栈的特点是先进后出,后进先出(LIFO),这一特点以及进一步运用(单调栈)是一些算法题的突破口,后续我也会分享LeetCode的一些题,希望大家关注点点,以免错过哦!

什么是栈?

我们前面学习的顺序表(数组)以及链表可以说是数据结构的物理结构,而栈和队列则是数据结构的逻辑结构。

这是什么意思呢?

如果把数据结构比作活生生的人,那么物理结构就是人的血肉和骨骼,看得见摸得着,在内存中也实际存储这;逻辑结构则是思想灵魂,看不见摸不着,它依赖于物理结构而存在。

简而言之,栈和队列其实都是由数组或者链表实现的,不过在此基础上加了一些限制条件以适用于不同场景,将其抽化成了一个数据结构

栈的定义及初始化

栈的定义

  • 我们采用数组的方式实现栈,因为数组比链表的缓存命中率更高(粗暴点来说cpu访问的速度会更快,访问没用的信息的数量会减少,这里就不具体展开了)。
  • 还是定义一个结构体来实现栈,分别为数组(此处应该用malloc,这样不够的时候可以扩容)
  • top用来表示栈顶,因为栈只在栈顶一段进行操作
  • capacity用来记录栈的容量。
typedef int STDataType;
typedef struct Stack
{
	int top;
	int capacity;
	STDataType* a;
}ST;

栈的初始化

  • 在进行插入的时候再开辟空间,为了防止野指针问题,将a置为NULL,capacity显而易见应该也为0
  • 关于top就有一点讲究,如果top置为0则指向最后一个数据的下一个,如果置为-1指向最后一个有效数据。因为每次插入数据后top才++,不是很理解的小伙伴们可以画一个数组模拟一下。
  • 我们这里就将top初始化为0,其也间接体现了存储数据的个数
  • 当然传进来的栈不能为空指针啦,不然相当于栈都还没创建,老生常谈的问题了,每个函数接口前都要assert暴力断言一下,下文就不赘述了

以下为函数接口实现:
 

void StackInit(ST* ps)
{
	assert(ps);

	ps->capacity = ps->top = 0;//指向最后一个数据的下一个
	ps->a = NULL;
}

栈的判空

上文提到top间接表示了存储数据的个数,所以我们直接判断top是否为0即可。

以下为函数接口实现:

bool StackEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}

栈顶压栈

  • 在数据结构中的学习当中,插入数据往往需要考虑扩容的问题,此处也是。当top==capacityd的时候我们就扩容
  • 插入后top需要++一下,而扩容后capacity则需要更新(易忘)
  • 这里应该包括了当top==0的情况,因为初始化的时候没有开辟空间

【数据结构】栈及其实现_第1张图片

以下为函数接口实现:

void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	
	//当内存满了则需要扩容
	if (ps->top== ps->capacity)
	{
		int newcapacity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
		STDataType *tmp=(STDataType*)realloc(ps->a, newcapacity);
		if (tmp == NULL)
		{
			perror("malloc fail\n");
			return;
		}
		else
		{
			ps->capacity = newcapacity;
			ps->a = tmp;
		}
	}
	//没满就直接压栈即可
	ps->a[ps->top] = x;
	ps->top++;
}

栈顶出栈

  • 删除数据,往往需要考虑数据结构是否为空的情况,这里就可以用到上文的StackEmpty函数接口了,增加了代码的可读性
  • 和顺序表删除数据一样,只需要top--即可,并不需要真的抹除其数据,下次入栈的时候新的数据就会覆盖原来的数据。
  • capacity不需要变化,因为其表示栈的容量

以下为函数接口实现:

void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	ps->top--;
}

栈的数据个数

  • 上文说了top初始化为0的时候代表数据个数,所以这里直接返回top即可
  • 数据个数一定为整数,所以用int即可,unsigned int也行

以下为函数接口实现:

int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

获取栈顶元素

  • 函数返回类型应该为数组存储的数据类型,而不要思维固化的写为int
  • 因为top初始化为0,指向最后一个数据的下一个位置,所以我们要返回top-1位置的数据
  • 当栈为空的时候肯定获取不了啦,所以这里还是assert断言爆打

以下为函数接口实现:

STDataType StackTop(ST* ps)//获得栈顶元素
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->a[ps->top-1];
}

栈的销毁

切记不可直接free掉代表栈的结构体就以为把栈销毁了,要先free掉结构体里的数组,然后再free掉代表栈的结构体,不然会内存泄漏!!!

以下为函数接口实现:

void StackDestroy(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->a=NULL;
    ps->top=ps->capacity=0
}

完整代码

void StackInit(ST* ps)
{
	assert(ps);

	ps->capacity = ps->top = 0;//指向最后一个数据的下一个
	ps->a = NULL;
}

bool StackEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}
void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	
	//当内存满了则需要扩容
	if (ps->top== ps->capacity)
	{
		int newcapacity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
		STDataType *tmp=(STDataType*)realloc(ps->a, newcapacity);
		if (tmp == NULL)
		{
			perror("malloc fail\n");
			return;
		}
		else
		{
			ps->capacity = newcapacity;
			ps->a = tmp;
		}
	}
	//没满就直接压栈即可
	ps->a[ps->top] = x;
	ps->top++;
}
void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	ps->top--;
}
STDataType StackTop(ST* ps)//获得栈顶元素
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->a[ps->top-1];
}
int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

void StackDestroy(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->a=NULL;
    ps->top=ps->capacity=0
}

总结

相比于链表,栈实现起来简直不要简单太多,但是大家别懈怠,后面还有二叉树在等着我们呢,现在只是过渡期,继续努力哦!

❤️我也会继续输出数据结构相关的博客的,希望大家多多支持!!!

感谢阅读本小白的博客,如果错误请指出,一定虚心采纳!

你可能感兴趣的:(数据结构,c++,c语言,链表,算法)