当前位置: 首页 > news >正文

数据结构之——栈的操作讲解与功能实现

目录

一.栈

A.栈的概念及结构

2.内存中栈区

B.栈的实现

        1.实现方式:

        2.栈的数据结构

        3.栈的初始化 

4.栈的销毁释放

5.栈数据的插入

6.空间检查

7.栈数据的删除——即出栈

8.访问栈区某个数据的函数

9.栈空间的长度函数

函数整体测试:

整体代码:


一.栈

A.栈的概念及结构

        栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。

         栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则,也可以叫后进先出

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

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

 

注:这只是栈空间中数据大概的一个出入顺序,下图为详细的栈区增删。 

  


 

 

2.内存中栈区

        内存中的栈区空间是相当有限的,原因是内存大多空间都给了堆区,堆区是动态开辟的,空间分配的利用上比堆区要高很多,所以我们实现的大多程序都是动态开辟类型的。

        栈区空间在内存是自顶向下开辟的:

       正因为栈内存空间有限,那么在进行函数递归的时候,若是递归次数过多,会把栈空间放满从而系统会崩溃,崩溃的原因就是栈溢出(stack overflow)  例:

int f(int n) {
	return n == 1 ? 1 : f(n - 1) + f(n - 2);
}
int main() {
	printf("%d\n", f(10000));

	return 0;
}

测试结果: 

 

B.栈的实现

        1.实现方式:

        栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些,因为数组在尾上插入数据的代价比较小,而且CPU在数组上的高速缓存命中率比在链表上更高。所以下面我对于栈的实现方式是以顺序表为主体的。

        对于顺序表的讲解和操作实现我已经写了一篇博客,地址在这:            数据结构——线性表之顺序表的基本操作讲解  大家想深入了解数据结构顺序表的实现可以看一看。

        2.栈的数据结构

静态版:一般不用,空间固定,不灵活。

//静态不常用
typedef int STDataType;
#define N 100
struct Stack {
	STDataType a[N];
	int top;
};

动态版:比静态版的空间利用效率更高。

//动态
typedef int  STDataType;    //重定义int类型为栈类型的
typedef struct Stack {
	STDataType* a;	//栈型指针——指向堆区空间的
	int top;	//栈现有的个数
	int capacity;	//栈区的容量
}ST;    //将栈的结构体类型名改为ST,简化

        3.栈的初始化 

void StackInit(ST* ps)
{
	assert(ps);    //断言——判断实参传过来的指针是否为空
	ps->a = NULL;    //先暂时让栈的指针指向NULL
	ps->top = ps->capacity = 0;
}

 

4.栈的销毁释放

void StackDestroy(ST* ps)
{
	assert(ps);    
	free(ps->a);    //free掉动态开辟的空间
	ps->a = NULL;   //free掉空间后,这块堆区空间还在,只是还给了操作系统,而且栈的指针a还保存
                    //这块空间的地址,a成为了野指针,很危险,需要置为空
	ps->capacity = ps->top = 0;    //将容量和存储个数设为0

5.栈数据的插入

void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	// 扩容
	if (ps->top == ps->capacity)
	{
        //扩容一般扩2倍
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;

		STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity*sizeof(STDataType));
        
        //判断是否开辟成功,若开辟失败则报错
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}

        //表示开辟失败
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
    
    //入栈,即在栈中存入要存的数据
	ps->a[ps->top] = x;
	ps->top++;    //栈空间的数据个数+1
}

        注:栈的结构成员top表示栈中数据存储的个数,栈区为空时,Top指针指向栈底,因为顺序表是以数组为核心实现的,所以Top也就是数组的下标了。每存储一个数据Top都指向这个数据的下一个数据开始位置。

 

6.空间检查

        在栈区空间的数据删除时,需要进行链表空间的判断,若栈空间为空,则不允许再删除了,所以要实现一个函数去检查。

:VS中的C文件里,并不支持bool类型,所以需要在.c文件开头加入#include<stdbool.h>库函数。

bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

7.栈数据的删除——即出栈

void StackPop(ST* ps)
{
	assert(ps);
    
    //判断栈区空间是否为空,若为空,则报错,不为空,则存储个数-1
	assert(!StackEmpty(ps));
	
	--ps->top;
}

栈的操作实现使用顺序表的原因:删除数据方式极为简单方便!

8.访问栈区某个数据的函数

STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	
	return ps->a[ps->top - 1];
}

        刚才讲到了栈结构的成员变量top是从数组下标0开始访问的,访问到的数据,所得下标top要减一。

       访问该数据时, 函数返回的是Top指向的上一个数据的数值。

        top-1原因:数据1,2,3,4,5入栈后,如下图 :

          当要访问数据4时,需要数据5出栈,才能访问到4,而top是下标,数据5对应的数组下标为4( top),那么下标3 ( top-1)的位置就是数据4,所以return ps->a[ps->top-1] ; 数据4即可被访问到。

 

9.栈空间的长度函数

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

	return ps->top;
}

函数整体测试:

整体代码:

Test.c:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"

void TestStack() {
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	printf("%d\n", StackTop(&st));

	StackPop(&st);
	printf("%d\n", StackTop(&st));
	StackPop(&st);

	StackPush(&st, 4);
	StackPush(&st, 5);

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

//int f(int n) {
//	return n == 1 ? 1 : f(n - 1) + f(n - 2);
//}
int main() {
	//printf("%d\n", f(10000));
	TestStack();
	return 0;
}

Stack.c文件:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
//初始化
void StackInit(ST* ps) {
	assert(ps);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

//入栈
void StackPush(ST* ps, STDataType x) {
	assert(ps);
	if (ps->top == ps->capacity) {
		//创建临时变量,若top==capacity说明两种情况
		//情况1:栈表刚开始,容量和数据个数都为0,先开个4字节的容量
		//情况2:说明栈的存储个数达到容量上限,需要扩容了
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//上面这个语句设置了情况二所说的扩容倍数大小为2倍
		//扩容
		STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL) {
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;

	}
	//将数据入栈
	ps->a[ps->top] = x;
	//个数+1
	ps->top++;
}

//出栈
void StackPop(ST* ps) {
	assert(ps);
	assert(!StackEmpty(ps));
	ps->top--;
}

//暴力检查
bool StackEmpty(ST* ps) {
	assert(ps);
	return ps->top == 0;
}
//计算栈表长度
int StackSize(ST* ps) {
	assert(ps);
	return ps->top;
}

//访问栈中某个数据
STDataType StackTop(ST* ps) {
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->a[ps->top - 1];
}

Stack.h文件:

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

//静态不常用
//typedef int STDataType;
//#define N 100
//struct Stack {
//	STDataType a[N];
//	int top;
//};

//动态
typedef int  STDataType;
typedef struct Stack {
	STDataType* a;	//栈型指针——指向堆区空间的
	int top;	//栈现有的个数
	int capacity;	//栈区的容量
}ST;

void StackInit(ST* ps);
void StackDestory(ST* ps);
void StackPush(ST* ps);
void StackPop(ST* ps);

bool StackEmpty(ST* ps);
int StackSize(ST* ps);
STDataType StackTop(ST* ps);

结论:数据结构建议不要直接访问数据,一定要通过函数接口访问(低耦合,高内聚)!!!

相关文章:

  • 剑指 Offer II 079+080+081+082
  • 前端小tips(持续更新)
  • matlab读取文件
  • php __destruct反序列化原理
  • 通俗易懂,一文学会前端缓存
  • python常用基础笔记
  • centos设置root免密自动登陆
  • JuiceFS 在多云存储架构中的应用 | 深势科技分享
  • 【LeetCode】思维向题笔记总结(持续更新)
  • springboot+vue农产品销售配送网站
  • ISE的FPGA程序加载与固化——Omapl138/TMS320C6748+FPGA核心板
  • SAP ABAP 定义事件以及处理事件
  • 西瓜书-2习题
  • 中国LED封装行业发展前景预测与投资战略规划分析报告
  • 传出神经系统分为哪两类,传出神经的分类与功能
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • java8 Stream Pipelines 浅析
  • PV统计优化设计
  • Python 基础起步 (十) 什么叫函数?
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • vue 配置sass、scss全局变量
  • 使用Swoole加速Laravel(正式环境中)
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (4)事件处理——(7)简单事件(Simple events)
  • (C语言)球球大作战
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (TOJ2804)Even? Odd?
  • (二十四)Flask之flask-session组件
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (一)认识微服务
  • .apk文件,IIS不支持下载解决
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .NET Core跨平台微服务学习资源
  • .NET/C# 避免调试器不小心提前计算本应延迟计算的值
  • .NET程序员迈向卓越的必由之路
  • :中兴通讯为何成功
  • [Angular 基础] - 自定义指令,深入学习 directive
  • [ANT] 项目中应用ANT
  • [C#]手把手教你打造Socket的TCP通讯连接(一)
  • [CISCN2019 华北赛区 Day1 Web5]CyberPunk --不会编程的崽
  • [codeforces]Levko and Permutation
  • [ERROR] ocp-server-ce-py_script_start_check-4.2.1 RuntimeError: ‘tenant_name‘
  • [HDU]2161Primes
  • [IE技巧] 使IE8以单进程的模式运行
  • [mit6.s081] 笔记 Lab2:system calls
  • [na]wireshark抓包排错-tcp.flags.reset
  • [Oh My C++ Diary]带参数的main()函数
  • [one_demo_3]漩涡递增矩阵