1. 栈的基本概念与特性
栈(Stack)是一种操作受限的线性表数据结构,它遵循后进先出(LIFO, Last In First Out)的原则。想象一下餐厅里叠放的餐盘——你总是从最上面取用干净的盘子,用完后也总是放回最上层。这种"后来先服务"的特性正是栈的核心特征。
栈有两个基本操作:
- 压栈(Push):将元素放入栈顶
- 弹栈(Pop):移除并返回栈顶元素
此外通常还会提供:
- 查看栈顶(Peek/Top):获取但不移除栈顶元素
- 判空(isEmpty):检查栈是否为空
- 获取大小(Size):返回栈中元素数量
栈在计算机科学中应用极为广泛:
- 函数调用时的调用栈(Call Stack)
- 表达式求值和语法解析
- 浏览器的前进后退功能
- 撤销(Undo)操作的实现
- 深度优先搜索(DFS)算法
注意:栈的插入和删除操作时间复杂度都是O(1),这使得它成为处理需要"最近相关性"问题的理想选择。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 栈的两种实现方式对比
2.1 基于数组的实现
数组实现栈是最直观的方式,我们需要:
- 一个固定大小的数组存储元素
- 一个整型变量top记录栈顶位置
c复制#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int top;
} ArrayStack;
初始化时top设为-1表示空栈。压栈操作将top加1后存入元素,弹栈则返回data[top]后将top减1。
优点:
- 实现简单直接
- 访问元素速度快(连续内存)
- 没有额外内存开销
缺点:
- 容量固定,可能栈溢出
- 需要预先分配足够大空间
- 空间利用率可能不高
2.2 基于链表的实现
链表实现栈更加灵活,每个节点存储数据和指向下一个节点的指针:
c复制typedef struct StackNode {
int data;
struct StackNode* next;
} StackNode;
typedef struct {
StackNode* top;
int size;
} LinkedStack;
压栈操作创建新节点并插入链表头部,弹栈则移除并返回头节点数据。
优点:
- 动态扩容,没有大小限制
- 内存按需分配,利用率高
- 适合元素数量变化大的场景
缺点:
- 每个元素有额外指针开销
- 访问速度略慢于数组
- 实现稍复杂
实际选择:小型固定规模栈用数组,大型或规模不确定的栈用链表。C++ STL中的stack默认基于deque实现,兼具两者优点。
3. C语言完整栈实现代码
3.1 数组版本实现
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;
}
// 判断栈是否为空
bool isEmpty(ArrayStack *s) {
return s->top == -1;
}
// 判断栈是否已满
bool isFull(ArrayStack *s) {
return s->top == MAX_SIZE - 1;
}
// 压栈
bool push(ArrayStack *s, int value) {
if (isFull(s)) {
printf("Stack overflow!\n");
return false;
}
s->data[++s->top] = value;
return true;
}
// 弹栈
int pop(ArrayStack *s) {
if (isEmpty(s)) {
printf("Stack underflow!\n");
exit(EXIT_FAILURE);
}
return s->data[s->top--];
}
// 查看栈顶元素
int peek(ArrayStack *s) {
if (isEmpty(s)) {
printf("Stack is empty!\n");
exit(EXIT_FAILURE);
}
return s->data[s->top];
}
// 测试代码
int main() {
ArrayStack s;
initStack(&s);
push(&s, 10);
push(&s, 20);
push(&s, 30);
printf("Top element: %d\n", peek(&s));
printf("Popped element: %d\n", pop(&s));
printf("Popped element: %d\n", pop(&s));
return 0;
}
3.2 链表版本实现
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 initStack(LinkedStack *s) {
s->top = NULL;
s->size = 0;
}
// 判断栈是否为空
bool isEmpty(LinkedStack *s) {
return s->top == NULL;
}
// 压栈
void push(LinkedStack *s, int value) {
StackNode* newNode = (StackNode*)malloc(sizeof(StackNode));
if (!newNode) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
newNode->data = value;
newNode->next = s->top;
s->top = newNode;
s->size++;
}
// 弹栈
int pop(LinkedStack *s) {
if (isEmpty(s)) {
printf("Stack underflow!\n");
exit(EXIT_FAILURE);
}
StackNode* temp = s->top;
int popped = temp->data;
s->top = temp->next;
free(temp);
s->size--;
return popped;
}
// 查看栈顶元素
int peek(LinkedStack *s) {
if (isEmpty(s)) {
printf("Stack is empty!\n");
exit(EXIT_FAILURE);
}
return s->top->data;
}
// 获取栈大小
int size(LinkedStack *s) {
return s->size;
}
// 测试代码
int main() {
LinkedStack s;
initStack(&s);
push(&s, 100);
push(&s, 200);
push(&s, 300);
printf("Stack size: %d\n", size(&s));
printf("Top element: %d\n", peek(&s));
printf("Popped element: %d\n", pop(&s));
printf("Popped element: %d\n", pop(&s));
return 0;
}
4. 栈的常见应用场景实现
4.1 括号匹配检查
栈非常适合解决需要检查嵌套结构的问题,如检查表达式中的括号是否匹配:
c复制#include <stdbool.h>
#include <string.h>
bool isBalanced(const char* expr) {
ArrayStack s;
initStack(&s);
for (int i = 0; expr[i] != '\0'; i++) {
if (expr[i] == '(' || expr[i] == '[' || expr[i] == '{') {
push(&s, expr[i]);
} else if (expr[i] == ')' || expr[i] == ']' || expr[i] == '}') {
if (isEmpty(&s)) return false;
char top = pop(&s);
if ((expr[i] == ')' && top != '(') ||
(expr[i] == ']' && top != '[') ||
(expr[i] == '}' && top != '{')) {
return false;
}
}
}
return isEmpty(&s);
}
4.2 表达式求值(中缀转后缀)
栈可用于将中缀表达式转换为后缀表达式(逆波兰表示法),更易于计算:
c复制// 运算符优先级
int precedence(char op) {
switch(op) {
case '+':
case '-': return 1;
case '*':
case '/': return 2;
case '^': return 3;
default: return 0;
}
}
void infixToPostfix(const char* infix, char* postfix) {
ArrayStack s;
initStack(&s);
int j = 0;
for (int i = 0; infix[i] != '\0'; i++) {
char c = infix[i];
if (isalnum(c)) {
postfix[j++] = c;
} else if (c == '(') {
push(&s, c);
} else if (c == ')') {
while (!isEmpty(&s) && peek(&s) != '(') {
postfix[j++] = pop(&s);
}
pop(&s); // 弹出'('
} else { // 运算符
while (!isEmpty(&s) && precedence(peek(&s)) >= precedence(c)) {
postfix[j++] = pop(&s);
}
push(&s, c);
}
}
while (!isEmpty(&s)) {
postfix[j++] = pop(&s);
}
postfix[j] = '\0';
}
4.3 函数调用栈模拟
计算机使用调用栈管理函数调用和返回,我们可以模拟这个过程:
c复制typedef struct {
char functionName[50];
int lineNumber;
int returnAddress;
} StackFrame;
typedef struct {
StackFrame frames[MAX_SIZE];
int top;
} CallStack;
void callFunction(CallStack *cs, const char* name, int line) {
if (cs->top == MAX_SIZE - 1) {
printf("Stack overflow - too many nested calls!\n");
return;
}
strcpy(cs->frames[++cs->top].functionName, name);
cs->frames[cs->top].lineNumber = line;
printf("Entering %s at line %d\n", name, line);
}
void returnFunction(CallStack *cs) {
if (cs->top == -1) {
printf("Stack underflow - no function to return from!\n");
return;
}
printf("Exiting %s\n", cs->frames[cs->top].functionName);
cs->top--;
}
5. 栈的高级应用与优化
5.1 多栈共享同一数组空间
当需要多个栈而内存有限时,可以让两个栈从数组两端向中间生长:
c复制typedef struct {
int data[MAX_SIZE];
int top1; // 栈1的栈顶,从0开始增长
int top2; // 栈2的栈顶,从MAX_SIZE-1开始减少
} DoubleStack;
void initDoubleStack(DoubleStack *ds) {
ds->top1 = -1;
ds->top2 = MAX_SIZE;
}
bool push1(DoubleStack *ds, int value) {
if (ds->top1 + 1 >= ds->top2) {
printf("Stack overflow!\n");
return false;
}
ds->data[++ds->top1] = value;
return true;
}
bool push2(DoubleStack *ds, int value) {
if (ds->top2 - 1 <= ds->top1) {
printf("Stack overflow!\n");
return false;
}
ds->data[--ds->top2] = value;
return true;
}
5.2 最小栈设计
设计一个能在O(1)时间内获取栈中最小元素的栈:
c复制typedef struct {
ArrayStack mainStack;
ArrayStack minStack;
} MinStack;
void initMinStack(MinStack *ms) {
initStack(&ms->mainStack);
initStack(&ms->minStack);
}
void pushMin(MinStack *ms, int value) {
push(&ms->mainStack, value);
if (isEmpty(&ms->minStack) || value <= peek(&ms->minStack)) {
push(&ms->minStack, value);
}
}
int popMin(MinStack *ms) {
int value = pop(&ms->mainStack);
if (value == peek(&ms->minStack)) {
pop(&ms->minStack);
}
return value;
}
int getMin(MinStack *ms) {
return peek(&ms->minStack);
}
5.3 栈的线程安全实现
在多线程环境中使用栈需要同步机制,以下是使用互斥锁的线程安全栈:
c复制#include <pthread.h>
typedef struct {
int data[MAX_SIZE];
int top;
pthread_mutex_t lock;
} ThreadSafeStack;
void initThreadSafeStack(ThreadSafeStack *ts) {
ts->top = -1;
pthread_mutex_init(&ts->lock, NULL);
}
bool threadSafePush(ThreadSafeStack *ts, int value) {
pthread_mutex_lock(&ts->lock);
bool result;
if (ts->top == MAX_SIZE - 1) {
printf("Stack overflow!\n");
result = false;
} else {
ts->data[++ts->top] = value;
result = true;
}
pthread_mutex_unlock(&ts->lock);
return result;
}
int threadSafePop(ThreadSafeStack *ts) {
pthread_mutex_lock(&ts->lock);
if (ts->top == -1) {
pthread_mutex_unlock(&ts->lock);
printf("Stack underflow!\n");
exit(EXIT_FAILURE);
}
int value = ts->data[ts->top--];
pthread_mutex_unlock(&ts->lock);
return value;
}
6. 栈的常见问题与调试技巧
6.1 栈溢出与栈下溢
栈溢出(Stack Overflow)发生在尝试向已满的栈压入元素时。预防措施:
- 压栈前总是检查栈是否已满
- 使用动态扩容的链表实现
- 设置合理的栈大小
栈下溢(Stack Underflow)发生在尝试从空栈弹出元素时。预防措施:
- 弹栈前检查栈是否为空
- 考虑返回错误代码而非直接退出
- 在调试版本中添加断言检查
6.2 内存泄漏问题
链表实现的栈可能出现内存泄漏:
- 每次弹栈后忘记释放节点内存
- 清空栈时未释放所有节点
- 栈销毁时残留节点
解决方法:
c复制void clearStack(LinkedStack *s) {
while (!isEmpty(s)) {
StackNode* temp = s->top;
s->top = temp->next;
free(temp);
}
s->size = 0;
}
6.3 调试栈的技巧
- 打印栈内容函数:
c复制void printStack(ArrayStack *s) {
printf("Stack (top to bottom): ");
for (int i = s->top; i >= 0; i--) {
printf("%d ", s->data[i]);
}
printf("\n");
}
- 使用断言检查不变式:
c复制#include <assert.h>
void checkStackInvariants(LinkedStack *s) {
int count = 0;
StackNode* current = s->top;
while (current != NULL) {
count++;
current = current->next;
}
assert(count == s->size);
}
- 记录操作日志:
c复制typedef struct {
char operation[20];
int value;
int stackSize;
} StackLogEntry;
typedef struct {
ArrayStack stack;
StackLogEntry log[1000];
int logCount;
} LoggedStack;
void loggedPush(LoggedStack *ls, int value) {
push(&ls->stack, value);
strcpy(ls->log[ls->logCount].operation, "push");
ls->log[ls->logCount].value = value;
ls->log[ls->logCount].stackSize = ls->stack.top + 1;
ls->logCount++;
}
7. 不同语言中的栈实现对比
7.1 C++ STL中的stack
C++标准库提供了stack容器适配器:
cpp复制#include <stack>
#include <iostream>
int main() {
std::stack<int> s;
s.push(10);
s.push(20);
s.push(30);
std::cout << "Top: " << s.top() << std::endl;
s.pop();
std::cout << "Size: " << s.size() << std::endl;
return 0;
}
特点:
- 默认基于deque实现
- 也可指定底层容器(如vector、list)
- 提供push/pop/top/size/empty等接口
7.2 Java中的Stack类
Java提供了java.util.Stack类:
java复制import java.util.Stack;
public class Main {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(10);
stack.push(20);
System.out.println("Top: " + stack.peek());
System.out.println("Popped: " + stack.pop());
}
}
注意:
- Stack是Vector的子类,线程安全但性能有损耗
- 更推荐使用Deque接口的实现:
java复制Deque<Integer> stack = new ArrayDeque<>();
7.3 Python中的列表作为栈
Python中直接用列表实现栈操作:
python复制stack = []
stack.append(10) # push
stack.append(20)
top = stack[-1] # peek
popped = stack.pop() # pop
特点:
- 列表天然支持栈操作
- 动态扩容,没有大小限制
- 对于高性能需求可使用collections.deque
8. 栈的扩展数据结构
8.1 双栈实现队列
用两个栈模拟队列的先进先出特性:
c复制typedef struct {
ArrayStack inStack;
ArrayStack outStack;
} StackQueue;
void enqueue(StackQueue *q, int value) {
push(&q->inStack, value);
}
int dequeue(StackQueue *q) {
if (isEmpty(&q->outStack)) {
while (!isEmpty(&q->inStack)) {
push(&q->outStack, pop(&q->inStack));
}
}
if (isEmpty(&q->outStack)) {
printf("Queue is empty!\n");
exit(EXIT_FAILURE);
}
return pop(&q->outStack);
}
8.2 栈与递归的关系
递归函数本质上使用了系统调用栈,任何递归都可以用栈+循环改写。例如递归的阶乘函数:
c复制// 递归版本
int factorialRec(int n) {
if (n <= 1) return 1;
return n * factorialRec(n - 1);
}
// 栈版本
int factorialStack(int n) {
ArrayStack s;
initStack(&s);
while (n > 1) {
push(&s, n--);
}
int result = 1;
while (!isEmpty(&s)) {
result *= pop(&s);
}
return result;
}
8.3 单调栈及其应用
单调栈是指栈内元素保持单调递增或递减的顺序,常用于解决"下一个更大元素"类问题:
c复制void nextGreaterElement(const int arr[], int n, int result[]) {
ArrayStack s;
initStack(&s);
for (int i = n - 1; i >= 0; i--) {
while (!isEmpty(&s) && peek(&s) <= arr[i]) {
pop(&s);
}
result[i] = isEmpty(&s) ? -1 : peek(&s);
push(&s, arr[i]);
}
}
