跳转至

概述

栈(Stack)是一种**后进先出**(LIFO: Last In First Out)的线性数据结构。栈只允许在表的一端(称为栈顶,Top)进行插入和删除操作,另一端称为栈底(Bottom)。

栈的生活类比

想象一摞盘子:你只能从顶部放新盘子(push),也只能从顶部取盘子(pop)。最后放上去的盘子,最先被取走——这就是LIFO原则。

栈的抽象模型

栈的结构示意

5
↑ 栈顶 (Top) ← push/pop位置
4
3
2
1
栈底 (Bottom)
操作序列: push(1)push(2)push(3)push(4)push(5)
graph TB
    subgraph "栈的LIFO特性"
        A["push 1, 2, 3"] --> B["栈: [1, 2, 3]"]
        B --> C["pop → 返回 3"]
        C --> D["栈: [1, 2]"]
        D --> E["pop → 返回 2"]
        E --> F["栈: [1]"]
    end
    
    style C fill:#E8F5E9
    style E fill:#E8F5E9

栈的基本操作

操作 描述 时间复杂度
push(x) 将元素x压入栈顶 O(1)
pop() 弹出栈顶元素并返回 O(1)
peek()/top() 返回栈顶元素(不弹出) O(1)
isEmpty() 判断栈是否为空 O(1)
size() 返回栈中元素个数 O(1)

栈的两种实现方式

数组实现 vs 链表实现

graph TB
    subgraph "数组实现(顺序栈)"
        A1["连续内存块"]
        A2["top指针记录栈顶位置"]
        A3["可能需要扩容"]
        A4["缓存友好"]
    end
    
    subgraph "链表实现(链式栈)"
        B1["分散内存节点"]
        B2["head指针指向栈顶"]
        B3["无需扩容,动态增长"]
        B4["每个节点额外指针开销"]
    end
    
    style A4 fill:#E8F5E9
    style B3 fill:#E8F5E9
特性 数组实现 链表实现
空间预分配 需要 不需要
扩容代价 O(n)复制
空间利用率 可能有浪费 每节点多一个指针
缓存友好 是 ✓ 否 ✗
实现复杂度 简单 中等

数组实现(顺序栈)

结构设计与内存布局

栈的数组实现内存布局

初始状态 (top = -1):

0
1
2
3
4
↑ top = -1 (空栈)

push(10) 后 (top = 0):

10
0
1
2
3
4
↑ top = 0

push(20), push(30) 后 (top = 2):

10
20
30
0
1
2
3
4
↑ top = 2

完整代码实现

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

#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];  // 数据存储区
    int top;              // 栈顶指针(索引)
} ArrayStack;

// 初始化栈
void initStack(ArrayStack *s) {
    s->top = -1;  // -1表示空栈
}

// 判断栈是否为空
bool isEmpty(ArrayStack *s) {
    return s->top == -1;
}

// 判断栈是否已满
bool isFull(ArrayStack *s) {
    return s->top == MAX_SIZE - 1;
}

// 入栈操作
void push(ArrayStack *s, int value) {
    if (isFull(s)) {
        printf("栈溢出 (Stack Overflow)!\n");
        return;
    }
    s->data[++s->top] = value;  // 先移动top,再存入元素
    printf("Push %d, top = %d\n", value, s->top);
}

// 出栈操作
int pop(ArrayStack *s) {
    if (isEmpty(s)) {
        printf("栈下溢 (Stack Underflow)!\n");
        return -1;
    }
    return s->data[s->top--];  // 先返回元素,再移动top
}

// 查看栈顶元素
int peek(ArrayStack *s) {
    if (isEmpty(s)) {
        printf("栈为空!\n");
        return -1;
    }
    return s->data[s->top];
}

// 获取栈的大小
int size(ArrayStack *s) {
    return s->top + 1;
}

// 打印栈内容
void printStack(ArrayStack *s) {
    if (isEmpty(s)) {
        printf("栈为空\n");
        return;
    }
    
    printf("栈内容(底→顶): ");
    for (int i = 0; i <= s->top; i++) {
        printf("%d ", s->data[i]);
    }
    printf("\n");
}
C++
#include <vector>
#include <iostream>

class Stack {
private:
    std::vector<int> data;
    
public:
    // 入栈
    void push(int value) {
        data.push_back(value);
    }
    
    // 出栈
    int pop() {
        if (isEmpty()) {
            throw std::runtime_error("Stack Underflow");
        }
        int value = data.back();
        data.pop_back();
        return value;
    }
    
    // 查看栈顶
    int peek() {
        if (isEmpty()) {
            throw std::runtime_error("Stack is empty");
        }
        return data.back();
    }
    
    // 判空
    bool isEmpty() {
        return data.empty();
    }
    
    // 获取大小
    int size() {
        return data.size();
    }
};
Python
class Stack:
    """数组实现的栈"""
    def __init__(self, max_size=100):
        self.data = []
        self.max_size = max_size
    
    def push(self, value):
        """入栈"""
        if len(self.data) >= self.max_size:
            raise OverflowError("Stack Overflow")
        self.data.append(value)
    
    def pop(self):
        """出栈"""
        if not self.data:
            raise IndexError("Stack Underflow")
        return self.data.pop()
    
    def peek(self):
        """查看栈顶"""
        if not self.data:
            raise IndexError("Stack is empty")
        return self.data[-1]
    
    def is_empty(self):
        """判空"""
        return len(self.data) == 0
    
    def size(self):
        """获取大小"""
        return len(self.data)
Java
import java.util.ArrayList;
import java.util.EmptyStackException;

public class Stack {
    private ArrayList<Integer> data;
    private int maxSize;
    
    public Stack() {
        this(100);
    }
    
    public Stack(int maxSize) {
        this.data = new ArrayList<>();
        this.maxSize = maxSize;
    }
    
    public void push(int value) {
        if (data.size() >= maxSize) {
            throw new StackOverflowError("Stack Overflow");
        }
        data.add(value);
    }
    
    public int pop() {
        if (isEmpty()) {
            throw new EmptyStackException();
        }
        return data.remove(data.size() - 1);
    }
    
    public int peek() {
        if (isEmpty()) {
            throw new EmptyStackException();
        }
        return data.get(data.size() - 1);
    }
    
    public boolean isEmpty() {
        return data.isEmpty();
    }
    
    public int size() {
        return data.size();
    }
}
Go
package main

import "errors"

// Stack 栈结构
type Stack struct {
    data    []int
    maxSize int
}

// NewStack 创建新栈
func NewStack(maxSize int) *Stack {
    return &Stack{
        data:    make([]int, 0, maxSize),
        maxSize: maxSize,
    }
}

// Push 入栈
func (s *Stack) Push(value int) error {
    if len(s.data) >= s.maxSize {
        return errors.New("stack overflow")
    }
    s.data = append(s.data, value)
    return nil
}

// Pop 出栈
func (s *Stack) Pop() (int, error) {
    if len(s.data) == 0 {
        return 0, errors.New("stack underflow")
    }
    value := s.data[len(s.data)-1]
    s.data = s.data[:len(s.data)-1]
    return value, nil
}

// Peek 查看栈顶
func (s *Stack) Peek() (int, error) {
    if len(s.data) == 0 {
        return 0, errors.New("stack is empty")
    }
    return s.data[len(s.data)-1], nil
}

// IsEmpty 判空
func (s *Stack) IsEmpty() bool {
    return len(s.data) == 0
}

// Size 获取大小
func (s *Stack) Size() int {
    return len(s.data)
}
Rust
pub struct Stack {
    data: Vec<i32>,
    max_size: usize,
}

impl Stack {
    pub fn new(max_size: usize) -> Self {
        Stack {
            data: Vec::with_capacity(max_size),
            max_size,
        }
    }
    
    pub fn push(&mut self, value: i32) -> Result<(), &'static str> {
        if self.data.len() >= self.max_size {
            return Err("Stack overflow");
        }
        self.data.push(value);
        Ok(())
    }
    
    pub fn pop(&mut self) -> Result<i32, &'static str> {
        self.data.pop().ok_or("Stack underflow")
    }
    
    pub fn peek(&self) -> Result<i32, &'static str> {
        self.data.last().copied().ok_or("Stack is empty")
    }
    
    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }
    
    pub fn size(&self) -> usize {
        self.data.len()
    }
}

操作示意

graph LR
    subgraph "Push操作"
        A1["检查是否满栈"] --> B1["top加1"]
        B1 --> C1["data[top] = value"]
    end
    
    subgraph "Pop操作"
        A2["检查是否空栈"] --> B2["返回data[top]"]
        B2 --> C2["top减1"]
    end
    
    style C1 fill:#E8F5E9
    style C2 fill:#E8F5E9

链表实现(链式栈)

结构设计

Text Only
链式栈结构:

栈顶指针 head
┌──────┐    ┌──────┐    ┌──────┐
│  30  │───→│  20  │───→│  10  │───→ NULL
│ next │    │ next │    │ next │
└──────┘    └──────┘    └──────┘
  节点3       节点2       节点1

特点:栈顶在链表头部,push/pop都是O(1)

完整代码实现

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

// 链表节点
typedef struct StackNode {
    int data;
    struct StackNode *next;
} StackNode;

// 链式栈
typedef struct {
    StackNode *top;   // 栈顶指针(链表头)
    int size;          // 栈大小
} LinkedStack;

// 初始化栈
void initLinkedStack(LinkedStack *s) {
    s->top = NULL;
    s->size = 0;
}

// 判断栈是否为空
bool isEmptyLS(LinkedStack *s) {
    return s->top == NULL;
}

// 入栈(头插法)
void pushLS(LinkedStack *s, int value) {
    StackNode *newNode = (StackNode *)malloc(sizeof(StackNode));
    newNode->data = value;
    newNode->next = s->top;  // 新节点指向原栈顶
    s->top = newNode;         // 更新栈顶
    s->size++;
}

// 出栈
int popLS(LinkedStack *s) {
    if (isEmptyLS(s)) {
        printf("栈下溢!\n");
        return -1;
    }
    
    StackNode *temp = s->top;
    int value = temp->data;
    s->top = s->top->next;  // 栈顶下移
    free(temp);              // 释放节点
    s->size--;
    
    return value;
}

// 查看栈顶元素
int peekLS(LinkedStack *s) {
    if (isEmptyLS(s)) {
        printf("栈为空!\n");
        return -1;
    }
    return s->top->data;
}

// 释放栈内存
void freeLinkedStack(LinkedStack *s) {
    while (!isEmptyLS(s)) {
        popLS(s);
    }
}

栈的核心应用

1. 函数调用栈

graph TB
    subgraph "函数调用过程"
        A["main 调用 funcA"] --> B["push main的返回地址"]
        B --> C["funcA 调用 funcB"]
        C --> D["push funcA的返回地址"]
        D --> E["funcB 执行完毕"]
        E --> F["pop 返回funcA"]
        F --> G["funcA 执行完毕"]
        G --> H["pop 返回main"]
    end
    
    style F fill:#E8F5E9
    style H fill:#E8F5E9
Text Only
调用栈示意:

┌─────────────────┐
│ funcB 的栈帧    │ ← 栈顶
├─────────────────┤
│ funcA 的栈帧    │
├─────────────────┤
│ main 的栈帧     │ ← 栈底
└─────────────────┘

每个栈帧包含:
- 返回地址
- 局部变量
- 参数
- 保存的寄存器

2. 括号匹配

graph TB
    subgraph "括号匹配过程: [ 2 + 3 × 4"
        A["遇到 '[' → push"]
        B["栈: ['[']"]
        C["遇到 ']' → pop匹配"]
        D["栈: 空 ✓"]
    end
    
    A --> B --> C --> D
    
    style D fill:#E8F5E9
C
#include <string.h>
#include <stdbool.h>

// 括号匹配检测
bool isValidParentheses(char *s) {
    ArrayStack stack;
    initStack(&stack);
    
    for (int i = 0; s[i] != '\0'; i++) {
        char c = s[i];
        
        // 左括号入栈
        if (c == '(' || c == '[' || c == '{') {
            push(&stack, c);
        } 
        // 右括号匹配
        else if (c == ')' || c == ']' || c == '}') {
            if (isEmpty(&stack)) return false;  // 没有左括号可匹配
            
            char top = pop(&stack);
            // 检查匹配
            if ((c == ')' && top != '(') ||
                (c == ']' && top != '[') ||
                (c == '}' && top != '{')) {
                return false;  // 括号不匹配
            }
        }
    }
    
    return isEmpty(&stack);  // 栈必须为空才算完全匹配
}

3. 表达式求值

后缀表达式(逆波兰表达式)

Text Only
中缀表达式: 3 + 4 × 2 - 1
后缀表达式: 3 4 2 × + 1 -

后缀表达式求值过程:
┌────────────────────────────────────────┐
│ 输入: 3 4 2 × + 1 -                     │
├────────────────────────────────────────┤
│ 读入3:   栈: [3]                        │
│ 读入4:   栈: [3, 4]                     │
│ 读入2:   栈: [3, 4, 2]                  │
│ 读入×:   pop 2, 4; push 4×2=8; 栈: [3, 8]│
│ 读入+:   pop 8, 3; push 3+8=11; 栈: [11]│
│ 读入1:   栈: [11, 1]                    │
│ 读入-:   pop 1, 11; push 11-1=10        │
│ 结果: 10                                │
└────────────────────────────────────────┘
C
#include <ctype.h>
#include <string.h>

// 后缀表达式求值
int evaluatePostfix(char *expression) {
    ArrayStack stack;
    initStack(&stack);
    
    char *token = strtok(expression, " ");
    
    while (token != NULL) {
        // 如果是数字,入栈
        if (isdigit(token[0])) {
            push(&stack, atoi(token));
        } 
        // 如果是运算符,弹出两个操作数计算
        else {
            int b = pop(&stack);  // 第二个操作数(先弹出)
            int a = pop(&stack);  // 第一个操作数
            
            switch (token[0]) {
                case '+': push(&stack, a + b); break;
                case '-': push(&stack, a - b); break;
                case '*': push(&stack, a * b); break;
                case '/': push(&stack, a / b); break;
                case '^': push(&stack, pow(a, b)); break;
            }
        }
        token = strtok(NULL, " ");
    }
    
    return pop(&stack);
}

中缀转后缀

C
// 获取运算符优先级
int precedence(char op) {
    switch (op) {
        case '+':
        case '-': return 1;
        case '*':
        case '/': return 2;
        case '^': return 3;
        default: return 0;
    }
}

// 中缀表达式转后缀表达式
void infixToPostfix(char *infix, char *postfix) {
    ArrayStack stack;
    initStack(&stack);
    int j = 0;
    
    for (int i = 0; infix[i] != '\0'; i++) {
        char c = infix[i];
        
        // 数字直接输出
        if (isdigit(c) || isalpha(c)) {
            postfix[j++] = c;
        }
        // 左括号入栈
        else if (c == '(') {
            push(&stack, c);
        }
        // 右括号:弹出直到遇到左括号
        else if (c == ')') {
            while (!isEmpty(&stack) && peek(&stack) != '(') {
                postfix[j++] = pop(&stack);
            }
            pop(&stack);  // 弹出左括号
        }
        // 运算符:弹出更高或相等优先级的运算符
        else {
            while (!isEmpty(&stack) && 
                   precedence(peek(&stack)) >= precedence(c)) {
                postfix[j++] = pop(&stack);
            }
            push(&stack, c);
        }
    }
    
    // 弹出剩余运算符
    while (!isEmpty(&stack)) {
        postfix[j++] = pop(&stack);
    }
    postfix[j] = '\0';
}

4. 进制转换

graph TB
    subgraph "十进制1348转八进制"
        A["1348 ÷ 8 = 168 余 4"]
        B["168 ÷ 8 = 21 余 0"]
        C["21 ÷ 8 = 2 余 5"]
        D["2 ÷ 8 = 0 余 2"]
        E["栈: [4, 0, 5, 2]"]
        F["逆序输出: 2504"]
    end
    
    A --> B --> C --> D --> E --> F
    
    style F fill:#E8F5E9
C
void decimalToBase(int num, int base) {
    ArrayStack stack;
    initStack(&stack);
    
    // 不断除以base,余数入栈
    while (num > 0) {
        push(&stack, num % base);
        num /= base;
    }
    
    // 弹出所有余数,得到转换结果
    printf("转换结果: ");
    while (!isEmpty(&stack)) {
        int digit = pop(&stack);
        if (digit < 10) {
            printf("%d", digit);
        } else {
            printf("%c", 'A' + digit - 10);  // 16进制字母
        }
    }
    printf("\n");
}

5. 浏览器前进后退

C
typedef struct {
    ArrayStack backStack;      // 后退栈
    ArrayStack forwardStack;   // 前进栈
    char current[256];          // 当前页面
} Browser;

// 访问新页面
void visit(Browser *b, char *url) {
    push(&b->backStack, b->current[0]);  // 当前页面入后退栈
    strcpy(b->current, url);
    
    // 清空前进栈
    while (!isEmpty(&b->forwardStack)) {
        pop(&b->forwardStack);
    }
}

// 后退
void back(Browser *b) {
    if (!isEmpty(&b->backStack)) {
        push(&b->forwardStack, b->current[0]);  // 当前页面入前进栈
        b->current[0] = pop(&b->backStack);     // 从后退栈取页面
    }
}

// 前进
void forward(Browser *b) {
    if (!isEmpty(&b->forwardStack)) {
        push(&b->backStack, b->current[0]);     // 当前页面入后退栈
        b->current[0] = pop(&b->forwardStack);  // 从前进栈取页面
    }
}

6. 深度优先搜索(DFS)

C
// 使用栈实现的DFS(非递归版本)
void dfsStack(Graph *g, int start) {
    ArrayStack stack;
    initStack(&stack);
    bool visited[MAX_V] = {false};
    
    push(&stack, start);
    visited[start] = true;
    
    while (!isEmpty(&stack)) {
        int v = pop(&stack);
        printf("访问 %d\n", v);
        
        // 将未访问的邻居入栈
        for (int i = 0; i < g->n; i++) {
            if (g->adj[v][i] && !visited[i]) {
                push(&stack, i);
                visited[i] = true;
            }
        }
    }
}

C++ STL stack

C++
#include <stack>
#include <iostream>

int main() {
    std::stack<int> s;
    
    // 入栈
    s.push(1);
    s.push(2);
    s.push(3);
    
    // 查看栈顶
    std::cout << "栈顶: " << s.top() << std::endl;  // 3
    
    // 出栈
    s.pop();
    std::cout << "栈顶: " << s.top() << std::endl;  // 2
    
    // 栈大小和判空
    std::cout << "大小: " << s.size() << std::endl;   // 2
    std::cout << "是否为空: " << s.empty() << std::endl;  // 0 (false)
    
    return 0;
}

栈的变体

1. 最小栈(Min Stack)

在O(1)时间内获取栈中最小元素。

C
typedef struct {
    ArrayStack data;  // 主栈
    ArrayStack min;   // 辅助栈,存储每个位置的最小值
} MinStack;

void minStackPush(MinStack *ms, int value) {
    push(&ms->data, value);
    
    // 辅助栈存储当前位置的最小值
    if (isEmpty(&ms->min) || value <= peek(&ms->min)) {
        push(&ms->min, value);
    } else {
        push(&ms->min, peek(&ms->min));
    }
}

int getMin(MinStack *ms) {
    return peek(&ms->min);
}

2. 双端栈

使用一个数组实现两个栈。

Text Only
1
2
3
4
5
6
7
8
数组两端分别作为两个栈的栈底:

栈1 grows →        ← grows 栈2
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 2 │   │   │   │   │ 6 │ 5 │
└───┴───┴───┴───┴───┴───┴───┴───┘
      ↑                 ↑
    top1              top2

常见问题与陷阱

1. 栈溢出

递归深度过大或栈空间不足:

C
1
2
3
4
// 危险:可能导致栈溢出
void infiniteRecursion() {
    infiniteRecursion();  // 无限递归
}

2. 栈下溢

对空栈执行pop操作:

C
1
2
3
ArrayStack s;
initStack(&s);
int x = pop(&s);  // 栈下溢!

空间复杂度

  • 数组实现:O(n),其中n为栈的最大容量
  • 链表实现:O(n),其中n为栈中元素个数

时间复杂度汇总

操作 时间复杂度 说明
push O(1) 直接操作栈顶
pop O(1) 直接操作栈顶
peek O(1) 直接访问栈顶
isEmpty O(1) 检查top指针
size O(1) 返回计数器或top+1

参考资料