C/C++ 数据结构(七)栈、容器适配器

本篇核心知识:栈、容器适配器、栈的分类(顺序栈、链栈)、基础操作


一、栈基本概念

1. 定义

栈是受限线性表,属于基础数据结构。

核心规则:先进后出(FILO / 后进先出 LIFO)

操作限制:插入(入栈)、删除(出栈)仅能在同一端进行 ,这一端称为栈顶 ;另一端固定不可操作,称为栈底

2. 专业术语

  1. 栈顶(top):允许插入、删除、访问的一端,栈的操作核心端。

  2. 栈底(bottom):固定端,仅作为栈的起始位置,不做增删操作。

  3. 入栈(压栈 /push):向栈中添加元素。

  4. 出栈(弹栈 /pop):从栈顶删除元素。

  5. 栈空:栈内无任何元素。

  6. 栈满:顺序栈特有概念,存储空间被占满,无法继续入栈。

  7. 栈大小:栈中当前元素总个数。


二、栈的整体分类

按照底层存储结构 划分,分为顺序栈链栈 两大类;在 C++ 标准库中,栈属于容器适配器

1. 顺序栈

(1)底层结构

依托**数组(连续内存)**实现,数组整体作为栈的存储空间。

数组下标:通常用下标0作为栈底,最后一个有效元素下标作为栈顶。

标识:一般定义一个栈顶指针 / 下标变量,记录当前栈顶位置。

(2)核心特点
  1. 内存连续,访问速度快(随机访问能力)。

  2. 存在栈满问题,数组长度固定,空间一旦耗尽无法扩容(静态顺序栈)。

  3. 实现简单、代码简洁,适合元素数量固定、预估上限的场景。

  4. 增删操作仅在数组尾部完成,效率极高。

(3)基础结构示意
复制代码
// C语言 静态顺序栈 简易结构
#define MAX_SIZE 100  // 栈最大容量
int stack[MAX_SIZE]; // 数组作为栈空间
int top = -1;        // 栈顶下标:-1 表示栈空

top = -1:栈空;

top == MAX_SIZE - 1:栈满。

2. 链栈(课堂重点讲解)

(1)底层结构

依托单链表(非连续内存) 实现,链表整体作为栈,链表头部作为栈顶(链表头部增删效率最高)。

节点结构:分为数据域 (存储元素) + 指针域(指向下一个节点)。

无固定容量,理论上只要内存足够可无限添加元素,不存在栈满

(2)核心特点
  1. 内存不连续,动态分配节点内存,按需申请、按需释放。

  2. 无栈满限制,适合元素数量动态变化、无法预估上限的场景。

  3. 每次入 / 出栈都需要操作节点指针,额外消耗少量内存(指针域)。

  4. 链表头部作为栈顶,增删效率和顺序栈一致。

(3)节点结构(C/C++ 通用)
复制代码
// 链栈节点结构
struct Node {
    int data;        // 数据域:存储栈元素
    Node* next;      // 指针域:指向下一个节点
};
Node* top = nullptr; // 栈顶指针,指向链表头部,nullptr表示栈空
(4)链栈节点指向规则(课堂重点)

栈顶节点的next指向前一个入栈的节点

原因:若反向指向,出栈时无法回溯找到上一级节点,会造成逻辑死区。

3. C++ 容器适配器:std::stack

(1)概念

容器适配器:不独立实现底层存储 ,复用已有标准容器(vector/deque/list)封装出栈结构。

默认底层:deque(双端队列)。

特性:仅对外暴露栈的标准接口,屏蔽底层容器的其他操作。

(2)接口规则

std::stack 严格遵循栈规则:仅支持栈顶操作,不允许访问中间 / 栈底元素。

常用接口:push()入栈、pop()出栈、top()取栈顶元素、empty()判空、size()获取元素个数。

(3)与原生栈的区别

无需手动管理内存,标准库封装完成,开发效率高。

底层可灵活切换容器,适配不同性能需求。


三、栈的通用基础操作(顺序栈 + 链栈 通用逻辑)

所有栈的核心操作共 5 个:判空、入栈、出栈、取栈顶元素、获取栈大小

重要原则:出栈、取栈顶前,必须先判断栈是否为空,空栈执行这两个操作会引发程序异常。

1. 判空(empty)

顺序栈:判断栈顶下标top == -1

链栈:判断栈顶指针top == nullptr

标准栈:直接调用stack.empty()

2. 入栈(push)

  1. 顺序栈:先判断是否栈满,未满则top++,再向stack[top]赋值。

  2. 链栈:新建节点 → 新节点next指向原栈顶 → 更新栈顶为新节点。

  3. 标准栈:stack.push(元素)

3. 出栈(pop)

  1. 顺序栈:先判空,非空则直接top--(逻辑删除,数据残留,新元素会覆盖)。

  2. 链栈:先判空,暂存原栈顶节点 → 栈顶指针后移 → 释放原节点内存(防止内存泄漏)。

  3. 标准栈:stack.pop()(仅删除,不返回元素)。

4. 取栈顶元素(top)

仅读取栈顶数据,不删除元素

前提:栈非空。

5. 获取栈元素个数(size)

顺序栈:top + 1(top 从 - 1 开始)。

链栈:需遍历链表统计节点个数(无直接记录时);也可额外定义变量实时计数。

标准栈:stack.size()


四、代码实现(结合课堂讲解)

1. 静态顺序栈(C 实现)

复制代码
#include <stdio.h>
#define MAX 10  // 栈最大容量
​
int stack[MAX];
int top = -1; // 栈空标识
// 1. 判空
int isEmpty() {
    return top == -1;
}
// 2. 判满
int isFull() {
    return top == MAX - 1;
}
// 3. 入栈
void push(int val) {
    if (isFull()) {
        printf("栈已满,无法入栈\n");
        return;
    }
    stack[++top] = val;
}
// 4. 出栈
void pop() {
    if (isEmpty()) {
        printf("栈为空,无法出栈\n");
        return;
    }
    top--;
}
// 5. 取栈顶
int getTop() {
    if (isEmpty()) return -1;
    return stack[top];
}

2. 链栈(C++ 实现)

复制代码
#include <iostream>
using namespace std;
// 链栈节点
struct Node {
    int data;
    Node* next;
};
Node* top = nullptr; // 栈顶指针
​
// 判空
bool isEmpty() {
    return top == nullptr;
}
​
// 入栈
void push(int val) {
    // 1. 新建节点
    Node* newNode = new Node;
    newNode->data = val;
    // 2. 新节点指向原栈顶
    newNode->next = top;
    // 3. 更新栈顶
    top = newNode;
}
​
// 出栈
void pop() {
    if (isEmpty()) {
        cout << "栈空,无法出栈" << endl;
        return;
    }
    Node* temp = top;  // 暂存待删除节点
    top = top->next;   // 栈顶后移
    delete temp;       // 释放内存,防止内存泄漏
}
​
// 取栈顶
int getTop() {
    if (isEmpty()) return 0;
    return top->data;
}

3. C++ 标准栈(std::stack)

复制代码
#include <iostream>
#include <stack>
using namespace std;
​
int main() {
    stack<int> st;
    st.push(100);  // 入栈
    st.push(200);
​
    cout << st.top() << endl;
    st.pop();
    cout << st.top() << endl;
​
    if (!st.empty()) {
        st.pop();
    }
    cout << st.size() << endl;
    return 0;
}

五、顺序栈 vs 链栈 对比总结

对比项 顺序栈 链栈
底层结构 数组,连续内存 链表,非连续内存
容量限制 固定容量,存在栈满 动态扩容,无栈满
内存开销 仅存储数据,开销小 额外存储指针,开销略大
访问速度 连续内存,访问更快 链式遍历,速度略低
适用场景 元素数量固定、数据量小 元素动态变化、大数据量
内存泄漏风险 无(数组自动回收) 需手动释放节点,风险高

六、栈的核心特性与拓展知识点

1. 内存栈(程序栈区,拓展)

日常代码中栈内存和数据结构栈逻辑一致:

函数局部变量、形参、函数调用栈帧,都存储在栈区

函数调用规则:先调用后释放,完全遵循后进先出。

风险:栈空间大小有限,递归过深会造成栈溢出(Stack Overflow)

2. 栈的经典应用场景

  1. 函数调用 & 递归

    程序利用栈记录函数调用层级、返回地址,递归的逐层调用与回溯完全依赖栈结构。

  2. 括号匹配校验

    编译器 / 编辑器校验()[]{},左括号入栈,右括号出栈,最终栈空则匹配合法。

  3. 表达式求值(后缀表达式)

    中缀转后缀、后缀表达式计算,是栈最经典的算法应用。

  4. 浏览器前进 / 后退、APP 页面跳转

    页面跳转压入栈,后退就是出栈。

  5. 撤销 / 恢复功能

    (Word、PS 等软件)

    每一步操作入栈,撤销即出栈。

  6. 进制转换(十进制转二进制 / 八进制)

    除基取余,余数入栈,最后依次出栈得到结果。

3. 易错点总结(课堂强调)

  1. 空栈操作:出栈、取栈顶必须先判空,否则代码崩溃。

  2. 链栈内存管理 :出栈必须delete节点,长期不释放会造成内存泄漏

  3. 顺序栈栈满:静态数组栈容量固定,超出上限无法入栈。

  4. 栈的访问限制严禁随机访问中间元素,只能操作栈顶,这是栈的核心约束。

  5. std::stack 注意:pop() 只删除元素,不返回栈顶值 ,需要先用top()获取。

4. 容器适配器补充(课堂内容)

C++ std::stack 默认基于deque实现,也可指定底层容器:

复制代码
stack<int, vector<int>> st; // 底层用vector
stack<int, list<int>> st2;  // 底层用list

选择原则:追求头尾操作效率优先deque;单纯数组存储选vector


七、拓展:栈的衍生结构

  1. 双端栈:一个数组两端分别作为栈顶,共享存储空间,节约内存。

  2. 共享栈 :两个栈共用一片连续内存,相向增长。

  3. 单调栈:算法专用栈,栈内元素保持单调递增 / 递减,常用于最大矩形、接雨水等经典算法题。

相关推荐
謓泽1 小时前
C语言不是语法,是通往机器的地图。
c语言·开发语言
不会C语言的男孩1 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
один but you2 小时前
constexpr函数
c++
Qres8212 小时前
算法复键——树状数组
数据结构·算法
凡人叶枫3 小时前
Effective C++ 条款41:了解隐式接口和编译期多态
java·开发语言·c++·effective c++
凡人叶枫3 小时前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++
2601_951643883 小时前
C语言长文整理,关键字和数据类型
c语言·数据类型·关键字·嵌入式开发·格式化输出
小胖xiaopangss3 小时前
BRpc使用
c++·rpc
-森屿安年-3 小时前
63. 不同路径 II
c++·算法·动态规划