数据结构 | 顺序栈

一、基础知识

栈是一种操作受限的特殊线性表,依托顺序表或者链表进行底层实现,在元素的插入、删除行为上做了严格约束,只允许在固定一端完成增删,是数据结构中最基础、面试考察频率极高的线性结构。

栈(Stack)核心特性:后进先出(LIFO),也就是后进入容器的元素会优先被取出,先存入的元素会被压在底层,最后才能操作。

基础概念

  1. 栈顶:栈中唯一允许执行插入、删除操作的一端,也是整个栈的动态操作端
  2. 栈底:位置固定、不允许做增删修改的一端,作为栈的底层容器
  3. 入栈:向栈顶位置添加新元素的操作,也叫压栈
  4. 出栈:删除栈顶现有元素的操作,也叫弹栈
  5. 空栈:栈内部不存在任何有效数据元素,无法执行出栈、获取栈顶等操作

应用场景

常见的括号匹配校验、函数递归底层调用、四则运算表达式求值、浏览器页面后退功能、文档编辑器撤销操作、系统临时数据缓存等场景,全部依托栈的后进先出特性实现,学好栈是掌握复杂数据结构的关键基础。


二、顺序栈 vs 链栈

栈整体分为两种实现方案:顺序栈(基于顺序表)链栈(基于单链表),两种实现各有优劣,本文采用顺序表实现栈。

顺序表 本身的操作特性存在明显差异:若在顺序表头部 进行插入、删除,需要批量移动后方所有元素,时间复杂度为 O (n),运行效率极低;若在顺序表尾部进行插入、删除,无需挪动任何元素,仅通过下标直接定位操作,时间复杂度稳定为 O (1),执行效率极高。

| 操作方式 | 头插头删 | 尾插尾删 |

时间复杂度 O(n) O(1)

结合栈只能单端操作的规则,为了保证程序运行性能,顺序栈统一选择顺序表尾部作为栈顶,全程在尾部完成入栈、出栈逻辑,最大化发挥顺序表的性能优势。


三、结构体设计

设计思想

顺序栈底层依托动态数组构建,区别于固定大小的静态数组,动态数组可以灵活指定栈容量,适配不同场景的数据存储需求。结合栈的运行逻辑,结构体需要包含三大核心成员,分别负责数据存储、栈顶标记、容量限制,三者配合完成所有栈操作。

  1. 动态数组:连续内存空间,负责存储栈中所有有效元素
  2. 栈顶下标:整型变量,实时标记当前栈顶元素的位置,是控制入栈、出栈的核心标识
  3. 最大容量:记录当前栈的最大存储上限,用于判断栈满状态,防止数组越界访问

结构体定义

cpp 复制代码
// 统一元素类型,后期维护可一键修改
typedef int ELEMTYPE;
// 顺序栈结构体
struct SeqStack
{
	// 动态数组:连续内存,存放栈数据
	ELEMTYPE* data;		
	// 栈顶下标:记录当前栈顶位置
	int top;			
	// 最大容量:限制栈存储空间
	int max_size;		
};

下标规则

  • 空栈标准定义:top = -1,代表栈内无有效元素
  • 有效元素总数:top + 1,下标从 0 开始计数
  • 栈满判定条件:栈顶下标 = 最大容量 - 1,数组空间完全占用

四、函数接口总览

结合栈的完整使用流程,封装全套标准化接口,覆盖初始化、增删、查询、状态判断、资源释放全流程,接口命名规范统一,逻辑分层清晰,符合 C++ 开发编码习惯。

cpp 复制代码
// 1. 初始化顺序栈:指定栈容量,开辟动态内存
void InitSeqStack(SeqStack& st, int capacity);
// 2. 入栈操作:向栈顶添加新元素
bool Push(SeqStack& st, ELEMTYPE val);
// 3. 出栈操作:删除当前栈顶元素
bool Pop(SeqStack& st);
// 4. 获取栈顶元素:只读访问栈顶数据
ELEMTYPE GetTop(SeqStack& st);
// 5. 获取元素个数:统计栈内有效数据数量
int GetSize(SeqStack& st);
// 6. 判空操作:检测栈是否为空
bool IsEmpty(SeqStack& st);
// 7. 判满操作:检测栈是否存满
bool IsFull(SeqStack& st);
// 8. 遍历打印:正向输出栈内所有元素
void ShowStack(SeqStack& st);
// 9. 销毁顺序栈:释放动态堆内存,防止内存泄漏
void DestroySeqStack(SeqStack& st);

五、函数实现

1. 顺序栈初始化

设计思路 :初始化是栈使用的前置操作,首先校验传入容量的合法性,避免负数或 0 容量;再通过new动态开辟 数组内存,为栈容器分配连续空间;最后初始化栈顶下标与最大容量,默认设置top = -1,保证初始化完成后栈处于空栈状态。

cpp 复制代码
void InitSeqStack(SeqStack& st, int capacity)
{
	// 断言校验:容量必须大于0,杜绝非法空间申请
	assert(capacity > 0);
	// C++动态开辟数组内存,初始化默认值
	st.data = new ELEMTYPE[capacity]{};
	// 空栈标识:栈顶下标初始化为-1
	st.top = -1;			
	// 绑定栈的最大存储容量
	st.max_size = capacity;	
}

2. 入栈操作

设计思路 :入栈是栈的核心新增操作,第一步先判断栈的状态 ,如果栈已满,直接禁止入栈并给出提示,防止数组越界;栈空间充足时,优先将栈顶下标向后移动一位,再将目标元素存入当前栈顶下标对应的数组位置,完成数据压栈,全程在数组尾部 操作,保证时间复杂度O (1)

cpp 复制代码
bool Push(SeqStack& st, ELEMTYPE val)
{
	// 前置判断:栈满则无法继续入栈
	if (IsFull(st))
	{
		cout << "栈已满,入栈失败!无法添加新元素" << endl;
		return false;
	}
	// 栈顶上移,更新栈顶位置,存入新元素
	st.data[++st.top] = val;
	return true;
}

3. 出栈操作

设计思路 :出栈用于删除栈顶元素,执行逻辑与入栈相反。首先检测栈是否为空 ,空栈无元素可删除,直接拦截操作;栈为正常状态时,无需手动清空数组数据,只需要将栈顶下标向前移动一位,逻辑上覆盖 原有栈顶元素,简化操作的同时保证高效执行,后续新元素入栈会直接覆盖废弃数据

cpp 复制代码
bool Pop(SeqStack& st)
{
	// 前置判断:空栈禁止出栈操作
	if (IsEmpty(st))
	{
		cout << "栈为空,出栈失败!无元素可删除" << endl;
		return false;
	}
	// 栈顶下标前移,逻辑删除栈顶元素
	st.top--;
	return true;
}

4. 获取栈顶元素

设计思路 :该功能为只读操作 ,不会修改栈的结构与数据。通过断言强制校验栈非空,避免空栈访问导致程序崩溃;利用栈顶下标直接定位数组末尾元素,快速返回栈顶数值,满足数据查询需求,常用于数据比对、条件判断等场景。

cpp 复制代码
ELEMTYPE GetTop(SeqStack& st)
{
	// 断言防护:空栈不允许获取栈顶
	assert(!IsEmpty(st));
	// 通过栈顶下标,直接返回栈顶元素
	return st.data[st.top];
}

5. 判空与判满

设计思路 :两个状态判断函数是所有栈操作的基础依赖,逻辑简单但至关重要。判空 直接检测栈顶下标是否为初始值 - 1;判满检测栈顶下标是否达到数组最大边界,两个函数独立封装,降低代码冗余,方便其他函数直接调用。

cpp 复制代码
// 判空:top=-1 即为空栈
bool IsEmpty(SeqStack& st)
{
	return st.top == -1;
}
// 判满:栈顶下标到达数组最后一位
bool IsFull(SeqStack& st)
{
	return st.top == st.max_size - 1;
}

6. 获取有效元素个数

设计思路:顺序栈数组下标从 0 开始存储数据,栈顶下标代表最后一个元素的位置,因此有效元素总数 = 栈顶下标 + 1。通过简单计算即可快速统计数量,无需循环遍历,执行效率极高。

cpp 复制代码
int GetSize(SeqStack& st)
{
	// 下标从0开始,元素总数 = 栈顶+1
	return st.top + 1;
}

7. 遍历打印栈元素

设计思路:从数组起始下标遍历至栈顶下标,只输出有效范围内的元素,过滤未使用的空闲空间;增加空栈判断提示,优化交互体验,直观展示栈内元素存储顺序,方便代码调试与学习观察。

cpp 复制代码
void ShowStack(SeqStack& st)
{
	if (IsEmpty(st))
	{
		cout << "当前栈为空,无有效元素" << endl;
		return;
	}
	cout << "栈内元素顺序:";
	// 遍历有效元素区间,循环打印
	for (int i = 0; i <= st.top; i++)
	{
		cout << st.data[i] << " ";
	}
	cout << endl;
}

8. 顺序栈销毁

设计思路 :顺序栈使用new申请堆内存,程序结束后必须手动释放 ,否则会造成内存泄漏。通过delete[]释放动态数组空间,再将所有成员变量重置为初始状态,彻底销毁栈结构,保证内存安全,养成规范的编码习惯。

cpp 复制代码
void DestroySeqStack(SeqStack& st)
{
	// 释放堆区动态数组内存
	delete[] st.data;
	// 野指针置空,防止非法访问
	st.data = nullptr;
	// 重置栈状态,回归空栈
	st.top = -1;
	st.max_size = 0;
}

六、总结

1. 本篇知识点总结

栈属于操作受限的线性表 ,严格遵循后进先出的核心规则,仅支持单端增删。结合顺序表操作效率差异,顺序栈选择尾部作为栈顶 ,保证入栈、出栈时间复杂度稳定为 O (1),顺序栈底层依靠动态数组 + 栈顶下标协同工作,通过下标移动实现元素增删,无需移动数据。

  • 三大核心操作:入栈下移栈顶存数据、出栈上移栈顶删数据、栈顶下标直接访问末尾元素
  • 顺序栈整体优势:内存连续、随机访问、实现简单、执行速度快,适合数据量固定的场景
  • 顺序栈明显短板:初始化容量固定,无法动态扩容,空间分配不合理时容易造成资源浪费或栈满溢出

2. 下篇内容预告

本文完整讲解了顺序栈 全部原理与 C++ 实现,下一篇博客将开启链栈。底层基于单链表实现栈结构,无需提前固定容量。以链表头部作为栈顶,动态申请节点内存,按需分配空间,解决顺序栈容量固化的痛点,灵活适配数据量不确定的场景。


相关推荐
疯狂打码的少年2 小时前
数据结构图的存储方式:从邻接矩阵到十字链表,一文打尽
数据结构·链表
Queenie_Charlie2 小时前
关于二叉树(2)
数据结构·c++·二叉树·简单树结构
澈2072 小时前
算法进阶:二叉树翻转与环形链表解析
数据结构·算法·排序算法
代码飞天2 小时前
算法与数据结构之树——让数据查找更加迅速
数据结构·算法
故事和你912 小时前
洛谷-算法2-2-常见优化技巧1
开发语言·数据结构·c++·算法·动态规划·图论
酉鬼女又兒2 小时前
JavaLeetCode 第一题「两数之和」:从暴力枚举到一遍哈希表的正确与错误实现,详解HashMap核心知识点及常见陷阱
java·开发语言·数据结构·算法·leetcode·职场和发展·散列表
云淡风轻~窗明几净2 小时前
关于TSP的sealine算法与角谷猜想(2026-04-25)
数据结构·人工智能·算法·动态规划·模拟退火算法
自我意识的多元宇宙2 小时前
【数据结构】图----图的应用(拓扑排序)
数据结构·算法
Lazionr2 小时前
双向链表及链表篇总结
数据结构·链表