数据结构:单向链表,顺序栈和链式栈

一、单向链表(Singly Linked List)

1.1 基本定义

单向链表是一种线性数据结构,由一系列节点通过指针单向连接而成。每个节点包含数据域和指向下一个节点的指针域。

1.2 结构定义

复制代码
// 单向链表节点结构
typedef struct ListNode {
    int data;               // 数据域
    struct ListNode *next;  // 指针域
} ListNode;

// 单向链表管理结构(可选)
typedef struct LinkedList {
    ListNode *head;         // 头指针
    ListNode *tail;         // 尾指针
    int length;             // 链表长度
} LinkedList;

1.3 核心特性

  • 动态内存分配:节点按需分配,无需预知数据规模

  • 非连续存储:物理地址分散,通过指针建立逻辑联系

  • 灵活操作:支持任意位置插入和删除

  • 单向遍历:仅支持从头至尾的顺序访问

1.4 时间复杂度分析

操作 时间复杂度 备注
头插 O(1) 直接修改头指针
尾插 O(1)/O(n) 若有尾指针则为O(1)
中间插入 O(n) 需遍历至指定位置
删除 O(1)/O(n) 头删除O(1),其他需遍历
查找 O(n) 必须顺序遍历

二、顺序栈(Sequential Stack)

2.1 基本定义

顺序栈基于数组实现,通过固定偏移量维护栈顶位置,利用连续内存空间存储数据元素。

2.2 结构定义

复制代码
// 静态顺序栈
#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];     // 存储数据的数组
    int top;                // 栈顶指针(数组下标)
} StaticSeqStack;

// 动态顺序栈
typedef struct {
    int *data;              // 动态数组指针
    int top;                // 栈顶指针
    int capacity;           // 栈容量
} DynamicSeqStack;

2.3 核心特性

  • 连续存储:数据在内存中连续存放

  • 大小固定:静态实现有固定上限,动态实现可扩容

  • 操作受限:仅允许在栈顶进行插入和删除

  • 高效访问:通过数组下标直接访问,缓存友好

2.4 关键操作实现

c

复制代码
// 初始化动态顺序栈
DynamicSeqStack* InitDynamicStack(int initCapacity) {
    DynamicSeqStack *stack = (DynamicSeqStack*)malloc(sizeof(DynamicSeqStack));
    stack->data = (int*)malloc(initCapacity * sizeof(int));
    stack->top = -1;
    stack->capacity = initCapacity;
    return stack;
}

// 入栈操作
int Push(DynamicSeqStack *stack, int value) {
    if (stack->top == stack->capacity - 1) {
        // 栈满,需要扩容
        int newCapacity = stack->capacity * 2;
        int *newData = (int*)realloc(stack->data, newCapacity * sizeof(int));
        if (!newData) return 0;
        stack->data = newData;
        stack->capacity = newCapacity;
    }
    stack->data[++stack->top] = value;
    return 1;
}

// 出栈操作
int Pop(DynamicSeqStack *stack) {
    if (stack->top == -1) {
        // 栈空处理
        return INT_MIN;
    }
    return stack->data[stack->top--];
}

三、链式栈(Linked Stack)

3.1 基本定义

链式栈基于链表实现栈结构,具备链表的动态特性与栈的操作限制。其设计存在两种主要模式:单结构体头节点法和双结构体封装法。

3.2 设计模式一:单结构体头节点法

该方法采用单一节点结构,使用头节点作为栈的标识,栈顶位于头节点之后。

结构定义
复制代码
// 单结构体定义
typedef struct StackNode {
    int data;               // 数据域
    struct StackNode *next; // 指针域
} StackNode;               // 栈节点同时也是栈标识
实现特点
  • 头节点作为栈标识:头节点不存储有效数据,其next指针指向栈顶

  • 统一节点类型:仅需一种结构体类型

  • 头插法操作:入栈采用头插法,出栈删除头节点后继

  • 函数参数简单:函数直接接收头节点指针

核心操作示例
复制代码
// 创建空栈
StackNode* CreateStack() {
    StackNode *head = (StackNode*)malloc(sizeof(StackNode));
    head->next = NULL;  // 空栈,头节点next为NULL
    return head;
}

// 入栈操作
void Push(StackNode *head, int value) {
    StackNode *newNode = (StackNode*)malloc(sizeof(StackNode));
    newNode->data = value;
    newNode->next = head->next;  // 新节点指向原栈顶
    head->next = newNode;        // 头节点指向新栈顶
}

// 出栈操作
int Pop(StackNode *head) {
    if (head->next == NULL) {
        return INT_MIN;  // 栈空处理
    }
    StackNode *top = head->next;
    int value = top->data;
    head->next = top->next;
    free(top);
    return value;
}

3.3 设计模式二:双结构体封装法

该方法采用两个结构体,分别定义节点和栈管理结构,实现逻辑与数据分离。

结构定义
复制代码
// 节点结构
typedef struct LinkedStackNode {
    int data;                    // 数据域
    struct LinkedStackNode *next;// 指针域
} LinkedStackNode;

// 栈管理结构
typedef struct {
    LinkedStackNode *top;        // 栈顶指针
    int size;                    // 栈大小
} LinkedStack;
实现特点
  • 逻辑与数据分离:栈管理结构与节点结构分离

  • 状态信息明确:可维护栈大小等状态信息

  • 无头节点开销:栈顶指针直接指向栈顶元素

  • 面向对象思想:更符合现代编程封装理念

核心操作示例
复制代码
// 创建空栈
LinkedStack* CreateLinkedStack() {
    LinkedStack *stack = (LinkedStack*)malloc(sizeof(LinkedStack));
    stack->top = NULL;
    stack->size = 0;
    return stack;
}

// 入栈操作
void PushLinkedStack(LinkedStack *stack, int value) {
    LinkedStackNode *newNode = (LinkedStackNode*)malloc(sizeof(LinkedStackNode));
    newNode->data = value;
    newNode->next = stack->top;  // 新节点指向原栈顶
    stack->top = newNode;        // 更新栈顶指针
    stack->size++;               // 更新栈大小
}

// 出栈操作
int PopLinkedStack(LinkedStack *stack) {
    if (stack->top == NULL) {
        return INT_MIN;  // 栈空处理
    }
    LinkedStackNode *topNode = stack->top;
    int value = topNode->data;
    stack->top = topNode->next;  // 更新栈顶指针
    free(topNode);
    stack->size--;               // 更新栈大小
    return value;
}

3.4 两种设计模式对比

对比维度 单结构体头节点法 双结构体封装法
结构复杂度 简单,一种结构体 复杂,两种结构体
内存开销 有头节点额外开销 无头节点开销
状态维护 需遍历计算栈大小 可直接获取栈大小
代码清晰度 逻辑相对简单 职责分离清晰
扩展性 扩展性有限 易于扩展功能
适用场景 简单栈操作 需要状态维护的复杂栈

四、三项结构综合对比

4.1 存储结构对比

特性 单向链表 顺序栈 链式栈
物理结构 非连续存储 连续存储 非连续存储
内存分配 动态节点分配 静态数组或动态数组 动态节点分配
指针开销 每个节点含指针 无指针开销 每个节点含指针
内存连续性 不连续,碎片化 连续,无碎片 不连续,碎片化

4.2 操作特性对比

特性 单向链表 顺序栈 链式栈
插入位置 任意位置 仅栈顶 仅栈顶
删除位置 任意位置 仅栈顶 仅栈顶
访问方式 顺序访问 随机访问(数组下标) 顺序访问
遍历方向 单向遍历 双向(理论上) 单向遍历
扩容机制 自然扩展 需要显式扩容 自然扩展

4.3 性能指标对比

指标 单向链表 顺序栈 链式栈
头插/入栈 O(1) O(1)(平摊) O(1)
尾插 O(1)/O(n) 不支持 不支持
中间插入 O(n) 不支持 不支持
头删/出栈 O(1) O(1) O(1)
查找访问 O(n) O(1)(通过下标) O(n)
缓存效率
空间利用率 较低(含指针) 较低(含指针)

五、结论

单向链表、顺序栈和链式栈代表了三种不同的线性数据组织策略。单向链表强调操作灵活性,顺序栈注重访问效率,链式栈则结合了动态扩展与操作约束。

链式栈的两种设计模式体现了不同的工程权衡:单结构体头节点法以简洁性见长,双结构体封装法则以扩展性取胜。实际选择应基于具体需求,权衡性能、内存、扩展性和实现复杂度等因素。

在系统设计中,理解这些基础结构的本质特性及其实现差异,对于选择合适的数据结构、优化算法性能、构建稳定可靠的软件系统具有重要意义。每种结构都有其适用场景,优秀的系统设计者应根据具体约束条件做出合理选择,而非盲目追求某种结构的普遍适用性。

相关推荐
XiaoFan0122 小时前
将有向工作流图转为结构树的实现
java·数据结构·决策树
睡一觉就好了。2 小时前
快速排序——霍尔排序,前后指针排序,非递归排序
数据结构·算法·排序算法
齐落山大勇3 小时前
数据结构——单链表
数据结构
皮皮哎哟3 小时前
深入浅出双向链表与Linux内核链表 附数组链表核心区别解析
c语言·数据结构·内核链表·双向链表·循环链表·数组和链表的区别
wWYy.4 小时前
指针与引用区别
数据结构
历程里程碑4 小时前
Linux 17 程序地址空间
linux·运维·服务器·开发语言·数据结构·笔记·排序算法
-dzk-5 小时前
【代码随想录】LC 203.移除链表元素
c语言·数据结构·c++·算法·链表
齐落山大勇5 小时前
数据结构——栈与队列
数据结构
毅炼5 小时前
hot100打卡——day17
java·数据结构·算法·leetcode·深度优先