【数据结构与算法】(7):超详细的栈操作,学透栈必看!

🤡博客主页:醉竺

🥰本文专栏:《数据结构与算法》

😻欢迎关注:感谢大家的点赞评论+关注,祝您学有所成!


 ✨✨💜💛想要学习更多数据结构与算法点击专栏链接查看💛💜✨✨


目录

一.栈的概念及结构

二.栈的存储原理

三.栈的顺序存储(顺序栈)  

四.栈的操作 

1.栈操作的函数接口 

2.初始化栈 

3.判断栈空

4.入栈 

5.出栈 

6.取栈顶元素

7.获取栈中有效元素个数

8.销毁栈 

9.栈的应用 

三.归纳总结(源代码提供) 


一.栈的概念及结构

栈:

  • 一种特殊的线性表,其限定仅在表尾进行插入和删除操作的线性表。
  • 行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
  • 或者说最早进入的元素存放的位置叫作栈底(bottom),最后进入的元素存放的位置叫作栈顶 (top)
  • 栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。  

栈有两种操作,压栈出栈 。

  1. 压栈:压栈操作(push)就是把新元素放入栈中,只允许从栈顶一侧放入元素,新元素的位置将会成为 新的栈顶。
  2. 出栈:出栈操作(pop)就是把元素从栈中弹出,只有栈顶元素才允许出栈,出栈元素的前一个元素将会成为新的栈顶。

其实,我们生活中满足栈这种后进先出的情形非常多,比如往抽屉里放东西,先放进去的肯定 会被堆到最里面,所以只能最后取出,而最后放入的物品往往在最前面或者最上面,所以会被最先取出。 

例如,如果用示意图表示用栈存取数据的过程,就会像下图一样:

在上图中,如果分别将数据 a1、a2、a3、a4、a5 存入栈中,那么在将数据出栈的时候,顺序就应该是 a5、a4、a3、a2、a1(与入栈顺序正好相反)。 

我们刚刚说过,栈是受限的线性表,比如因为只能在栈顶进行元素的插入和删除操作,所以也无法指定插入和删除操作的位置,所以,栈所支持的操作,可以理解为线性表操作的子集,一般包括栈的创建、入栈(增加数据)、出栈(删除数据)、获取栈顶元素(查找数据)、判断栈是否为空或者是否已满等操作。

二.栈的存储原理

栈既可以用数组来实现,也可以用链表来实现。 数组实现的栈也叫顺序栈;链表实现的栈也叫做链式栈。

栈的数组实现如下:

​  

栈的链表实现如下:

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


三.栈的顺序存储(顺序栈)  

所谓顺序栈,就是顺序存储(用一段连续的内存空间依次存储)栈中的数据。

之前我们学习顺序表的时候就提出过2种保存数据的方案同样适合顺序栈。

  1. 静态存储:通过为一维数组静态分配内存的方式来保存数据。
  2. 动态存储:通过为一维数组动态分配内存的方式来保存数据。

为了顺序栈中数据存满时可以对栈进行扩容,在这里,我会采用第2种保存数据的方案来编写顺序栈的实现代码。 此外,为了考虑到元素存取的便利性,将数组下标为0的一端作为栈底最合适。 

以下栈的存储结构体里面有一个数组a用来当作数组栈存储元素;top为栈顶指针;capacity是数组栈容量(若栈满了可以动态扩张)。 

// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
//typedef int STDataType;
//#define N 10
//typedef struct Stack
//{
//	STDataType a[N];
//	int top; // 栈顶
//}Stack;

// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* a; //数组栈
	int top; // 栈顶
	int capacity; // 容量
}Stack;

四.栈的操作 

1.栈操作的函数接口 

这里把即将要对栈实现的操作函数接口列出来以便查阅。 

// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
//typedef int STDataType;
//#define N 10
//typedef struct Stack
//{
//	STDataType a[N];
//	int top; // 栈顶
//}Stack;

// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* a; //数组栈
	int top; // 栈顶
	int capacity; // 容量
}Stack;

// 初始化栈
void StackInit(Stack* pst);

// 入栈
void StackPush(Stack* pst, STDataType data);
// 出栈
void StackPop(Stack* pst);

// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* pst);

// 获取栈顶元素
STDataType StackTop(Stack* pst);
// 获取栈中有效元素个数
int StackSize(Stack* pst);

2.初始化栈 

初始化栈的时候,指向栈顶的栈顶指针有两种初始化操作:

  • top = 0;
  • top = -1;  

这两种方式有什么区别呢?又该如何选择呢?

1.进栈操作不一样:

  • 当 top = 0 表示栈空时,栈顶指针: 始终指向栈顶元素的位置的下一位。元素先入栈,栈顶指针再加1。
  • 当 top = -1 表示栈空时,栈顶指针: 始终指向栈顶元素的位置。栈顶指针先加1,元素再入栈。

2. 出栈操作不一样:

  • 当 top = 0 表示栈空时,栈顶指针先减1,元素再出栈。
  • 当 top = -1 表示栈空时,栈顶指针先出栈,元素再减1。

在这里我推荐第一种,因为第一种top的数值和栈内元素的数量是一样,top=0也代表栈内元素为0,top=2时,栈内元素也是两个,可以一一对应比较方便。 

// 初始化栈
void StackInit(Stack* pst)
{
	assert(pst);

	pst->a = NULL;
	//pst->top = -1; // top指向的是 栈顶元素的位置
	pst->top = 0; // top指向的是 栈顶元素的下一个元素的位置
	pst->capacity = 0;
}

3.判断栈空

采用第一种top == 0的方式。 

// 检测栈是否为空,如果为空返回True,如果不为空返回False
bool StackEmpty(Stack* pst)
{
	assert(pst);
	/*
	if (pst->top == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
	*/

	return pst->top == 0;
}

4.入栈 

  • 入栈前要判断是否栈满,如果栈已满,则再开辟当前空间的二倍
  • 否则将元素放入栈顶,栈顶指针向上移动一个位置(top++)

// 入栈
void StackPush(Stack* pst, STDataType data)
{
	assert(pst);
	
	if (pst->top == pst->capacity) //栈为空或者栈满
	{
		int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;

		STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("栈内存空间开辟失败");
		    return;
		}

		pst->a = tmp;
		pst->capacity = newcapacity;
	}
	pst->a[pst->top] = data; //元素入栈
	pst->top++; //栈顶"游标"向上移动一位
}

5.出栈 

  • 出栈前要判断是否栈空,如果栈是空的,则出栈失败
  • 否则将栈顶元素暂存给一个变量,栈顶指针向下移动一个位置(top--)。 

// 出栈
void StackPop(Stack* pst)
{
	assert(pst);
	assert(!StackEmpty(pst)); //断言栈是否为空,为空则报错

	pst->top--;
}

6.取栈顶元素

取栈顶元素和出栈不同。取栈顶元素只是把栈顶元素复制一份返回,栈顶指针未移动,栈内元素个数未变。而出栈是指栈顶指针向下移动一个位置,栈内不再包含这个元素。

// 获取栈顶元素
STDataType StackTop(Stack* pst)
{
	assert(pst);
	assert(!StackEmpty(pst));

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

7.获取栈中有效元素个数

采取第一种top=0为栈空时,这种top的数值和栈内元素的数量是一样,top=0也代表栈内元素为0,top=2时,栈内元素也是两个,可以一一对应比较方便。 

// 获取栈中有效元素个数
int StackSize(Stack* pst)
{
	assert(pst);

	return pst->top;
}

8.销毁栈 

// 销毁栈
void StackDestroy(Stack* pst)
{
	assert(pst);
	
	free(pst->a);
	pst->a = NULL;
	pst->top = 0;
	pst->capacity = 0;
}

9.栈的应用 

  • 函数调用:每进入一个函数,就会将临时变量作为一个栈入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
  • 浏览器的后退功能:我们使用两个栈,X 和 Y,我们把首次浏览的页面依次压入栈 X,当点击后退按钮时,再依次从栈 X 中出栈,并将出栈的数据依次放入栈 Y。当我们点击前进按钮时,我们依次从栈 Y 中取出数据, 放入栈 X 中。当栈 X 中没有数据时,那就说明没有页面可以继续后退浏览了。当栈 Y 中没有数据,那就说明没有页面可以点击前进按钮浏览了。 

三.归纳总结(源代码提供) 

创作不易,看到这的希望能点个赞支持我一下吧!这里提供实现栈功能的源代码,需要的直接复制即可运行~

创建三个文件:Stack.h,Stack.c,Test.c

1.Stack.h:

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>

// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
//typedef int STDataType;
//#define N 10
//typedef struct Stack
//{
//	STDataType a[N];
//	int top; // 栈顶
//}Stack;

// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* a; //数组栈
	int top; // 栈顶
	int capacity; // 容量
}Stack;

// 初始化栈
void StackInit(Stack* pst);

// 入栈
void StackPush(Stack* pst, STDataType data);
// 出栈
void StackPop(Stack* pst);

// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* pst);

// 获取栈顶元素
STDataType StackTop(Stack* pst);
// 获取栈中有效元素个数
int StackSize(Stack* pst);


// 销毁栈
void StackDestroy(Stack* pst);

2.Stack.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include "Stack.h"

// 初始化栈
void StackInit(Stack* pst)
{
	assert(pst);

	pst->a = NULL;
	//pst->top = -1; // top指向的是 栈顶元素的位置
	pst->top = 0; // top指向的是 栈顶元素的下一个元素的位置
	pst->capacity = 0;
}

// 入栈
void StackPush(Stack* pst, STDataType data)
{
	assert(pst);
	
	if (pst->top == pst->capacity) //栈为空或者栈满
	{
		int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;

		STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("栈内存空间开辟失败");
		    return;
		}

		pst->a = tmp;
		pst->capacity = newcapacity;
	}
	pst->a[pst->top] = data; //元素入栈
	pst->top++; //栈顶"游标"向上移动一位
}

// 检测栈是否为空,如果为空返回True,如果不为空返回False
bool StackEmpty(Stack* pst)
{
	assert(pst);
	/*
	if (pst->top == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
	*/

	return pst->top == 0;
}

// 出栈
void StackPop(Stack* pst)
{
	assert(pst);
	assert(!StackEmpty(pst)); //断言栈是否为空,为空则报错

	pst->top--;
}

// 获取栈顶元素
STDataType StackTop(Stack* pst)
{
	assert(pst);
	assert(!StackEmpty(pst));

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

// 获取栈中有效元素个数
int StackSize(Stack* pst)
{
	assert(pst);

	return pst->top;
}

// 销毁栈
void StackDestroy(Stack* pst)
{
	assert(pst);
	
	free(pst->a);
	pst->a = NULL;
	pst->top = 0;
	pst->capacity = 0;
}

3.Test.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include "Stack.h"

void Test()
{
	Stack st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	printf("%d ", StackTop(&st));
	StackPop(&st);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);

	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}

	StackDestroy(&st);
}

int main()
{
	Test();
	return 0;
}
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

醉竺

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

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

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

打赏作者

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

抵扣说明:

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

余额充值