数据结构 --- 栈

一、栈的基本概念

        栈(Stack)是一种只允许在固定一端进行数据插入和删除操作的线性数据结构。这一端称为栈顶(Top),另一端称为栈底(Bottom)。数据的插入操作称为入栈(Push),数据的删除操作称为出栈(Pop)。

1.1 栈的核心特性

  • 先进后出(FILO - First In Last Out):最先入栈的元素最后出栈。
  • 后进先出(LIFO - Last In First Out):最后入栈的元素最先出栈。
  • 操作受限:只能在栈顶进行插入和删除,不能随机访问中间元素。

1.2 栈的抽象数据类型(ADT)定义

// 栈的抽象数据类型定义
ADT Stack {
    数据对象:具有相同类型的数据元素集合
    数据关系:线性关系,遵循LIFO原则
    
    基本操作:
    InitStack(&S):初始化栈
    DestroyStack(&S):销毁栈
    ClearStack(&S):清空栈
    StackEmpty(S):判断栈是否为空
    StackFull(S):判断栈是否已满(顺序栈)
    GetTop(S, &e):获取栈顶元素
    Push(&S, e):元素e入栈
    Pop(&S, &e):栈顶元素出栈
    StackLength(S):返回栈中元素个数
    StackTraverse(S, visit()):遍历栈中元素
}

二、栈的存储结构

2.1 顺序栈

使用数组(连续内存空间)实现的栈称为顺序栈。

2.1.1 顺序栈的结构定义
#define MAXSIZE 100  // 栈的最大容量

typedef struct {
    int data[MAXSIZE];  // 存储栈元素的数组
    int top;            // 栈顶指针
} SeqStack;
2.1.2 栈顶指针的四种组合方式
类型栈顶指针指向空栈条件满栈条件特点
满增栈最后一个元素top = -1top = MAXSIZE-1栈顶向高地址增长,位置有元素
空增栈下一个可用位置top = 0top = MAXSIZE栈顶向高地址增长,位置无元素
满减栈最后一个元素top = MAXSIZEtop = 0栈顶向低地址减少,位置有元素
空减栈下一个可用位置top = MAXSIZE-1top = -1栈顶向低地址减少,位置无元素

2.1.3 顺序栈基本操作实现(以满增栈为例)
// 初始化栈
void InitStack(SeqStack *S) {
    S->top = -1;  // 空栈
}

// 判断栈空
int StackEmpty(SeqStack *S) {
    return S->top == -1;
}

// 判断栈满
int StackFull(SeqStack *S) {
    return S->top == MAXSIZE - 1;
}

// 入栈
int Push(SeqStack *S, int value) {
    if (StackFull(S)) {
        printf("栈已满,无法入栈\n");
        return -1;
    }
    S->top++;
    S->data[S->top] = value;
    return 0;
}

// 出栈
int Pop(SeqStack *S, int *value) {
    if (StackEmpty(S)) {
        printf("栈为空,无法出栈\n");
        return -1;
    }
    *value = S->data[S->top];
    S->top--;
    return 0;
}

// 获取栈顶元素
int GetTop(SeqStack *S, int *value) {
    if (StackEmpty(S)) {
        printf("栈为空\n");
        return -1;
    }
    *value = S->data[S->top];
    return 0;
}

2.2 链式栈

使用链表实现的栈称为链式栈,不需要预先分配固定大小的空间。

2.2.1 链式栈的结点定义
typedef struct StackNode {
    int data;                // 数据域
    struct StackNode *next;  // 指针域,指向下一个结点
} StackNode;

typedef struct {
    StackNode *top;  // 栈顶指针
    int count;       // 栈中元素个数
} LinkStack;
2.2.2 链式栈基本操作实现
// 初始化链式栈
void InitLinkStack(LinkStack *S) {
    S->top = NULL;
    S->count = 0;
}

// 判断栈空
int LinkStackEmpty(LinkStack *S) {
    return S->top == NULL;
}

// 入栈(头插法)
int LinkPush(LinkStack *S, int value) {
    StackNode *newNode = (StackNode *)malloc(sizeof(StackNode));
    if (newNode == NULL) {
        printf("内存分配失败\n");
        return -1;
    }
    
    newNode->data = value;
    newNode->next = S->top;  // 新结点指向原栈顶
    S->top = newNode;        // 更新栈顶指针
    S->count++;
    return 0;
}

// 出栈
int LinkPop(LinkStack *S, int *value) {
    if (LinkStackEmpty(S)) {
        printf("栈为空,无法出栈\n");
        return -1;
    }
    
    StackNode *temp = S->top;
    *value = temp->data;
    S->top = temp->next;     // 栈顶指针下移
    free(temp);              // 释放原栈顶结点
    S->count--;
    return 0;
}

// 获取栈顶元素
int LinkGetTop(LinkStack *S, int *value) {
    if (LinkStackEmpty(S)) {
        printf("栈为空\n");
        return -1;
    }
    *value = S->top->data;
    return 0;
}

// 销毁链式栈
void DestroyLinkStack(LinkStack *S) {
    int value;
    while (!LinkStackEmpty(S)) {
        LinkPop(S, &value);  // 逐个出栈释放
    }
}

三、栈的应用场景

3.1 函数调用栈(Call Stack)

程序执行时,函数调用、返回地址、局部变量等都保存在栈中。

// 示例:递归函数的栈帧
int factorial(int n) {
    if (n <= 1) return 1;          // 递归基
    return n * factorial(n - 1);    // 递归调用
}

// 调用过程栈帧变化:
// factorial(3) → factorial(2) → factorial(1)
// 每层调用都会在栈中创建新的栈帧

3.2 表达式求值

使用两个栈:操作数栈和运算符栈。

  • 中缀表达式转后缀表达式:运算符优先级处理
  • 后缀表达式求值:遇到操作数入栈,遇到运算符弹出操作数计算
// 示例:中缀表达式 "3 + 4 * 2 / (1 - 5)" 的求值过程
// 1. 转换为后缀表达式:"3 4 2 * 1 5 - / +"
// 2. 后缀表达式求值步骤:
//    - 3 入栈
//    - 4 入栈
//    - 2 入栈
//    - 遇到 *:弹出 2 和 4,计算 4*2=8,8入栈
//    - 1 入栈
//    - 5 入栈
//    - 遇到 -:弹出 5 和 1,计算 1-5=-4,-4入栈
//    - 遇到 /:弹出 -4 和 8,计算 8/(-4)=-2,-2入栈
//    - 遇到 +:弹出 -2 和 3,计算 3+(-2)=1,1入栈
// 最终结果:1

3.3 括号匹配检查

// 检查括号是否匹配的算法
int checkParentheses(char *str) {
    SeqStack S;
    InitStack(&S);
    
    for (int i = 0; str[i] != '\0'; i++) {
        if (str[i] == '(' || str[i] == '[' || str[i] == '{') {
            Push(&S, str[i]);  // 左括号入栈
        } else if (str[i] == ')' || str[i] == ']' || str[i] == '}') {
            if (StackEmpty(&S)) {
                return 0;  // 栈空但遇到右括号,不匹配
            }
            
            char topChar;
            Pop(&S, &topChar);
            
            // 检查括号是否配对
            if ((str[i] == ')' && topChar != '(') ||
                (str[i] == ']' && topChar != '[') ||
                (str[i] == '}' && topChar != '{')) {
                return 0;
            }
        }
    }
    
    // 遍历结束后栈应为空
    return StackEmpty(&S);
}

3.4 浏览器的前进后退功能

使用两个栈实现:

  • 后退栈:保存访问过的页面
  • 前进栈:保存后退操作中弹出的页面

3.5 撤销(Undo)和重做(Redo)功能

文本编辑器、绘图软件等常用栈实现操作历史记录。

四、栈的变体与扩展

4.1 双栈共享空间

两个栈共享同一数组空间,分别从两端向中间增长。

#define MAXSIZE 100

typedef struct {
    int data[MAXSIZE];
    int top1;  // 栈1的栈顶指针,从0开始增长
    int top2;  // 栈2的栈顶指针,从MAXSIZE-1开始减少
} DoubleStack;

// 初始化双栈
void InitDoubleStack(DoubleStack *S) {
    S->top1 = -1;
    S->top2 = MAXSIZE;
}

// 判断栈1空
int Stack1Empty(DoubleStack *S) {
    return S->top1 == -1;
}

// 判断栈2空
int Stack2Empty(DoubleStack *S) {
    return S->top2 == MAXSIZE;
}

// 判断栈满(两个栈顶相遇)
int DoubleStackFull(DoubleStack *S) {
    return S->top1 + 1 == S->top2;
}

// 向栈1入栈
int Push1(DoubleStack *S, int value) {
    if (DoubleStackFull(S)) {
        printf("双栈已满\n");
        return -1;
    }
    S->top1++;
    S->data[S->top1] = value;
    return 0;
}

// 向栈2入栈
int Push2(DoubleStack *S, int value) {
    if (DoubleStackFull(S)) {
        printf("双栈已满\n");
        return -1;
    }
    S->top2--;
    S->data[S->top2] = value;
    return 0;
}

4.2 最小栈(Min Stack)

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

typedef struct {
    SeqStack dataStack;  // 数据栈
    SeqStack minStack;   // 最小栈,栈顶始终是当前最小值
} MinStack;

// 初始化最小栈
void InitMinStack(MinStack *S) {
    InitStack(&S->dataStack);
    InitStack(&S->minStack);
}

// 入栈
void MinPush(MinStack *S, int value) {
    Push(&S->dataStack, value);
    
    // 如果最小栈为空或新值≤最小栈栈顶,则入最小栈
    if (StackEmpty(&S->minStack) || value <= GetTopValue(&S->minStack)) {
        Push(&S->minStack, value);
    }
}

// 出栈
int MinPop(MinStack *S, int *value) {
    if (StackEmpty(&S->dataStack)) {
        return -1;
    }
    
    Pop(&S->dataStack, value);
    
    // 如果出栈的值等于最小栈栈顶,则最小栈也出栈
    int minTop;
    GetTop(&S->minStack, &minTop);
    if (*value == minTop) {
        int temp;
        Pop(&S->minStack, &temp);
    }
    return 0;
}

// 获取最小值
int GetMin(MinStack *S, int *minValue) {
    if (StackEmpty(&S->minStack)) {
        return -1;
    }
    GetTop(&S->minStack, minValue);
    return 0;
}

4.3 单调栈(Monotonic Stack)

栈中元素保持单调递增或递减的顺序,常用于解决"下一个更大/更小元素"问题。

// 示例:求每个元素的下一个更大元素
void nextGreaterElement(int arr[], int n, int result[]) {
    SeqStack S;
    InitStack(&S);
    
    for (int i = n - 1; i >= 0; i--) {
        // 弹出栈中所有小于等于当前元素的元素
        while (!StackEmpty(&S) && GetTopValue(&S) <= arr[i]) {
            int temp;
            Pop(&S, &temp);
        }
        
        // 如果栈不为空,栈顶就是下一个更大元素
        if (!StackEmpty(&S)) {
            int top;
            GetTop(&S, ⊤);
            result[i] = top;
        } else {
            result[i] = -1;  // 没有更大的元素
        }
        
        // 当前元素入栈
        Push(&S, arr[i]);
    }
}

五、栈的算法题常见考点

5.1 栈的基本操作

  • 实现栈的Push、Pop、GetMin等操作
  • 使用两个栈实现队列
  • 使用队列实现栈

5.2 表达式相关

  • 中缀表达式转后缀表达式
  • 后缀表达式求值
  • 计算器实现(支持加减乘除和括号)

5.3 括号匹配与验证

  • 有效的括号
  • 最长有效括号子串
  • 删除无效的括号

5.4 单调栈应用

  • 柱状图中最大的矩形
  • 接雨水问题
  • 每日温度(下一个更高温度)
  • 下一个更大元素I/II

5.5 递归与栈

  • 二叉树的前序、中序、后序遍历(迭代实现)
  • 二叉树的层序遍历
  • 图的深度优先搜索(DFS)

六、栈的性能分析与优化

6.1 时间复杂度分析

操作顺序栈链式栈
入栈(Push)O(1)O(1)
出栈(Pop)O(1)O(1)
获取栈顶(Top)O(1)O(1)
判断栈空O(1)O(1)
遍历栈O

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值