【初阶数据结构】链表的柔光之美

news/2025/2/26 8:42:44

目录

一、为什么需要链表

二、链表与数组的对比

三、链表节点定义

四、链表基本操作

1. 创建链表

2. 插入节点

头插法(时间复杂度O(1))

尾插法(时间复杂度O(n))

3. 删除节点

4. 遍历链表

五、进阶操作

1. 反转链表(迭代法)

2. 检测环(快慢指针法)

六、内存管理要点

七、常见错误排查

八、链表变体

十、完整示例代码

总结

路过的佬们点点关注哦~

你们的鼓励是我前进的动力~


一、为什么需要链表

在C语言程序设计中,数组是最基础的数据结构,但它存在明显的局限性:

  • 固定长度,无法动态扩展

  • 插入/删除元素需要大量数据移动

  • 内存空间要求连续

链表(Linked List)通过动态内存分配指针连接完美解决了这些问题。每个元素(节点)包含:

  1. 数据域 - 存储实际数据

  2. 指针域 - 存储下一个节点的地址

二、链表与数组的对比

三、链表节点定义

typedef struct Node {
    int data;           // 数据域
    struct Node *next;  // 指针域
} Node;

四、链表基本操作

1. 创建链表

Node* createList(int data) {
    Node* head = (Node*)malloc(sizeof(Node));
    if (head == NULL) {
        printf("内存分配失败!");
        exit(1);
    }
    head->data = data;
    head->next = NULL;
    return head;
}

2. 插入节点

头插法(时间复杂度O(1))
void insertAtHead(Node** head, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = *head;
    *head = newNode;
}
尾插法(时间复杂度O(n))
void insertAtTail(Node* head, int data) {
    Node* current = head;
    while (current->next != NULL) {
        current = current->next;
    }
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;
    current->next = newNode;
}

3. 删除节点

void deleteNode(Node** head, int key) {
    Node *temp = *head, *prev;
    
    // 删除头节点
    if (temp != NULL && temp->data == key) {
        *head = temp->next;
        free(temp);
        return;
    }

    // 查找待删除节点
    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }

    if (temp == NULL) return;

    // 解除链接并释放内存
    prev->next = temp->next;
    free(temp);
}

4. 遍历链表

void printList(Node* head) {
    Node* current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

五、进阶操作

1. 反转链表(迭代法)

void reverseList(Node** head) {
    Node *prev = NULL;
    Node *current = *head;
    Node *next = NULL;
    
    while (current != NULL) {
        next = current->next;  // 保存下一个节点
        current->next = prev;  // 反转指针
        prev = current;        // 前移prev
        current = next;        // 前移current
    }
    *head = prev;
}

2. 检测环(快慢指针法)

int hasCycle(Node *head) {
    Node *slow = head, *fast = head;
    
    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;
        
        if (slow == fast) {
            return 1;  // 存在环
        }
    }
    return 0;  // 无环
}

六、内存管理要点

必须检查malloc返回值

Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
    // 处理内存分配失败
}

及时释放内存

void freeList(Node* head) {
    Node* temp;
    while (head != NULL) {
        temp = head;
        head = head->next;
        free(temp);
    }
}

避免野指针

free(temp);
temp = NULL;  // 释放后立即置空

七、常见错误排查

  1. 段错误(Segmentation Fault)

    • 访问已释放的内存

    • 指针未初始化就使用

  2. 内存泄漏

    • 使用valgrind工具检测

    • 确保每个malloc都有对应的free

  3. 逻辑错误

    • 忘记更新头指针

    • 指针操作顺序错误

八、链表变体

双向链表

typedef struct DNode {
    int data;
    struct DNode *prev;
    struct DNode *next;
} DNode;

循环链表

// 尾节点指向头节点
head->next = head;

带头节点的链表

  • 统一操作逻辑

  • 简化边界条件处理

九、应用场景

  1. 实现栈/队列

  2. 多项式运算

  3. 文件系统目录结构

  4. 图结构的邻接表

  5. 内存管理系统

十、完整示例代码

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

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

// [此处插入上述各个函数实现]

int main() {
    Node* list = createList(5);
    insertAtHead(&list, 2);
    insertAtTail(list, 8);
    
    printf("原始链表: ");
    printList(list);  // 输出: 2 -> 5 -> 8 -> NULL
    
    reverseList(&list);
    printf("反转后: ");
    printList(list);  // 输出: 8 -> 5 -> 2 -> NULL
    
    deleteNode(&list, 5);
    printf("删除后: ");
    printList(list);  // 输出: 8 -> 2 -> NULL
    
    freeList(list);
    return 0;
}

总结

链表是C语言中最基础也最重要的数据结构之一,掌握链表需要理解:

  • 指针的操作原理

  • 动态内存管理机制

  • 数据结构与算法的关系

建议通过以下方式巩固学习:

  1. 手写实现所有链表操作

  2. 使用调试工具观察内存变化

  3. 尝试实现双向链表等变体

  4. 解决LeetCode链表相关题目(如206反转链表、141环形链表

掌握链表将为学习更复杂的数据结构(树、图等)打下坚实基础。

路过的佬们点点关注哦~

你们的鼓励是我前进的动力~


http://www.niftyadmin.cn/n/5868408.html

相关文章

系统调用过程

注意&#xff1a;本系统调用过程基于32位操作系统 中断服务程序的寻址过程 1.用户态程序产生系统调用write()&#xff1b; 2.产生中断指令ENTER_KERNEL(int $0x80128)&#xff0c;CPU收到中断指令去查询中断向量表&#xff0c;找出中断号0x80对应的中断服务程序的内存基地址(0…

C# string转unicode字符

在 C# 中&#xff0c;将字符串转换为 Unicode 字符&#xff08;即每个字符的 Unicode 码点&#xff09;可以通过遍历字符串中的每个字符并获取其 Unicode 值来实现。Unicode 值是一个整数&#xff0c;表示字符在 Unicode 标准中的唯一编号。 以下是实现方法&#xff1a; 1. 获…

[H滑动窗口] lc239. 滑动窗口最大值(模拟+数据结构+单调队列+滑动窗口模板题)

文章目录 1. 题目来源2. 题目解析 1. 题目来源 链接&#xff1a;239. 滑动窗口最大值 相关博文&#xff1a; [单调队列模板] 单调队列模板 题单&#xff1a; 待补充 2. 题目解析 一道单调队列模板题&#xff0c;不赘述了吧。 看看日后有没有写不出来来补题、或者有新…

深入理解 `Sinks.Empty<Void>` 和 `Mono<Void>`:如何触发完成信号并结合 WebSocket 示例

在响应式编程中&#xff0c;Sinks 是 Project Reactor 提供的一个强大工具&#xff0c;用于手动控制数据流的信号发射。Sinks.Empty<Void> 是一种特殊的 Sinks&#xff0c;它不发射任何数据&#xff0c;仅用于表示完成或错误信号。结合 Mono<Void>&#xff0c;它可…

网络基础知识-2

N个节点完全互联的网型网即N个节点的无向完全图&#xff0c;无向完全图的边数计算如下&#xff1a;每个节点都要指向其他N-1个节点&#xff0c;但是因为无向两个节点之间的边会重复&#xff0c;因此有N(N-1)/2条边HDLC&#xff08;高级数据链路控制协议&#xff09;是一种面向比…

二叉树-左叶子之和

代码随想录-刷题笔记 404. 左叶子之和 - 力扣&#xff08;LeetCode&#xff09; 内容&#xff1a; 该题仅作为搜索&#xff0c;但是其中的规则让人摸不着头脑&#xff0c;看起来似乎很头疼 但是仔细一思考&#xff0c;能发现左叶子无非是这样的定义 当发现一个节点的 左孩…

单片机裸机编程:状态机与其他高效编程框架

在单片机裸机编程中&#xff0c;状态机是一种非常强大的工具&#xff0c;能够有效管理复杂的逻辑和任务切换。除了状态机&#xff0c;还有其他几种编程模式可以在不使用 RTOS 的情况下实现高效的程序设计。以下是一些常见的方法&#xff1a; 1. 状态机编程 状态机通过定义系统…

C语言 —— 此去经年 应是良辰好景虚设 - 函数

目录 1. 函数的概念 1.1 库函数 1.2 自定义函数 2. 形参和实参 3. return 语句 4. 数组做函数参数 5. 嵌套调用和链式访问 5.1 嵌套调用 5.2 链式访问 6. 函数的声明和定义 6.1 单个文件 6.2 多个文件 7. static 和 extern 7.1 static 修饰局部变量 7.2 static 修…