初级数据结构(三)——栈

news2024/11/22 11:38:54

  文中代码源文件已上传:数据结构源码

<-上一篇 初级数据结构(二)——链表        |        初级数据结构(四)——队列 下一篇->

1、栈的特性

1.1、函数栈帧简述

        即使是刚入门几天的小白,对栈这个字也应该略有耳闻。在操作系统层面,栈是系统在内存中划分的一整块连续的地址范围。并且系统对于单个程序在栈区的空间使用也是连续的。以一段代码举例:

void FunctionInside()
{
    /* ... */
}

void Function_1()
{
    /* ... */
}

void Function_2()
{
    /* ... */
    FunctionInside();
    /* ... */
}

int main()
{
    Function_1();
    Function_2();
    return 0;
}

        在运行上述代码时,首先调用 main 函数,系统将为 main 函数在栈上单独开辟一块空间供 main 函数使用,这块空间称作 main 函数的栈帧。而当在 main 中调用 Function_1 时,系统又在 main 函数栈帧之上为 Function_1 开辟一块  Function_1 的栈帧。而随着 Function_1 函数调用结束, Function_1 的栈帧也被销毁,该栈帧的空间归还给操作系统。

        而进入 Function_2 函数时,系统同样为 Function_2 开辟一块栈帧。如果如上述代码中的  Function_1 和 Function_2 是连续调用的话, Function_2 栈帧很可能覆盖之前 Function_1 被销毁的栈帧空间。此时进入 Function_2 函数内部,又在内部调用 FunctionInside 函数。此时,系统将在 Function_2 栈帧之上为 FunctionInside 创建栈帧。

        随着 FunctionInside 调用结束 FunctionInside 栈帧也随之归还,今儿回到 Function_2 的栈帧空间。当然, Function_2 调用结束后, Function_2 的栈帧也将归还,并回到 main 函数的栈帧空间。最后随着程序结束,main 函数的栈帧也随之销毁。

         或许有点绕。但看上图也不难发现其规律,这就是栈的特性:后入先出、先入后出,也称为 LIFO ( Last In First Out )。

1.2、栈结构

        基于某些需求(如通过程序判断记录数学公式字符串的大中小括号是否配对、计算字符串中的数学公式的值、甚至最基本的数组倒序等),程序的数据处理需要用到后入先出的特性,对这类数据进行操作的结构就称为栈结构。

        栈结构的实现仍然是通过顺序表或者链表实现,只是在存取数据时必须遵循 LIFO 的规则。并且,栈结构不存在改和查的操作。如果要,必须将尾部数据至需要修改的数据之间的元素依次弹出后重新依次写入新数据,至于查,只允许查看尾部元素,一旦查看必须弹出。

        通常栈结构都用顺序表创建较为方便,因此以下便以顺序表的方式进行演示。同时最后附上链表实现栈结构的代码。

2、栈创建

2.1、文件结构

        与之前章节相同,依然创建三个文件,文件名如下:

        stack.h :用于创建项目的结构体类型以及声明函数;

        stack.c :用于创建栈各种操作功能的函数;

        main.c :仅创建 main 函数,用作测试。

 2.2、前期工作

        stack.h 中内容如下:

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

//存储数据类型的定义及打印占位符预定义
#define DATAPRT "%d"
typedef int DATATYPE;

//栈主体
typedef struct Stack
{
	DATATYPE* data;		//数据段
	size_t top;			//栈顶位置下标
	size_t capacity;	//开辟空间记录
}Stack;

//函数声明---------------------------------

//初始化
extern int StackInit(Stack*);
//销毁
extern void StackDestroy(Stack*);
//入栈
extern void StackPush(Stack*, DATATYPE);
//出栈
extern void StackPop(Stack*);

        然后 stack.c 中先创建初始化及销毁的两个函数。这里需要注意的是对结构体中的 top 值的操作。虽然这个值等同于顺序表中的 size ,但需要区分 top 是储存栈结构最后一个数据的下标。顺序表中的空表 size 则是等于 0 ,取顺序表的最后一个元素下标都是以 size - 1 进行操作,而对于栈来说,如果是空栈,top 则置为 -1 :

#include "stack.h"

//初始化
int StackInit(Stack* st)
{
	//参数判断
	if (!st)
	{
		fprintf(stderr, "Illegal Stack Address\n");
		return;
	}
	//初始开辟1个数据位
	st->data = (DATATYPE*)malloc(sizeof(DATATYPE) * 1);
	if (!st->data)
	{
		fprintf(stderr, "Malloc Fail\n");
		return -2;
	}
	st->top = -1;
	st->capacity = 1;
	return 0;
}

//销毁
void StackDestroy(Stack* st)
{
	//参数判断
	if (!st)
	{
		fprintf(stderr, "Illegal Stack Address\n");
		return;
	}
	//释放
	free(st->data);
	st->data = NULL;
	st->top = -1;
	st->capacity = 0;
}

        在 main.c 中则预先创建一个结构体变量,无需进行其他操作。

#include "stack.h"

int main()
{
	Stack st;
	return 0;
}

3、栈的数据操作

3.1、入栈

        入栈实际上就是顺序表的尾插,具体实现过程可以参考第一篇顺序表中的插入数据操作。这个功能实现起来并不复杂。此外跟顺序表一样,如果已开辟的空间不足则需要进行扩容操作。这里可以将扩容封装为一个函数,使代码看起来更简洁。此外,由于扩容函数仅在该代码文件内调用,可以加上 static 修饰。

        在 static.c 中加入以下代码:

//扩容
static void StackExpand(Stack* st)
{
	//参数判断
	if (!st)
	{
		fprintf(stderr, "Illegal Stack Address\n");
		return;
	}
	DATATYPE* temp = (DATATYPE*)realloc(st->data, sizeof(DATATYPE) * st->capacity * 2);
	if (!temp)
	{
		fprintf(stderr, "Realloc Fail\n");
		return;
	}
	st->data = temp;
	st->capacity *= 2;
}

//入栈
void StackPush(Stack* st, DATATYPE data)
{
	//参数判断
	if (!st)
	{
		fprintf(stderr, "Illegal Stack Address\n");
		return;
	}
	st->top++;
	//空间不足则扩容
	if ((st->top) >= (st->capacity))
	{
		StackExpand(st);
	}
	//入栈
	st->data[st->top] = data;
}

        然后在 main.c 中输入以下,并测试:

    StackInit(NULL);
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);

        测试无误,进行下一步。

3.2、出栈

        入栈等于尾插,那出栈自然就等同于尾删。对于出栈操作需要注意两点,栈是否为空以及空间回收。是否空栈只需判别 top 是否等于 -1 。至于空间回收,顺序表中也有提到,操作逻辑完全一致。

        这里为了代码可读性,将空栈判断封装为函数,将空间回收也一并封装为函数。 stack.c 中增加如下代码:

//收缩空间
static void StackContract(Stack* st)
{
	//参数判断
	if (!st)
	{
		fprintf(stderr, "Illegal Stack Address\n");
		return;
	}
	DATATYPE* temp = (DATATYPE*)realloc(st->data, sizeof(DATATYPE) * st->capacity / 2);
	if (!temp)
	{
		fprintf(stderr, "Realloc Fail\n");
		return;
	}
	st->data = temp;
	st->capacity /= 2;
}

//空表判断 空表返回true
static bool StackIsEmpty(Stack* st)
{
	//参数判断
	if (!st)
	{
		fprintf(stderr, "Illegal Stack Address\n");
		return true;
	}
	return (st->top == -1 ? true : false);
}

//出栈
void StackPop(Stack* st)
{
	//参数判断
	if (!st)
	{
		fprintf(stderr, "Illegal Stack Address\n");
		return;
	}
	//数据为空直接返回
	if (StackIsEmpty(st))
	{
		fprintf(stderr, "Stack Empty\n");
		return;
	}
	//出栈
	st->top--;
	//空间过剩则收缩空间
	if ((st->top) < (st->capacity) / 2)
	{
		StackContract(st);
	}
}

3.3、附加功能

        出栈功能写完后,获取栈顶数据及打印输出栈顶数据的功能也一并加上。首先在 stack.h 中进行声明:

//打印
extern void StackPrint(Stack*);
//获取栈顶数据
extern DATATYPE StackGetTopData(Stack*);

        然后继续在 static.c 中补充这两个功能。

        这里需要注意,因为获取栈顶数据是有返回值的,因此如果空表或者传入空指针便不能简单地 return ,容易对返回值的接收产生误解。而不论 return 任何值,均有可能被误以为栈顶数据就是该值,因此这里以 assert 判定最佳:

//获取栈顶数据
DATATYPE StackGetTopData(Stack* st)
{
	//参数判断
	assert(st);
	//空表警告
	assert(!StackIsEmpty(st));
	//取数据并出栈
	DATATYPE data = st->data[st->top];
	StackPop(st);
	return data;
}

//打印
void StackPrint(Stack* st)
{
	//参数判断
	if (!st)
	{
		fprintf(stderr, "Illegal Stack Address\n");
		return;
	}
	//空表打印NULL后返回
	if (StackIsEmpty(st))
	{
		printf("NULL ");
		return;
	}
	//打印栈顶并出栈
	printf(DATAPRT " ", StackGetTopData(st));
}

        至此功能完毕。之后便是测试,同时也顺带测试之前出栈的函数。完整的 main 函数代码:

int main()
{
	Stack st;
	StackInit(NULL);
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	StackPrint(&st);        //5
	StackPrint(&st);        //4
	StackPrint(&st);        //3
	StackPrint(&st);        //2
	StackPrint(&st);        //1
	StackPrint(&st);        //NULL
	StackPrint(&st);        //NULL
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPrint(&st);        //3
	StackDestroy(&st);
	StackPrint(&st);        //NULL

	return 0;
}

        测试结果如下: 

        至此栈结构便完成了。 

4、以链表实现栈

        链表实现栈的文件结构与顺序表实现栈的结构一致,根据以下代码自行测试研究。有链表的基础打底,这里实现起来也将十分轻松:

        stack.h :

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

#define DATAPRT "%d"

typedef int DATATYPE;

typedef struct StackNode
{
	DATATYPE data;
	struct StackNode* next;
}StackNode;

typedef struct StackInfo
{
	StackNode* StackHead;
	size_t StackSize;
}StackInfo;

//函数声明---------------------------------
//初始化
extern void StackInit(StackInfo*);
//销毁
extern void StackDestroy(StackInfo*);
//入栈
extern void StackPush(StackInfo*, DATATYPE);
//出栈
extern void StackPop(StackInfo*);
//获取栈顶数据
extern DATATYPE StackGetHead(StackInfo*);
//打印栈顶数据
extern void StackPrint(StackInfo*);

        stack.c :

#include "stack.h"

//初始化
void StackInit(StackInfo* info)
{
	//参数有效判断
	if (!info)
	{
		fprintf(stderr, "Illegal StackInformation Address\n");
		return;
	}
	//初始化
	info->StackHead = NULL;
	info->StackSize = 0;
}

//销毁
void StackDestroy(StackInfo* info)
{
	//参数有效判断
	if (!info)
	{
		fprintf(stderr, "Illegal StackInformation Address\n");
	}
	//空链表直接返回
	if (!info->StackSize)
	{
		return;
	}
	//逐节点释放空间
	StackNode* currentNode = info->StackHead;
	while (currentNode)
	{
		StackNode* destroyNode = currentNode;
		currentNode = currentNode->next;
		free(destroyNode);
	}
	info->StackHead = NULL;
	info->StackSize = 0;
}

//判空
static bool StackIsEmpty(StackInfo* info)
{
	//参数有效判断
	if (!info)
	{
		fprintf(stderr, "Illegal StackInformation Address\n");
		return;
	}
	return (info->StackSize == 0 ? true : false);
}

//入栈
void StackPush(StackInfo* info, DATATYPE data)
{
	//参数有效判断
	if (!info)
	{
		fprintf(stderr, "Illegal StackInformation Address\n");
		return;
	}
	StackNode* newNode = (StackNode*)malloc(sizeof(StackNode));
	if (!newNode)
	{
		fprintf(stderr, "Malloc Fail\n");
		return;
	}
	newNode->data = data;
	newNode->next = info->StackHead;
	info->StackHead = newNode;
	info->StackSize++;
}

//出栈
void StackPop(StackInfo* info)
{
	//参数有效判断
	if (!info)
	{
		fprintf(stderr, "Illegal StackInformation Address\n");
		return;
	}
	if (StackIsEmpty(info))
	{
		fprintf(stderr, "Stack Empty\n");
		return;
	}
	StackNode* destroyNode = info->StackHead;
	info->StackHead = info->StackHead->next;
	info->StackSize--;
	free(destroyNode);
}

//获取栈顶数据
DATATYPE StackGetHead(StackInfo* info)
{
	//参数有效判断
	assert(info);
	//空表警告
	assert(!StackIsEmpty(info));
	DATATYPE data = info->StackHead->data;
	StackPop(info);
	return data;
}

//打印栈顶数据
void StackPrint(StackInfo* info)
{
	//参数有效判断
	if (!info)
	{
		fprintf(stderr, "Illegal StackInformation Address\n");
		return;
	}
	if (StackIsEmpty(info))
	{
		printf("NULL ");
		return;
	}
	printf(DATAPRT " ", StackGetHead(info));
}

        main.c 的测试用例:

#include "stack.h"

int main()
{
	StackInfo info;
	StackInit(NULL);
	StackInit(&info);
	StackPush(&info, 1);
	StackPush(&info, 2);
	StackPush(&info, 3);
	StackPush(NULL, 20);
	StackPush(&info, 4);
	StackPush(&info, 5);
	StackPrint(&info);
	StackPrint(&info);
	StackPrint(&info);
	StackPrint(&info);
	StackPrint(&info);
	StackPrint(&info);
	StackPrint(&info);
	StackPrint(&info);
	StackPush(&info, 1);
	StackPush(&info, 2);
	StackPush(&info, 3);
	StackPrint(&info);
	StackDestroy(&info);
	StackPrint(&info);
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1307572.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文读懂NISQ时代的量子竞赛

/目录/ 一、关于NISQ&#xff0c;业内专家怎么看&#xff1f; 二、NISQ时代的量子硬件资源估算 2.1. NISQ量子比特要求 2.2. NISQ计算时间 2.3. NISQ代码经典模拟 三、主流量子算法“优势”的资源估算 3.1. 用于化学模拟的VQE算法 3.2. 用于组合优化的QAOA算法 3.3. 量…

你真的了解Shiro框架吗?

关注公众号回复20231110获取最新网络安全以及内网渗透等资料。 文章目录 关注公众号回复20231110获取最新网络安全以及内网渗透等资料。Shiro的核心架构Shiro中的认证认证shiro中认证的关键对象 认证流程调试认证流程Shiro的加密过程Shiro中的解密过程总结 Shiro的核心架构 Shi…

低代码开发与传统软件开发:未来趋势与竞争格局

近年来&#xff0c;低代码开发平台的快速发展引起了各行各业的广泛关注。低代码开发平台简化了软件开发的复杂性&#xff0c;提供了更快速、更灵活的开发方式。于是&#xff0c;许多人开始产生一个疑问&#xff1a;未来低代码开发是否会取代传统软件开发&#xff1f;今天这篇文…

验收支撑-软件项目验收计划书

软件项目验收计划的作用主要有以下几点&#xff1a; 确保项目质量&#xff1a;通过项目验收&#xff0c;客户或相关方可以对项目的成果进行全面、系统的评估&#xff0c;以确保项目达到预期的质量标准。发现和解决问题&#xff1a;在项目开发过程中&#xff0c;难免会存在一些问…

ROS-ROS运行管理-ROS元功能包

ROS是多进程(节点)的分布式框架&#xff0c;一个完整的ROS系统实现&#xff1a; 可能包含多台主机&#xff1b;每台主机上又有多个工作空间(workspace)&#xff1b;每个的工作空间中又包含多个功能包(package)&#xff1b;每个功能包又包含多个节点(Node)&#xff0c;不同的节…

(Nerf学习)GaussianEditor

论文链接 https://arxiv.org/pdf/2311.14521.pdf 原码链接 https://github.com/buaacyw/GaussianEditor 一、安装&#xff08;WIN失败&#xff0c;求解决方法&#xff09; 我使用的环境是&#xff1a;Win11 python3.8 CUDA11.8 显卡3060 1、克隆我们的存储库并创建 conda …

【INTEL(ALTERA)】Agilex7 FPGA Development Kit DK-DK-DEV-AGI027RBES 编程/烧录/烧写/下载步骤

DK-DEV-AGI027RBES 的编程步骤&#xff1a; 将 USB 电缆插入 USB 端口 J8&#xff08;使用 J10 时&#xff0c;DIPSWITCH SW5.3&#xff08;DK-DEV-AGI027RES 和 DK-DEV-AGI027R1BES&#xff09;和 SW8.3&#xff08;DK-DEV-AGI027RB 和 DK-DEV-AGI027-RA&#xff09;应关闭&a…

【动态规划】路径问题_不同路径_C++

题目链接&#xff1a;leetcode不同路径 目录 题目解析&#xff1a; 算法原理 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 编写代码 题目解析&#xff1a; 题目让我们求总共有多少条不同的路径可到达右下角&#xff1b; 由题可得&#xff1a; 机器人位于…

蚂蚁SEO实用的网络baidu蜘蛛有哪些

网络蜘蛛是一种用于从互联网上自动抓取信息的程序。它们根据给定的规则和指令&#xff0c;遍历网站上的页面&#xff0c;收集信息并将其存储在数据库中。网络蜘蛛在搜索引擎、数据挖掘、信息提取等领域有着广泛的应用。本文将介绍一种实用的网络蜘蛛&#xff0c;并探讨其实现原…

iOS加密CoreML模型

生成模型加密密钥 必须在Xcode的Preferences的Accounts页面登录Apple ID&#xff0c;才能在Xcode中生成模型加密密钥。 在Xcode中打开模型&#xff0c;单击Utilities选项卡&#xff0c;然后单击“Create Encryption Key”按钮。 从下拉菜单中选择当前App的Personal Team&…

【抽象责任链模式】实践优化

责任链模式 原文来自 ——> https://nageoffer.com/pages/51ffef/#%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F &#xff08;小调整重点标注&#xff0c;我是菜鸡&#xff09; 1. 什么是责任链 责任链设计模式是一种行为型设计模式&#xff0c;其主要目的是解耦请求发送…

mongoAltas使用

创建项目 https://cloud.mongodb.com/v2#/org/6548f5a62d5ab00f5b67a61a/projects 部署数据库 选择厂商部署 更改数据库用户和密码 添加数据库可访问地址 添加连接信息到vscode MONGO_URLmongodbsrv://burnchi:burnchicluster0.ynoq32i.mongodb.net/Auth-Mongodb偶尔分享web开…

mysql的redolog、undo、binlog的作用

概览&#xff1a; MySQL三大日志包括&#xff1a;undolog&#xff0c;redo log&#xff0c;binlog&#xff0c;它们分别有以下作用&#xff1a; undolog&#xff1a;是Innodb存储引擎事务生成的日志。用于事务的回滚和MVCC&#xff0c;保证了事务的原子性。 redo log&#x…

H5页面生成工具源码

源码介绍 H5是基于Vue2.0开发的&#xff0c;通过拖拽的形式&#xff0c;生成页面的工具&#xff0c;类似易企秀、百度H5等工具。 H5特征&#xff1a; 1、编辑器 参考线 吸附线、组件对齐 拽改变组件形状 元素: 复制&#xff08;画布&#xff09; 元素: 删除&#xff08…

Fractal-Streets

title: Fractal Streets date: 2023-12-13 14:48:45 tags: 分形 categories: 算法进阶指南 题目大意 将原来的城市复制一遍放在原城市的上方&#xff0c;将原城市顺时针90放在原城市的左上方&#xff0c;将逆时针90后的城市放在原城市的左边&#xff0c;然后用道路将四部分链接…

美赛F奖经验分享,干货满满,快来查收!

2023年美赛结果出来之后&#xff0c;陆续有人给我发私信求经验&#xff0c;跟一些同学交流后我发现&#xff0c;很多人其实对美赛了解程度很少。我借此机会介绍一下美赛&#xff0c;并分享一下获奖经验。我的内容主要包括以下几个部分&#xff1a;美赛是什么、得奖分布、选题建…

AI全栈大模型工程师(二十六)如何选择 GPU 和云服务厂商

&#x1f4a1; 这节课会带给你 如何选择 GPU 和云服务厂商&#xff0c;追求最高性价比 如何部署自己 fine-tune 的模型&#xff0c;向业务提供高可用推理服务 如何控制内容安全&#xff0c;做好算法备案&#xff0c;确保合规 开始上课&#xff01; 硬件选型 当我们为模型训练及…

从传统型数据库到非关系型数据库

一 什么是数据库 数据库顾名思义保存数据的仓库&#xff0c;其本质是一个具有数据存储功能的复杂系统软件&#xff0c;数据库最终把数据保存在计算机硬盘&#xff0c;但数据库并不是直接读写数据在硬盘&#xff0c;而是中间隔了一层操作系统&#xff0c;通过文件系统把数据保存…

挺进云存储,天翼云全新一代XSSD勇立潮头

引言&#xff1a;自研高性能分布式存储引擎LAVA&#xff0c;实现云硬盘持续创新获得新突。 【全球云观察 &#xff5c; 科技热点关注】 作为算力基础设施的基石&#xff0c;云存储的发展一直备受公有云厂商所重视&#xff0c;对拉动云厂商营收规模带来重要价值&#xff0c;就…

用python打印出菱形图案

你可以使用Python编写一个简单的函数来打印菱形图案。下面是一个例子&#xff0c;这个函数接受一个参数n&#xff0c;表示菱形的高度&#xff0c;然后打印出一个菱形图案&#xff1a; def print_diamond(n): # 上半部分 for i in range(n): print(" " …