数据结构——链表

引言

链表(Linked List)是计算机科学中最基础且灵活的数据结构之一。与数组的连续内存分配不同,链表通过指针将零散的内存块串联起来,允许动态调整数据规模,避免内存浪费。链表广泛应用于操作系统内核、数据库索引、动态内存管理等领域。本文将深入解析链表的核心概念、实现方式、典型应用场景及常见变种,帮助你全面掌握这一数据结构。


一、链表的基本概念

链表由一系列**节点(Node)**组成,每个节点包含两部分:

  1. 数据域:存储实际数据(如整数、字符串、对象等)。

  2. 指针域:指向下一个节点的地址(在双向链表中还可能包含指向前一个节点的指针)。

链表的操作特性:

  • 动态内存分配:无需预先分配固定内存空间。

  • 高效插入/删除 :时间复杂度为 O(1)(已知插入位置时)。

  • 低效随机访问 :查找第 i 个元素需要从头遍历,时间复杂度为 O(n)


二、链表的分类与实现

链表根据指针域的复杂度分为三种主要类型,下面分别通过 C语言代码 实现并分析其特点。

1. 单向链表(Singly Linked List)

每个节点仅包含指向下一个节点的指针,适合单向遍历场景。

代码实现

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

// 定义链表节点结构体
typedef struct Node {
    int data;           // 数据域
    struct Node *next;  // 指针域(指向下一个节点)
} Node;

// 创建新节点
Node* createNode(int value) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("内存分配失败!\n");
        exit(EXIT_FAILURE);
    }
    newNode->data = value;
    newNode->next = NULL;
    return newNode;
}

// 在链表尾部插入节点
void insertAtTail(Node **head, int value) {
    Node *newNode = createNode(value);
    if (*head == NULL) {
        *head = newNode;
        return;
    }
    Node *current = *head;
    while (current->next != NULL) {
        current = current->next;
    }
    current->next = newNode;
}

// 删除指定值的节点
void deleteNode(Node **head, int value) {
    if (*head == NULL) return;

    Node *current = *head;
    Node *prev = NULL;

    // 处理头节点为待删除节点的情况
    if (current->data == value) {
        *head = current->next;
        free(current);
        return;
    }

    // 遍历查找待删除节点
    while (current != NULL && current->data != value) {
        prev = current;
        current = current->next;
    }

    if (current == NULL) {
        printf("未找到值为 %d 的节点\n", value);
        return;
    }

    prev->next = current->next;
    free(current);
}

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

int main() {
    Node *head = NULL;  // 链表头指针

    insertAtTail(&head, 10);
    insertAtTail(&head, 20);
    insertAtTail(&head, 30);
    printList(head);    // 输出: 10 -> 20 -> 30 -> NULL

    deleteNode(&head, 20);
    printList(head);    // 输出: 10 -> 30 -> NULL

    return 0;
}

特点

  • 优点:内存利用率高,插入/删除速度快。

  • 缺点:无法逆向遍历,删除节点需记录前驱节点。


2. 双向链表(Doubly Linked List)

每个节点包含两个指针:prev(指向前驱节点)和 next(指向后继节点),支持双向遍历。

代码实现(关键函数)

cpp 复制代码
typedef struct DNode {
    int data;
    struct DNode *prev;
    struct DNode *next;
} DNode;

// 在双向链表头部插入节点
void insertAtHead(DNode **head, int value) {
    DNode *newNode = (DNode*)malloc(sizeof(DNode));
    newNode->data = value;
    newNode->prev = NULL;
    newNode->next = *head;

    if (*head != NULL) {
        (*head)->prev = newNode;
    }
    *head = newNode;
}

// 删除指定值的节点(双向链表版本)
void deleteDNode(DNode **head, int value) {
    DNode *current = *head;
    while (current != NULL && current->data != value) {
        current = current->next;
    }

    if (current == NULL) return;

    if (current->prev != NULL) {
        current->prev->next = current->next;
    } else {
        *head = current->next;
    }

    if (current->next != NULL) {
        current->next->prev = current->prev;
    }

    free(current);
}

特点

  • 优点:支持双向遍历,删除操作更高效。

  • 缺点:内存占用稍高(每个节点多一个指针)。


3. 循环链表(Circular Linked List)

尾节点的指针指向头节点,形成闭环。适合轮询调度等场景。

代码实现(循环链表初始化)

cpp 复制代码
void insertAtTailCircular(Node **head, int value) {
    Node *newNode = createNode(value);
    if (*head == NULL) {
        *head = newNode;
        newNode->next = newNode;  // 指向自身形成环
        return;
    }

    Node *current = *head;
    while (current->next != *head) {
        current = current->next;
    }
    current->next = newNode;
    newNode->next = *head;
}

三、链表的应用场景

  1. 实现动态数据结构:如队列、栈、哈希表链地址法。

  2. 内存管理:操作系统使用链表管理空闲内存块。

  3. 文件系统:FAT文件系统用链表记录文件簇的分配。

  4. LRU缓存淘汰算法:双向链表快速移动热点数据。


四、链表的常见问题与优化

  1. 内存泄漏

    • 务必在删除节点后调用 free() 释放内存。

    • 使用工具(如Valgrind)检测内存泄漏。

  2. 边界条件处理

    • 空链表插入/删除操作。

    • 头节点和尾节点的特殊处理。

  3. 性能优化

    • 引入尾指针 提升尾部插入效率(时间复杂度从 O(n) 降至 O(1))。

    • 使用带头节点的链表简化代码逻辑(无需单独处理头指针变更)。


五、总结

链表通过指针的灵活链接,完美解决了数组的固定大小限制问题,是构建复杂动态系统的基石。掌握单链表、双向链表和循环链表的实现与适用场景,能够帮助你在实际开发中高效处理动态数据。尽管链表的随机访问效率较低,但其在插入、删除操作上的优势使其在特定场景下不可替代。


参考资料

  1. 《算法导论》------ Thomas H. Cormen

  2. 《数据结构与算法分析》------ Mark Allen Weiss

  3. GeeksforGeeks: Linked List Data Structure


如果对链表的具体操作或应用仍有疑问,欢迎在评论区留言交流!

相关推荐
forestqq1 小时前
openEuler22.03LTS系统升级docker至26.1.4以支持启用ip6tables功能
linux·运维·docker
Zz_waiting.2 小时前
java数据结构_二叉树_5.4
数据结构·算法
亲爱的老吉先森4 小时前
常见数据结构的C语言定义---《数据结构C语言版》
c语言·开发语言·数据结构
蓝创精英团队4 小时前
基于Ubuntu Ollama 部署 DeepSeek-R132B 聊天大模型(附带流式接口调用示例)
linux·运维·ubuntu·deepseek
快去睡觉~6 小时前
Linux之Http协议分析以及cookie和session
linux·运维·http
致奋斗的我们6 小时前
项目:利用rsync备份全网服务器数据
linux·运维·服务器·开发语言·github·rsync·openeuler
Htht1116 小时前
【Linux】之【bug】“sudo wpa_cli -i wlan0 scan“ 返回 FAIL-BUSY 解决
linux·运维·bug
pineapple rong7 小时前
shell脚本控制——处理信号
linux·bash
凡夫贩夫7 小时前
从零开始:CentOS 7系统中Docker的安装与卸载全记录
linux·运维·服务器·docker·centos
robin59117 小时前
CentOS虚机在线扩容系统盘数据盘
linux·运维·centos