C语言数据结构:顺序栈与链栈实现详解

Noah
Noah
发布时间:2026-10-15| 5人浏览过
原创

栈是一种运算受限的线性表,遵循“后进先出”(LIFO)原则。在C语言学习中,熟练掌握其顺序存储(基于数组)和链式存储(基于链表)的实现是数据结构的重点。本文将结合具体代码,详细解析这两种结构在初始化、入栈、出栈及边界处理上的逻辑差异。

学习目标

理解栈的核心特性及其在实际计算中的应用场景,掌握以下关键点:

  • 清晰界定栈顶(Top)、栈底(Bottom)与空栈的概念。
  • 区分并实现顺序栈(预分配内存)与链栈(动态分配内存)的结构。
  • 掌握进栈(Push)与出栈(Pop)操作的底层逻辑及状态检测(上溢/下溢)。

栈的基本概念

栈是限定仅在表尾进行插入和删除操作的线性表。这种受限性使得栈的操作具有严格的单向性:

  • 栈顶:允许进行插入(进栈)和删除(出栈)的一端。
  • 栈底:不允许操作的另一端。
  • LIFO 原则:栈元素遵循 Last In First Out(后进先出)。就像洗盘子或叠放书脊一样,最后放入的元素必须最先取出。

顺序栈(Sequential Stack)实现

顺序栈利用一段连续的内存空间(通常为一维数组)来存储数据元素,使用整型变量 top 作为栈顶指针指示当前位置。

第1步:定义结构与初始化

首先需要定义栈的数据类型,包括用于存储数据的数组指针和记录栈顶位置的索引。初始化时,需要为数组动态分配内存,并将 top 置为 0(表示初始为空栈)。

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

#define MAXSIZE 6 // 定义最大容量

typedef struct {
    int *data;  // 存储元素的数组指针
    int top;    // 栈顶指针(索引)
} SeqStack;

// 初始化函数
SeqStack initSeqStack() {
    SeqStack s;
    s.data = (int *)malloc(MAXSIZE * sizeof(int));
    if (!s.data) {
        printf("内存分配失败!\n");
        exit(0);
    }
    s.top = 0; // 初始栈顶为0
    return s;
}

第2步:判断栈空与栈满

在进行入栈前检查是否“上溢”(Full),出栈前检查是否“下溢”(Empty),这是防止程序崩溃的关键。

// 判断栈是否为空
int isEmpty(SeqStack s) {
    return s.top == 0;
}

// 判断栈是否已满
int isFull(SeqStack s) {
    return s.top == MAXSIZE;
}

第3步:进栈(Push)与出栈(Pop)

进栈:将新元素存入当前 top 指向的位置,随后 top 自增。如果栈已满,则无法执行此操作。

出栈:top 先自减,再读取该位置的值并返回。如果栈已空,说明无元素可取,即发生“下溢”错误。

// 进栈操作
void push(SeqStack *s, int value) {
    if (isFull(*s)) {
        printf("栈满,无法进栈!\n");
        return;
    }
    s->data[s->top] = value;
    s->top++;
}

// 出栈操作
int pop(SeqStack *s) {
    if (isEmpty(*s)) {
        printf("栈空,无法出栈!\n");
        return -1; // 根据需求定义错误码
    }
    s->top--;
    return s->data[s->top];
}

第4步:获取栈顶元素(Get Top)

与出栈不同,Get Top 仅查看不移动栈顶指针。由于 C 语言数组从 0 开始编号,当 top=N 时,最后一个有效元素的索引为 N-1。

int getTop(SeqStack s) {
    if (isEmpty(s)) {
        printf("栈空!\n");
        return -1;
    }
    return s.data[s.top - 1];
}

链栈(Linked Stack)实现

链栈采用单链表结构实现,通常在链表头部作为栈顶。这样做的好处是不像顺序栈那样需要预先固定容量,而是利用系统的动态内存管理,理论上只受限于系统可用内存。

第1步:定义链栈节点

链栈节点包含数据域和指向下一个节点的指针域。头指针 head 指向栈顶的第一个元素。

typedef struct Node {
    int data;
    struct Node *next;
} LNode, *LinkStack;

第2步:链栈的进栈与出栈

由于是在链表头部操作,进栈类似于头插法,出栈则是移除首元结点,两者的时间复杂度均为 O(1)。

// 进栈(头插法)
void push(LinkStack *stack, int val) {
    LNode *newNode = (LNode *)malloc(sizeof(LNode));
    newNode->data = val;
    newNode->next = (*stack)->next;
    (*stack)->next = newNode;
}

// 出栈
int pop(LinkStack stack) {
    if (stack->next == NULL) {
        printf("栈空!\n");
        return -1;
    }
    LNode *temp = stack->next;
    int val = temp->data;
    
    // 修改头指针的指向
    stack->next = temp->next;
    free(temp); // 释放被弹出节点的内存
    return val;
}

考试要点:手动模拟进出栈序列

在期末或自考考试中,经常会出现要求列出特定输入序列所有可能的输出排列的题目。这类题目不涉及复杂代码,但核心在于严格遵循 LIFO 原则:

  1. 明确限制条件:只能在入栈过程中选择在任何时刻暂停入栈并开始出栈。
  2. 不可越级:如果元素 A 已经入栈且尚未出栈,那么位于它后面入栈的元素 B、C... 必须先于 A 出栈。因此,绝对不可能出现“A 进、B 进、B 出、C 进、A 出”这样的逆序错误。

例题演示:输入序列为 A, B, C。求所有合法的出栈序列。

  • A进, A出 -> (空栈) -> B进, B出 -> C进, C出 => ABC
  • A进, B进, B出, A出 -> (空栈) -> C进, C出 => BAC
  • A进, B进, C进, C出, B出, A出 => CBA
  • 以此类推,共有5种合法情况(卡特兰数问题的一种表现形式)。

总结

无论是顺序栈还是链栈,其本质都是利用特定的内存布局来实现“后进先出”的逻辑约束。顺序栈适合规模已知且固定的场景,而链栈则在处理不确定长度数据时展现了灵活性。掌握了这两者的C语言底层实现,不仅有助于通过数据结构课程的考核,也为后续学习复杂的树形结构和图算法打下坚实基础。

以上就是C语言顺序栈与链栈实现的详细内容,更多关于C语言数据结构栈的知识请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。