考研数据结构之栈(Stack)详解(包含真题及解析)

考研数据结构之栈(Stack)详解(包含真题及解析)

一、栈的基本概念

栈(Stack) 是一种后进先出(LIFO, Last In First Out) 的线性数据结构,仅允许在栈顶(Top) 进行插入(入栈,Push)和删除(出栈,Pop)操作。
核心特性

  • 插入和删除操作的时间复杂度为 (O(1))
  • 常见应用:函数调用栈、表达式求值、括号匹配、递归实现等

二、栈的顺序存储结构(顺序栈)

顺序栈通过数组实现,需预先分配固定大小的连续内存空间,并用一个指针 top 标记栈顶位置。

1. 结构定义

#define MAX_SIZE 100  // 栈的最大容量

typedef struct {
    int data[MAX_SIZE];
    int top;          // 栈顶指针(通常初始化为-1)
} SeqStack;

2. 基本操作实现

初始化栈
void InitStack(SeqStack *S) {
    S->top = -1;  // 空栈时top=-1
}
入栈(Push)
int Push(SeqStack *S, int value) {
    if (S->top >= MAX_SIZE - 1) {  // 栈满
        return 0;  // 入栈失败
    }
    S->data[++S->top] = value;  // top先+1,再赋值
    return 1;
}
出栈(Pop)
int Pop(SeqStack *S, int *value) {
    if (S->top == -1) {  // 栈空
        return 0;  // 出栈失败
    }
    *value = S->data[S->top--];  // 先取数据,再top-1
    return 1;
}
获取栈顶元素(Peek)
int GetTop(SeqStack S) {
    if (S.top == -1) {
        printf("栈空!");
        return -1;
    }
    return S.data[S.top];
}

优缺点

  • ✅ 优点:内存连续,访问速度快
  • ❌ 缺点:容量固定,可能溢出

三、栈的链式存储结构(链栈)

链栈通过链表实现,动态分配内存,栈顶为链表的头结点。

1. 结构定义

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

typedef struct {
    StackNode *top;  // 栈顶指针
} LinkStack;

2. 基本操作实现

初始化栈
void InitLinkStack(LinkStack *S) {
    S->top = NULL;  // 空栈
}
入栈(Push)
void LinkPush(LinkStack *S, int value) {
    StackNode *newNode = (StackNode*)malloc(sizeof(StackNode));
    newNode->data = value;
    newNode->next = S->top;  // 新结点指向原栈顶
    S->top = newNode;        // 更新栈顶
}
出栈(Pop)
int LinkPop(LinkStack *S, int *value) {
    if (S->top == NULL) {
        return 0;  // 栈空
    }
    StackNode *temp = S->top;
    *value = temp->data;
    S->top = S->top->next;  // 栈顶下移
    free(temp);             // 释放原栈顶
    return 1;
}

优缺点

  • ✅ 优点:动态扩容,无溢出风险
  • ❌ 缺点:内存碎片化,访问速度稍慢

四、顺序栈 vs 链栈

特性顺序栈链栈
内存分配静态(固定大小)动态(按需分配)
插入/删除时间复杂度(O(1))(O(1))
空间利用率可能浪费灵活高效
适用场景数据规模已知数据规模动态变化

五、考研高频考点

  1. 栈的应用:实现括号匹配、中缀转后缀表达式
  2. 算法设计:双栈模拟队列、最小栈问题
  3. 代码手写:顺序栈的入栈/出栈操作、链栈的创建与销毁

考研数据结构之栈(Stack)详解:顺序存储与链式存储

六、考研真题及解析

真题示例1:括号匹配(应用题)

题目:给定一个只包含括号的字符串(如 “()[]{}”),判断其是否有效。要求使用栈实现,并分析时间复杂度。
答案

int isValid(char* s) {
    LinkStack stack;
    InitLinkStack(&stack);
    for (int i = 0; s[i] != '\0'; i++) {
        if (s[i] == '(' || s[i] == '[' || s[i] == '{') {
            LinkPush(&stack, s[i]);
        } else {
            if (stack.top == NULL) return 0;
            char top_char;
            LinkPop(&stack, &top_char);
            if ((s[i] == ')' && top_char != '(') ||
                (s[i] == ']' && top_char != '[') ||
                (s[i] == '}' && top_char != '{')) {
                return 0;
            }
        }
    }
    return stack.top == NULL; // 栈空说明全部匹配
}

解析

  1. 遍历字符串,遇到左括号则入栈
  2. 遇到右括号时,若栈空或不匹配则返回错误
  3. 最终栈空表示完全匹配
    时间复杂度:(O(n)),每个元素仅入栈、出栈一次

真题示例2:中缀转后缀表达式(应用题)

题目:将中缀表达式 “a + b * c - (d + e)” 转换为后缀表达式,写出转换过程。
答案
转换结果:a b c * + d e + -
步骤解析

当前字符操作栈状态(底→顶)输出
a直接输出a
+栈空,入栈+a
b直接输出+a b
*优先级高于栈顶’+',入栈+ *a b
c直接输出+ *a b c
-弹出栈顶’*‘和’+‘,’-'入栈-a b c * +
(直接入栈- (a b c * +
d直接输出- (a b c * + d
+栈顶为’(', 入栈- ( +a b c * + d
e直接输出- ( +a b c * + d e
)弹出栈内运算符直到’(’-a b c * + d e +
结束弹出栈中剩余运算符a b c * + d e + -

真题示例3:双栈模拟队列(算法设计)

题目:用两个栈实现队列的入队(enqueue)和出队(dequeue)操作,说明时间复杂度。
解答

typedef struct {
    SeqStack s1; // 主栈(用于入队)
    SeqStack s2; // 辅助栈(用于出队)
} MyQueue;

void enqueue(MyQueue *q, int x) {
    Push(&q->s1, x); // 直接压入s1
}

int dequeue(MyQueue *q) {
    if (q->s2.top == -1) { 
        // 将s1元素全部倒入s2
        while (q->s1.top != -1) {
            int val;
            Pop(&q->s1, &val);
            Push(&q->s2, val);
        }
    }
    int res;
    Pop(&q->s2, &res);
    return res;
}

时间复杂度分析

  • 入队:(O(1))
  • 出队:均摊 (O(1))(每个元素最多经历两次栈操作)

真题示例4:最小栈(编程题)

题目:设计一个支持常数时间获取最小值的栈,要求所有操作时间复杂度为 (O(1))。
解答

typedef struct {
    SeqStack data_stack; // 主栈
    SeqStack min_stack;  // 辅助栈(存储历史最小值)
} MinStack;

void minStackPush(MinStack *obj, int val) {
    Push(&obj->data_stack, val);
    if (obj->min_stack.top == -1 || val <= GetTop(obj->min_stack)) {
        Push(&obj->min_stack, val);
    }
}

void minStackPop(MinStack *obj) {
    int val = GetTop(obj->data_stack);
    Pop(&obj->data_stack, &val);
    if (val == GetTop(obj->min_stack)) {
        int tmp;
        Pop(&obj->min_stack, &tmp);
    }
}

int minStackGetMin(MinStack *obj) {
    return GetTop(obj->min_stack);
}

真题示例5:出栈序列判断(选择题)

题目:若入栈序列为1,2,3,4,5,则下列哪个不可能是合法的出栈序列?
A. 3,2,5,4,1
B. 5,4,3,2,1
C. 4,5,3,2,1
D. 2,1,3,5,4

答案:C(4,5,3,2,1)
解析

  • 选项C中,4出栈后栈内剩余元素为1,2,3。此时若要5出栈,需先入栈5,但5尚未入栈,因此矛盾。

七、总结

通过真题训练可以加深对栈的特性、应用场景及代码实现的理解。重点掌握:

  1. 栈在括号匹配、表达式转换中的核心逻辑
  2. 双栈配合解决复杂问题的思路(如队列模拟、最小栈)
  3. 出栈序列合法性的快速判断方法(模拟栈操作)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老北京儿码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值