栈(Stack)详解:定义、作用、应用场景及与队列的区别(附 C/C++ 代码)

1. 什么是栈

栈(Stack)是一种**后进先出(LIFO,Last In First Out)**的线性数据结构。它只允许在一端(称为栈顶 top)进行插入和删除操作,另一端(栈底 bottom)是固定的。可以把栈想象成一摞盘子:后放上去的盘子总是最先被拿走。

栈的核心操作只有两个:

  • 入栈(push):把元素放到栈顶。
  • 出栈(pop):把栈顶元素移除。

此外还有两个常用辅助操作:

  • 取栈顶(top / peek):查看栈顶元素但不移除。
  • 判空(empty):判断栈中是否还有元素。

2. 栈的作用

栈的核心价值在于保存"现场"并支持回溯。因为后进先出的特性,栈天然适合处理需要"先记住状态、稍后再恢复"的场景。具体来说,栈的作用体现在以下几个方面:

  • 保存调用现场:函数调用时,系统用栈保存返回地址、局部变量和参数,函数返回时再从栈中恢复。
  • 实现嵌套与递归:递归调用本质上是不断入栈,回溯时不断出栈。
  • 表达式求值与语法解析:编译器用栈处理运算符优先级、括号匹配等问题。
  • 撤销与回退:编辑器的撤销操作、浏览器的后退功能,本质上都是把历史状态压入栈中。

3. 栈的常见应用场景

3.1 函数调用与递归

程序运行时,每次函数调用都会在系统栈中压入一个栈帧,保存返回地址和局部变量。递归函数尤其依赖栈,递归深度过大时会导致栈溢出(stack overflow)。

3.2 括号匹配

编译器在解析代码时,用栈检查括号是否成对匹配。遇到左括号入栈,遇到右括号出栈并比对类型。

3.3 表达式求值

中缀表达式转后缀表达式、后缀表达式求值,都依赖栈来暂存操作数和运算符。

3.4 浏览器的前进后退

浏览器用两个栈分别记录后退和前进的历史页面。点击后退时,当前页入前进栈,后退栈出栈;点击前进时则相反。

3.5 深度优先搜索(DFS)

图的深度优先遍历可以用栈实现,无论是显式使用栈还是通过递归隐式使用栈。

4. 栈与队列的区别

栈和队列都是线性数据结构,但它们的操作规则完全不同

对比项 栈(Stack) 队列(Queue)
操作规则 后进先出(LIFO) 先进先出(FIFO)
插入位置 栈顶 队尾
删除位置 栈顶 队头
典型比喻 一摞盘子 排队买票
主要应用 函数调用、括号匹配、表达式求值 任务调度、消息队列、广度优先搜索

5. C/C++ 代码实现

5.1 使用标准库(C++)

C++ 标准库提供了 std::stack,可以直接使用:

cpp 复制代码
#include <iostream>
#include <stack>
using namespace std;

int main() {
    stack<int> s;

    // 入栈
    s.push(10);
    s.push(20);
    s.push(30);

    cout << "栈顶元素: " << s.top() << endl; // 30

    // 出栈
    s.pop();
    cout << "出栈后栈顶: " << s.top() << endl; // 20

    // 判空
    cout << "栈是否为空: " << (s.empty() ? "是" : "否") << endl;

    // 遍历(注意:栈只能从栈顶访问)
    while (!s.empty()) {
        cout << s.top() << " ";
        s.pop();
    }
    cout << endl;
    return 0;
}

5.2 数组实现(C 语言)

用数组模拟栈,需要维护一个栈顶指针:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int top; // 栈顶下标,-1 表示空栈
} Stack;

// 初始化
void init(Stack *s) {
    s->top = -1;
}

// 判空
bool isEmpty(Stack *s) {
    return s->top == -1;
}

// 判满
bool isFull(Stack *s) {
    return s->top == MAX_SIZE - 1;
}

// 入栈
bool push(Stack *s, int value) {
    if (isFull(s)) {
        printf("栈已满,无法入栈\n");
        return false;
    }
    s->data[++s->top] = value;
    return true;
}

// 出栈
bool pop(Stack *s, int *value) {
    if (isEmpty(s)) {
        printf("栈为空,无法出栈\n");
        return false;
    }
    *value = s->data[s->top--];
    return true;
}

// 取栈顶
bool peek(Stack *s, int *value) {
    if (isEmpty(s)) {
        printf("栈为空\n");
        return false;
    }
    *value = s->data[s->top];
    return true;
}

int main() {
    Stack s;
    init(&s);

    push(&s, 10);
    push(&s, 20);
    push(&s, 30);

    int val;
    peek(&s, &val);
    printf("栈顶元素: %d\n", val); // 30

    while (pop(&s, &val)) {
        printf("%d ", val);
    }
    printf("\n");
    return 0;
}

5.3 链表实现(C 语言)

链表实现栈可以动态扩容,不受固定大小限制:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct Node {
    int data;
    struct Node *next;
} Node;

typedef struct {
    Node *top; // 栈顶指针
} Stack;

// 初始化
void init(Stack *s) {
    s->top = NULL;
}

// 判空
bool isEmpty(Stack *s) {
    return s->top == NULL;
}

// 入栈(头插法)
void push(Stack *s, int value) {
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->data = value;
    newNode->next = s->top;
    s->top = newNode;
}

// 出栈
bool pop(Stack *s, int *value) {
    if (isEmpty(s)) {
        printf("栈为空,无法出栈\n");
        return false;
    }
    Node *temp = s->top;
    *value = temp->data;
    s->top = temp->next;
    free(temp);
    return true;
}

// 取栈顶
bool peek(Stack *s, int *value) {
    if (isEmpty(s)) {
        printf("栈为空\n");
        return false;
    }
    *value = s->top->data;
    return true;
}

int main() {
    Stack s;
    init(&s);

    push(&s, 10);
    push(&s, 20);
    push(&s, 30);

    int val;
    peek(&s, &val);
    printf("栈顶元素: %d\n", val); // 30

    while (pop(&s, &val)) {
        printf("%d ", val);
    }
    printf("\n");
    return 0;
}

6. 实战:用栈实现括号匹配

下面用 C++ 实现一个经典的括号匹配检查器,这是栈最典型的应用之一:

cpp 复制代码
#include <iostream>
#include <stack>
#include <string>
using namespace std;

bool isMatching(char open, char close) {
    return (open == '(' && close == ')') ||
           (open == '[' && close == ']') ||
           (open == '{' && close == '}');
}

bool isValid(string s) {
    stack<char> st;
    for (char c : s) {
        if (c == '(' || c == '[' || c == '{') {
            st.push(c); // 左括号入栈
        } else if (c == ')' || c == ']' || c == '}') {
            if (st.empty() || !isMatching(st.top(), c)) {
                return false; // 栈空或类型不匹配
            }
            st.pop(); // 匹配成功,出栈
        }
    }
    return st.empty(); // 栈空说明全部匹配
}

int main() {
    string test1 = "{[()]}";
    string test2 = "{[(])}";
    string test3 = "((()))";

    cout << test1 << " : " << (isValid(test1) ? "合法" : "非法") << endl;
    cout << test2 << " : " << (isValid(test2) ? "合法" : "非法") << endl;
    cout << test3 << " : " << (isValid(test3) ? "合法" : "非法") << endl;
    return 0;
}

7. 总结

栈是一种后进先出的线性数据结构,核心操作是入栈和出栈。它的主要作用是保存现场、支持回溯,广泛应用于函数调用、递归、括号匹配、表达式求值、浏览器历史记录和深度优先搜索等场景。与队列相比,栈的特点是后进先出,而队列是先进先出,两者在操作规则和应用场景上有本质区别。掌握栈的原理和实现,是理解程序运行机制和算法设计的重要基础。

相关推荐
zmzb010314 分钟前
C++课后习题训练记录Day203
开发语言·c++
wuyk55515 分钟前
从零吃透 Modbus 通信|第 6 章:线圈功能码 05/0F 实现 & Modbus‑TCP 基础入门
c语言·网络·网络协议·tcp/ip
zzzll111135 分钟前
LangChain4j:Java 生态的 AI 应用开发利器
java·开发语言·人工智能
Dream Cosmos1 小时前
Linux 多线程——线程互斥:从抢票问题到 Mutex 底层原理
linux·c++
JAI科研1 小时前
Deepseek Agent Harness教程(七) | Deepseek Harness不是一个内核加一堆插件
开发语言·人工智能·深度学习·算法·自然语言处理·transformer·vllm
zbyyd1 小时前
Linux 进程间通信(IPC):信号、消息队列、共享内存与信号量详解
linux·运维·c语言
孙克旭_1 小时前
单链表进阶实操:5 道常考面试题详细解析【Java 实现】
java·开发语言·数据结构·单链表
Titan20241 小时前
HTTPS基础知识梳理
服务器·网络·c++·网络协议·学习·http·https
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】SSE协议
c++·学习·面试·大模型·sdk