【数据结构】 栈的深度剖析!超详细精解!

🎥 屿小夏 : 个人主页
🔥个人专栏 : 数据结构解析
🌄 莫道桑榆晚,为霞尚满天!

文章目录

  • 📑前言
  • 🌤️栈的概念剖析
    • ☁️什么是栈?
    • ☁️栈的特性
    • ☁️栈的图解
  • 🌤️栈的详细实现
    • ☁️动态栈的初始化
      • ⭐栈的结构体
      • ⭐栈的初始化
    • ☁️入栈
    • ☁️出栈
    • ☁️获取栈顶元素
    • ☁️检测栈是否为空
    • ☁️栈中有效元素个数
    • ☁️栈销毁
  • 🌤️栈的泛用性
  • 🌤️全篇总结

📑前言

什么是栈?栈这种数据结构有什么样的特性?它能够拿来干嘛?本文我们将深度探讨,剖析清楚栈的全部,你让熟练掌握栈的运用!

🌤️栈的概念剖析

☁️什么是栈?

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

  • ​ 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

  • ​ 出栈:栈的删除操作叫做出栈。出数据也在栈顶。

☁️栈的特性

  1. 只能在栈顶进行插入和删除操作,称为入栈和出栈。
  2. 元素的插入和删除操作只能在栈顶进行,不允许在其他位置进行操作。
  3. 栈的大小是固定的,一旦栈满了就无法再插入新的元素,称为栈溢出。
  4. 栈的大小可以动态调整,称为动态栈。
  5. 栈可以用数组或链表实现。

☁️栈的图解


🌤️栈的详细实现

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


定长的静态栈,实际中我们一般不使用,所以我们下面主要实现的就是支持动态增长的栈 。

☁️动态栈的初始化

⭐栈的结构体

typedef int StackData;
typedef struct Stack
{
	StackData* data;
	int size;
	int capacity;
}Stack;

​ 对数据类型进行重命名,这样以后需要更换其他数据类型使用的时候只需要更改这一个地方就可以了。

​ 定义一个数组动态的开辟空间,size用来存储当前栈内的有效元素个数,capacity来表示栈的总容量大小,这样的是为了当栈内有效元素个数与容量一致时,方便对其进行扩容。

⭐栈的初始化

void StackInit(Stack* SK)
{
	assert(SK);
 
	SK->data = NULL;
	SK->capacity = 0;
	SK->size = 0;
}

​ 先将栈中所有的数据都初始化为空,因为栈只需要在容量满的时候进行扩容,这里可以不需要先开辟空间,这一步可以节省。

☁️入栈

void StackPush(Stack* SK,StackData x)
{
	assert(SK);
 
	if (SK->capacity == SK->size)
	{
		SK->capacity = (SK->capacity == 0 ? 5 : SK->capacity * 2);
		StackData* tmp = (StackData*)realloc(SK->data,sizeof(StackData) *SK->capacity);
		if (tmp == NULL)
		{
			perror("realloc ");
			exit(-1);
		}
		SK->data = tmp;
	}
	SK->data[SK->size] = x;
	SK->size++;
}

​ 在对栈进行数据插入前,首先进行栈区容量检查,如果满或是空,就先进行空间的增容。然后再将元素插入到栈顶,栈中有效元素个数size++。

☁️出栈

void StackPop(Stack* SK)
{
	assert(SK);
	assert(SK->size > 0);
	SK->size--;
}

​ 栈的出栈相较入栈更为简单,因为栈的空间是动态开辟来的,无法对其一部分空间进行释放,故无法达到真正意义上的删除。这里也不可将元素的值置为0,因为有一种情况就是元素数据的值就是0。因此这里只需要让size–就可以达到目的,size是栈中有效的元素个数,插入元素时也是使用size为下标进行插入,所以size–后,代表栈少了一个元素,再进行入栈操作,新的元素就会覆盖旧的值。

☁️获取栈顶元素

StackData StackTop(Stack* SK)
{
	assert(SK);
 
	assert(SK->size > 0);
	return SK->data[SK->size-1];
}

想要知道栈顶的元素,首先要对栈中元素进行判定,如果栈中元素不大于0,则栈中无元素。

栈有元素时,size是有效元素个数,数组的性质下标是从0开始,所以这里size-1,就代表栈的顶部元素。

☁️检测栈是否为空

bool StackEmpty(Stack* SK)
{
	assert(SK);
 
	return SK->size == 0;
}

在什么情况下栈为空?只有一种情况就是栈内的元素个数为0,此时站内无元素就是空。

☁️栈中有效元素个数

int StackSize(Stack* SK)
{
	assert(SK);
 
	return SK->size;
}

当我们在对栈进行多次入栈和出栈操作后,我们想知道此时栈中还有多少元素时,就会用到。如何知道呢,在最开始我们定义了一个size,它存的值就是栈内的有效元素个数,所以只需要返回size就可以。

☁️栈销毁

void StackDestroy(Stack* SK)
{
	assert(SK);
 
	free(SK->data);
	SK->capacity = 0;
	SK->size = 0;
	SK->data = NULL;
}

这里不能直接对SK进行释放,因为SK中的data是动态开辟来的,所以需要先对data进行free,然后将其他成员置空。

🌤️栈的泛用性

栈作为一种数据结构,在计算机科学中具有广泛的泛用性,它可以应用于各种不同的问题和场景。

  1. 函数调用:栈可以用于函数调用和返回的过程中,每次函数调用时将函数的参数和返回地址压入栈中,函数返回时从栈中弹出返回地址并继续执行。
  2. 表达式求值:栈可以用于中缀表达式转换为后缀表达式,并通过栈进行后缀表达式的求值。栈可以保存运算符和操作数,并按照运算符的优先级进行计算。
  3. 括号匹配:栈可以用于检查括号是否匹配的问题。遍历字符串,遇到左括号时将其压入栈中,遇到右括号时弹出栈顶元素并判断是否匹配。
  4. 浏览器的前进后退功能:栈可以用于实现浏览器的前进后退功能。每次访问一个新的页面时,将该页面的URL压入栈中,点击后退按钮时从栈中弹出URL并加载对应的页面。
  5. 撤销操作:栈可以用于实现撤销操作的功能。每次进行一个操作时,将操作的信息压入栈中,点击撤销按钮时从栈中弹出操作信息并执行相应的撤销操作。
  6. 迷宫求解:栈可以用于解决迷宫求解问题。使用栈保存走过的路径,每次选择一个可行的方向前进,如果遇到死路则回退到上一个位置并选择其他方向。

🌤️全篇总结

经过对归并排序的思想,特性,代码实现这些细致入微的讲解,相信聪明的你肯定已经学会了叭😊😊!

☁️ 好啦,由于篇幅有限,本章仅对栈进行了细致入微的讲解,后序还会有更多的数据结构文章分享哦!
看到这里了希望给博主留个:
👍 点赞🌟收藏 ⭐️ 关注!
💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要!
你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2023年12月14日
下一篇 2023年12月14日

相关推荐