(超详细)数据结构——“栈”的深度解析

前言:

在前几章我们介绍了线性表的基本概念,也讲解了包括顺序表,单链表,双向链表等线性表,相信大家已经对线性表比较熟悉了,今天我们要实现线性表的另一种结构------栈。

1.栈的概念

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。**进行数据插入和删除操作的一端****称为栈顶,另一端称为栈底。**栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。这里的栈与内存中的栈在结构上是一致的,它们提取数据和存放数据也叫出栈和压栈。
压栈:栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶
出栈:栈的删除操作叫做出栈。 出数据也在栈顶

2.栈的实现

栈的实现可以选择使用链表或者数组,这两种结构都可以很好的实现栈的各种操作,相对而言,数组是更好的选择,因为栈的插入只需要对结构进行尾插,而数组尾插的代价相较于链表来说没有那么大。

3.代码实现

为了方便管理,我们将栈的实现分为三个文件,分别是test.c ,Stack.c ,Stack.h(stack中译为堆叠,也就是栈的意思) ,由于我们选择使用数组来实现栈,所以在数据结构中我们就使用动态顺序表,顺序表中要包含顺序表空间大小,一个指向这块空间的指针和top来表示栈顶,而这个数据的类型是视情况而定的,我们使用typedef对数组中的元素类型改名,使栈存储的数据类型更加灵活,为了方便使用,我们也将栈类型改名为ST:

typedef int STDataType;
typedef struct Stack
{
	STDataType* arr;
	int top;
	int capacity;//表示当前空间大小

}ST;

实现了栈的基本结构之后,我们接下来要实现栈的各种操作,例如栈的压栈和出栈等操作。

3.1 栈的初始化

在程序刚开始执行使,栈内部的所有成员都没有初始化,它们的值都是不确定的,而不确定意味着不可控,我们后续需要通过这些成员的值来判断执行一些操作,例如表示数组的空间大小capacity变量,如果它的值是不确定的,那么我们也无法对数组执行有效操作,所以我们要对它进行初始化,而刚开始栈是空的,所以我们让指向栈的指针指向空,元素为0个:

void STInit(ST* pst)
{
	assert(pst);
	pst->arr = NULL;
	pst->capacity = 0;
	pst->top = 0;
}//初始化

3.2 栈的销毁

我们栈的空间使用的的是顺序表结构实现的,而顺序表的空间都是在堆上开辟的,所以我们需要手动将这些空间释放:

void STDestroy(ST* pst)
{
	assert(pst);

	free(pst->arr);
	pst->arr = NULL;
	pst->capacity = pst->top = 0;
}//销毁

3.3 压栈操作

压栈就是进栈的意思,对栈进行压栈操作之前,我们要判断栈空间够不够,如果不够,我们需要手动从堆上开辟一块空间,而一次开辟的空间是有限的,将空间开辟得太大也会造成空间浪费,所以我们判断在空间不够时使用realloc函数重新开辟一块更大的空间,而插入操作则比较简单,只需要在数组中top指向的位置将我们要插入的数据插入,再让top加一就可以了:

void STPush(ST* pst, STDataType x)
{
	assert(pst);
	if (pst->capacity == pst->top)
	{
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* ret = (STDataType*)realloc(pst->arr, newcapacity * sizeof(STDataType));
		if (ret == NULL)
		{
			perror("realloc fail");
			return;
		}
		pst->capacity = newcapacity;
		pst->arr = ret;
	}
	pst->arr[pst->top++] = x;
}//压栈

3.4 出栈操作

出栈操作也就是删除栈顶的元素,这个操作比较简单,只需要让top减一,因为我们在取数据时,只取top下面的数据,top上面的数据则默认不在栈内,这样就相当于我们将这个元素删除了:

void STPop(ST* pst)
{
	assert(pst);
	pst->top--;
}//删除

3.5 栈顶

有时我们频繁对栈进行插入和删除操作,我们需要查看栈顶情况,就可以使用这个方法,,这个方法的实现也相对简单,只需要返回栈中top下标的下一个 元素,因为top始终指向栈顶元素的下一个位置,我们插入元素也是先将元素放到下标为top的位置中,再让top加一:

STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	return pst->arr[pst->top - 1];
}//栈顶

3.6 判空

我们有时要对栈进行置空操作,如果不确定栈何时为空就可以使用这个方法,判空操作只需要判断top是否为0就可以了,如果为零,就为真,返回true,如果不为空,就为假返回false:

bool STEmpty(ST* pst)
{
	assert(pst);
	return pst->top == 0;
}//判空

3.7 栈的大小

我们前面提到了,top就是栈的大小,如果我们直接查看top不是更快吗,为什么要单独写一个方法呢,事实上我们单独实现一个方法是为了方便统一操作,要执行何种操作,只需要调用一个方法就可以实现:

int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}//大小

3.8 测试

实现完这些方法之后,我们要测试一下我们的代码有没有问题:

经过我们测试,这些方法都没有问题,本期栈的讲解到这就结束了,是不是比前几期简单一些呢,我们亲自上手就知道了,代码放在下面感兴趣的小伙伴们可以试试哦。

Stack.h :

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
	STDataType* arr;
	int top;
	int capacity;//表示当前空间大小

}ST;
void STInit(ST* pst);//初始化

void STDestroy(ST* pst);//销毁

void STPush(ST* pst, STDataType x);//压栈

void STPop(ST* pst);//删除

STDataType STTop(ST* pst);//栈顶元素

bool STEmpty(ST* pst);//判空

int STSize(ST* pst);//大小

Stack.c :

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"

void STInit(ST* pst)
{
	assert(pst);
	pst->arr = NULL;
	pst->capacity = 0;
	pst->top = 0;
}//初始化

void STPush(ST* pst, STDataType x)
{
	assert(pst);
	if (pst->capacity == pst->top)
	{
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* ret = (STDataType*)realloc(pst->arr, newcapacity * sizeof(STDataType));
		if (ret == NULL)
		{
			perror("realloc fail");
			return;
		}
		pst->capacity = newcapacity;
		pst->arr = ret;
	}
	pst->arr[pst->top++] = x;
}//压栈

void STPop(ST* pst)
{
	assert(pst);
	pst->top--;
}//删除

STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	return pst->arr[pst->top - 1];
}//栈顶

bool STEmpty(ST* pst)
{
	assert(pst);
	return pst->top == 0;
}//判空

int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}//大小

void STDestroy(ST* pst)
{
	assert(pst);

	free(pst->arr);
	pst->arr = NULL;
	pst->capacity = pst->top = 0;
}//销毁

test.c :

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void test()
{
	ST s;

	STInit(&s);
	printf("插入四个元素\n");
	STPush(&s, 1);
	STPush(&s, 2);
	STPush(&s, 3);
	STPush(&s, 4);
	
	printf("栈顶元素为:%d\n", STTop(&s));
	printf("%d个元素\n", STSize(&s));
	while (!STEmpty(&s))
	{
		printf("%d ", STTop(&s));
		STPop(&s);

	}
	STDestroy(&s);
}

int main()
{
	test();
	return 0;
}
相关推荐
pianmian11 小时前
python数据结构基础(7)
数据结构·算法
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
ChoSeitaku4 小时前
链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)
数据结构·考研·链表
偷心编程4 小时前
双向链表专题
数据结构
香菜大丸4 小时前
链表的归并排序
数据结构·算法·链表
jrrz08284 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
@小博的博客4 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
DARLING Zero two♡6 小时前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
泉崎6 小时前
11.7比赛总结
数据结构·算法
你好helloworld6 小时前
滑动窗口最大值
数据结构·算法·leetcode