数据结构与算法:栈的基本概念,顺序栈与链式栈的详细实现

一、栈的基本概念整理

1. 栈的定义与特性

栈(Stack)是一种线性数据结构 ,遵循后进先出(LIFO)先进后出(FILO) 的原则。

text

复制代码
入栈顺序:A → B → C → D
出栈顺序:D → C → B → A

2. 栈、队列和表的对比

特性 队列 表(链表/数组)
插入位置 只能在栈顶 只能在队尾 任意位置
删除位置 只能在栈顶 只能在队首 任意位置
访问特性 只能访问栈顶 只能访问队首队尾 任意位置访问
操作原则 后进先出(LIFO) 先进先出(FIFO) 任意顺序
典型应用 函数调用、括号匹配 任务调度、消息队列 通用数据存储

二、顺序栈(空增栈类型)的详细实现

1. 空增栈的特点

  • 空栈:栈顶指针指向下一个入栈位置

  • 增栈:栈向高地址方向增长

2. 数据结构定义

c

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

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

typedef struct {
    int data[MAX_SIZE];  // 存储栈元素的数组
    int top;             // 栈顶指针,指向下一个入栈位置
} SeqStack;

3. 栈的四种状态图示

text

复制代码
初始状态(栈空):
    top=0
    ┌───┬───┬───┬───┬───┐
    │   │   │   │   │   │
    └───┴───┴───┴───┴───┘
    0   1   2   3   4   ← 数组下标
    ↑
  栈底             栈顶方向→

入栈10后:
    top=1
    ┌───┬───┬───┬───┬───┐
    │10 │   │   │   │   │
    └───┴───┴───┴───┴───┘
    ↑        ↑
  栈底     栈顶方向→

再入栈20、30后:
    top=3
    ┌───┬───┬───┬───┬───┐
    │10 │20 │30 │   │   │
    └───┴───┴───┴───┴───┘
    ↑           ↑
  栈底       栈顶方向→

栈满状态:
    top=MAX_SIZE
    ┌───┬───┬───┬───┬───┐
    │10 │20 │30 │...│99 │
    └───┴───┴───┴───┴───┘
    ↑                    ↑
  栈底                栈顶方向→

4. 完整操作实现

c

复制代码
// 1. 初始化栈
void InitSeqStack(SeqStack *s) {
    s->top = 0;  // 空增栈:top指向下一个入栈位置
    printf("初始化空栈,top = %d\n", s->top);
}

// 2. 判断栈是否为空
bool IsEmptySeqStack(SeqStack *s) {
    return s->top == 0;
}

// 3. 判断栈是否已满
bool IsFullSeqStack(SeqStack *s) {
    return s->top == MAX_SIZE;
}

// 4. 获取栈中元素个数
int GetSizeSeqStack(SeqStack *s) {
    return s->top;
}

// 5. 入栈操作
bool PushSeqStack(SeqStack *s, int value) {
    if (IsFullSeqStack(s)) {
        printf("栈已满,无法入栈元素 %d\n", value);
        return false;
    }
    
    // 关键操作:先赋值,后移动栈顶指针
    s->data[s->top] = value;
    s->top++;
    
    printf("入栈: %d, 栈顶指针: top = %d\n", value, s->top);
    return true;
}

// 6. 出栈操作
bool PopSeqStack(SeqStack *s, int *value) {
    if (IsEmptySeqStack(s)) {
        printf("栈已空,无法出栈\n");
        return false;
    }
    
    // 关键操作:先移动栈顶指针,后取值
    s->top--;
    *value = s->data[s->top];
    
    printf("出栈: %d, 栈顶指针: top = %d\n", *value, s->top);
    return true;
}

// 7. 获取栈顶元素(不出栈)
bool PeekSeqStack(SeqStack *s, int *value) {
    if (IsEmptySeqStack(s)) {
        printf("栈已空,无栈顶元素\n");
        return false;
    }
    
    // 注意:top指向下一个入栈位置,所以栈顶元素在top-1位置
    *value = s->data[s->top - 1];
    return true;
}

// 8. 清空栈
void ClearSeqStack(SeqStack *s) {
    s->top = 0;
    printf("栈已清空,top = %d\n", s->top);
}

// 9. 遍历栈(从栈底到栈顶)
void TraverseSeqStack(SeqStack *s) {
    if (IsEmptySeqStack(s)) {
        printf("栈为空\n");
        return;
    }
    
    printf("栈元素(栈底 → 栈顶): ");
    for (int i = 0; i < s->top; i++) {
        printf("%d ", s->data[i]);
    }
    printf("\n");
}

// 10. 遍历栈(从栈顶到栈底)
void ReverseTraverseSeqStack(SeqStack *s) {
    if (IsEmptySeqStack(s)) {
        printf("栈为空\n");
        return;
    }
    
    printf("栈元素(栈顶 → 栈底): ");
    for (int i = s->top - 1; i >= 0; i--) {
        printf("%d ", s->data[i]);
    }
    printf("\n");
}

5. 使用示例与测试

c

复制代码
// 测试函数
void TestSeqStack() {
    printf("===== 测试顺序栈(空增栈) =====\n\n");
    
    SeqStack stack;
    
    // 1. 初始化栈
    InitSeqStack(&stack);
    
    // 2. 入栈操作测试
    printf("1. 入栈操作:\n");
    for (int i = 1; i <= 5; i++) {
        PushSeqStack(&stack, i * 10);
    }
    TraverseSeqStack(&stack);
    ReverseTraverseSeqStack(&stack);
    printf("当前栈大小: %d\n\n", GetSizeSeqStack(&stack));
    
    // 3. 出栈操作测试
    printf("2. 出栈操作:\n");
    int popped;
    PopSeqStack(&stack, &popped);
    printf("出栈元素: %d\n", popped);
    TraverseSeqStack(&stack);
    printf("当前栈大小: %d\n\n", GetSizeSeqStack(&stack));
    
    // 4. 查看栈顶元素
    printf("3. 查看栈顶元素:\n");
    int topValue;
    if (PeekSeqStack(&stack, &topValue)) {
        printf("栈顶元素: %d\n", topValue);
    }
    TraverseSeqStack(&stack);
    printf("\n");
    
    // 5. 栈空和栈满测试
    printf("4. 栈状态测试:\n");
    printf("栈是否为空: %s\n", IsEmptySeqStack(&stack) ? "是" : "否");
    printf("栈是否已满: %s\n", IsFullSeqStack(&stack) ? "是" : "否");
    printf("\n");
    
    // 6. 清空栈测试
    printf("5. 清空栈:\n");
    ClearSeqStack(&stack);
    printf("栈是否为空: %s\n", IsEmptySeqStack(&stack) ? "是" : "否");
    printf("\n");
    
    // 7. 栈满测试
    printf("6. 栈满测试:\n");
    for (int i = 1; i <= MAX_SIZE + 2; i++) {
        if (!PushSeqStack(&stack, i)) {
            break;  // 栈满时停止
        }
    }
    printf("最终栈大小: %d\n", GetSizeSeqStack(&stack));
}

int main() {
    TestSeqStack();
    return 0;
}

6. 输出结果示例

text

复制代码
===== 测试顺序栈(空增栈) =====

1. 入栈操作:
入栈: 10, 栈顶指针: top = 1
入栈: 20, 栈顶指针: top = 2
入栈: 30, 栈顶指针: top = 3
入栈: 40, 栈顶指针: top = 4
入栈: 50, 栈顶指针: top = 5
栈元素(栈底 → 栈顶): 10 20 30 40 50 
栈元素(栈顶 → 栈底): 50 40 30 20 10 
当前栈大小: 5

2. 出栈操作:
出栈: 50, 栈顶指针: top = 4
出栈元素: 50
栈元素(栈底 → 栈顶): 10 20 30 40 
当前栈大小: 4

3. 查看栈顶元素:
栈顶元素: 40
栈元素(栈底 → 栈顶): 10 20 30 40 

4. 栈状态测试:
栈是否为空: 否
栈是否已满: 否

5. 清空栈:
栈已清空,top = 0
栈是否为空: 是

6. 栈满测试:
入栈: 1, 栈顶指针: top = 1
...(省略中间过程)...
入栈: 100, 栈顶指针: top = 100
栈已满,无法入栈元素 101
最终栈大小: 100

三、链式栈(无头结点,空栈类型)的详细实现

1. 链式栈的特点

  • 无头结点:栈顶指针直接指向栈顶元素

  • 空栈类型:栈顶指针为NULL表示栈空

  • 动态分配:无需预先指定栈的大小

2. 数据结构定义

c

复制代码
// 栈节点定义
typedef struct StackNode {
    int data;               // 数据域
    struct StackNode *next; // 指针域,指向下一个节点
} StackNode;

// 链式栈定义
typedef struct {
    StackNode *top;  // 栈顶指针,指向栈顶元素
    int size;        // 栈中元素个数
} LinkStack;

3. 内存结构图示

text

复制代码
初始状态(栈空):
    top → NULL
    size = 0

入栈10后:
    top → [10|NULL]
    size = 1

再入栈20后:
    top → [20|∙] → [10|NULL]
    size = 2

再入栈30后:
    top → [30|∙] → [20|∙] → [10|NULL]
    size = 3

出栈后(弹出30):
    top → [20|∙] → [10|NULL]
    size = 2

4. 完整操作实现

c

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

// 1. 初始化栈
void InitLinkStack(LinkStack *s) {
    s->top = NULL;
    s->size = 0;
    printf("初始化链式栈,top = NULL, size = 0\n");
}

// 2. 判断栈是否为空
bool IsEmptyLinkStack(LinkStack *s) {
    return s->top == NULL;
}

// 3. 获取栈中元素个数
int GetSizeLinkStack(LinkStack *s) {
    return s->size;
}

// 4. 创建新节点
StackNode* CreateNode(int value) {
    StackNode *node = (StackNode*)malloc(sizeof(StackNode));
    if (node == NULL) {
        printf("内存分配失败\n");
        return NULL;
    }
    node->data = value;
    node->next = NULL;
    return node;
}

// 5. 入栈操作(头插法)
bool PushLinkStack(LinkStack *s, int value) {
    // 创建新节点
    StackNode *newNode = CreateNode(value);
    if (newNode == NULL) {
        return false;
    }
    
    // 新节点的next指向原栈顶
    newNode->next = s->top;
    
    // 更新栈顶指针
    s->top = newNode;
    s->size++;
    
    printf("入栈: %d, 栈大小: %d\n", value, s->size);
    return true;
}

// 6. 出栈操作
bool PopLinkStack(LinkStack *s, int *value) {
    if (IsEmptyLinkStack(s)) {
        printf("栈已空,无法出栈\n");
        return false;
    }
    
    // 保存栈顶节点
    StackNode *temp = s->top;
    
    // 获取栈顶数据
    *value = temp->data;
    
    // 更新栈顶指针
    s->top = temp->next;
    
    // 释放原栈顶节点
    free(temp);
    s->size--;
    
    printf("出栈: %d, 栈大小: %d\n", *value, s->size);
    return true;
}

// 7. 获取栈顶元素(不出栈)
bool PeekLinkStack(LinkStack *s, int *value) {
    if (IsEmptyLinkStack(s)) {
        printf("栈已空,无栈顶元素\n");
        return false;
    }
    
    *value = s->top->data;
    return true;
}

// 8. 清空栈
void ClearLinkStack(LinkStack *s) {
    int value;
    printf("清空栈中元素: ");
    while (!IsEmptyLinkStack(s)) {
        PopLinkStack(s, &value);
        printf("%d ", value);
    }
    printf("\n栈已清空,size = %d\n", s->size);
}

// 9. 销毁栈(完全释放内存)
void DestroyLinkStack(LinkStack *s) {
    ClearLinkStack(s);
    printf("链式栈已销毁\n");
}

// 10. 遍历栈(从栈顶到栈底)
void TraverseLinkStack(LinkStack *s) {
    if (IsEmptyLinkStack(s)) {
        printf("栈为空\n");
        return;
    }
    
    printf("栈元素(栈顶 → 栈底): ");
    StackNode *current = s->top;
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}

// 11. 遍历栈(从栈底到栈顶)- 使用递归或辅助栈
void ReverseTraverseLinkStack(LinkStack *s) {
    if (IsEmptyLinkStack(s)) {
        printf("栈为空\n");
        return;
    }
    
    // 使用辅助栈实现逆序输出
    LinkStack tempStack;
    InitLinkStack(&tempStack);
    
    StackNode *current = s->top;
    while (current != NULL) {
        PushLinkStack(&tempStack, current->data);
        current = current->next;
    }
    
    printf("栈元素(栈底 → 栈顶): ");
    current = tempStack.top;
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}

// 12. 检查栈中是否包含某个元素
bool ContainsLinkStack(LinkStack *s, int value) {
    StackNode *current = s->top;
    while (current != NULL) {
        if (current->data == value) {
            return true;
        }
        current = current->next;
    }
    return false;
}

// 13. 复制栈(深拷贝)
bool CopyLinkStack(LinkStack *src, LinkStack *dest) {
    // 清空目标栈
    ClearLinkStack(dest);
    
    // 如果源栈为空,直接返回
    if (IsEmptyLinkStack(src)) {
        return true;
    }
    
    // 使用辅助栈实现逆序复制
    LinkStack tempStack;
    InitLinkStack(&tempStack);
    
    // 将源栈元素压入临时栈(实现逆序)
    StackNode *current = src->top;
    while (current != NULL) {
        PushLinkStack(&tempStack, current->data);
        current = current->next;
    }
    
    // 将临时栈元素压入目标栈(恢复原顺序)
    current = tempStack.top;
    while (current != NULL) {
        PushLinkStack(dest, current->data);
        current = current->next;
    }
    
    return true;
}

5. 使用示例与测试

c

复制代码
// 测试链式栈的函数
void TestLinkStack() {
    printf("===== 测试链式栈(无头结点) =====\n\n");
    
    LinkStack stack;
    
    // 1. 初始化栈
    InitLinkStack(&stack);
    
    // 2. 入栈操作测试
    printf("1. 入栈操作:\n");
    for (int i = 1; i <= 5; i++) {
        PushLinkStack(&stack, i * 10);
    }
    TraverseLinkStack(&stack);
    ReverseTraverseLinkStack(&stack);
    printf("当前栈大小: %d\n\n", GetSizeLinkStack(&stack));
    
    // 3. 出栈操作测试
    printf("2. 出栈操作:\n");
    int popped;
    PopLinkStack(&stack, &popped);
    printf("出栈元素: %d\n", popped);
    TraverseLinkStack(&stack);
    printf("当前栈大小: %d\n\n", GetSizeLinkStack(&stack));
    
    // 4. 查看栈顶元素
    printf("3. 查看栈顶元素:\n");
    int topValue;
    if (PeekLinkStack(&stack, &topValue)) {
        printf("栈顶元素: %d\n", topValue);
    }
    TraverseLinkStack(&stack);
    printf("\n");
    
    // 5. 栈空测试
    printf("4. 栈状态测试:\n");
    printf("栈是否为空: %s\n", IsEmptyLinkStack(&stack) ? "是" : "否");
    printf("\n");
    
    // 6. 检查元素是否存在
    printf("5. 检查元素是否存在:\n");
    printf("栈中是否包含20: %s\n", ContainsLinkStack(&stack, 20) ? "是" : "否");
    printf("栈中是否包含50: %s\n", ContainsLinkStack(&stack, 50) ? "是" : "否");
    printf("\n");
    
    // 7. 复制栈测试
    printf("6. 复制栈测试:\n");
    LinkStack copiedStack;
    InitLinkStack(&copiedStack);
    CopyLinkStack(&stack, &copiedStack);
    printf("原始栈: ");
    TraverseLinkStack(&stack);
    printf("复制栈: ");
    TraverseLinkStack(&copiedStack);
    printf("\n");
    
    // 8. 清空栈测试
    printf("7. 清空栈:\n");
    ClearLinkStack(&stack);
    printf("栈是否为空: %s\n", IsEmptyLinkStack(&stack) ? "是" : "否");
    printf("\n");
    
    // 9. 销毁栈
    printf("8. 销毁栈:\n");
    DestroyLinkStack(&stack);
}

// 栈的应用:表达式求值(简单示例)
int EvaluateExpression(const char* expr) {
    LinkStack operandStack;   // 操作数栈
    LinkStack operatorStack;  // 操作符栈
    InitLinkStack(&operandStack);
    InitLinkStack(&operatorStack);
    
    // 这里只是演示栈的应用,完整的表达式求值需要更复杂的实现
    printf("表达式: %s\n", expr);
    
    // 简单处理:假设表达式只有数字和+,-运算符,没有括号
    int result = 0;
    int currentNumber = 0;
    
    for (int i = 0; expr[i] != '\0'; i++) {
        if (expr[i] >= '0' && expr[i] <= '9') {
            currentNumber = currentNumber * 10 + (expr[i] - '0');
        } else if (expr[i] == '+' || expr[i] == '-') {
            PushLinkStack(&operandStack, currentNumber);
            currentNumber = 0;
            PushLinkStack(&operatorStack, expr[i]);
        }
    }
    
    // 处理最后一个数字
    if (currentNumber != 0) {
        PushLinkStack(&operandStack, currentNumber);
    }
    
    // 简单计算:这里只是演示,实际需要按照运算符优先级计算
    printf("操作数栈大小: %d\n", GetSizeLinkStack(&operandStack));
    printf("操作符栈大小: %d\n", GetSizeLinkStack(&operatorStack));
    
    DestroyLinkStack(&operandStack);
    DestroyLinkStack(&operatorStack);
    
    return result;
}

int main() {
    TestLinkStack();
    
    printf("\n===== 栈的应用示例 =====\n");
    EvaluateExpression("3+5-2");
    
    return 0;
}

6. 输出结果示例

text

复制代码
===== 测试链式栈(无头结点) =====

1. 入栈操作:
入栈: 10, 栈大小: 1
入栈: 20, 栈大小: 2
入栈: 30, 栈大小: 3
入栈: 40, 栈大小: 4
入栈: 50, 栈大小: 5
栈元素(栈顶 → 栈底): 50 40 30 20 10 
栈元素(栈底 → 栈顶): 10 20 30 40 50 
当前栈大小: 5

2. 出栈操作:
出栈: 50, 栈大小: 4
出栈元素: 50
栈元素(栈顶 → 栈底): 40 30 20 10 
当前栈大小: 4

3. 查看栈顶元素:
栈顶元素: 40
栈元素(栈顶 → 栈底): 40 30 20 10 

4. 栈状态测试:
栈是否为空: 否

5. 检查元素是否存在:
栈中是否包含20: 是
栈中是否包含50: 否

6. 复制栈测试:
清空栈中元素: 10 20 30 40 
栈已清空,size = 0
原始栈: 栈元素(栈顶 → 栈底): 40 30 20 10 
复制栈: 栈元素(栈顶 → 栈底): 40 30 20 10 

7. 清空栈:
清空栈中元素: 10 20 30 40 
栈已清空,size = 0
栈是否为空: 是

8. 销毁栈:
清空栈中元素: 
栈已清空,size = 0
链式栈已销毁

===== 栈的应用示例 =====
表达式: 3+5-2
操作数栈大小: 3
操作符栈大小: 2
清空栈中元素: 2 5 3 
栈已清空,size = 0
链式栈已销毁
清空栈中元素: 45 43 
栈已清空,size = 0
链式栈已销毁

四、顺序栈与链式栈的对比总结

特性 顺序栈(空增栈) 链式栈(无头结点)
存储结构 数组(连续内存) 链表(离散内存)
容量 固定大小 动态扩展
空间效率 存储密度高 有指针开销
时间效率 O(1) 访问 O(1) 插入删除
内存分配 编译时确定 运行时动态分配
栈满判断 top == MAX_SIZE 内存耗尽时失败
栈空判断 top == 0 top == NULL
适用场景 栈大小可预估 栈大小变化大
优点 实现简单,访问快 灵活,无大小限制
缺点 容量固定,可能溢出 有指针开销,内存碎片

五、栈的常见应用场景

  1. 函数调用栈:保存函数调用信息

  2. 括号匹配:检查表达式括号是否匹配

  3. 表达式求值:中缀转后缀,后缀表达式求值

  4. 浏览器的前进后退:使用两个栈实现

  5. 撤销操作:编辑器中的撤销功能

  6. 递归算法:保存递归调用信息

相关推荐
2603_949462102 小时前
Flutter for OpenHarmony 社团管理App实战 - 资产管理实现
开发语言·javascript·flutter
naruto_lnq2 小时前
分布式日志系统实现
开发语言·c++·算法
索荣荣2 小时前
Java正向代理与反向代理实战指南
java·开发语言
郑州光合科技余经理2 小时前
可独立部署的Java同城O2O系统架构:技术落地
java·开发语言·前端·后端·小程序·系统架构·uni-app
啊我不会诶2 小时前
Codeforces Round 1071 (Div. 3) vp补题
开发语言·学习·算法
格林威2 小时前
Baumer相机金属弹簧圈数自动计数:用于来料快速检验的 6 个核心算法,附 OpenCV+Halcon 实战代码!
人工智能·数码相机·opencv·算法·计算机视觉·视觉检测·堡盟相机
json{shen:"jing"}2 小时前
js收官总概述
开发语言·python
froginwe112 小时前
Java 文档注释
开发语言