Bootstrap

数据结构:链表详解

1. 什么是链表?

链表(Linked List)是一种 线性数据结构,由一系列 节点(Node) 组成,每个节点包含 数据域 和 指针域。链表中的节点在内存中 不一定连续存储,通过指针连接。

链表的特点
  1. 动态大小:链表的大小可以动态调整。

  2. 非连续存储:节点在内存中不一定连续存储。

  3. 插入和删除高效:在链表中插入或删除节点的时间复杂度为 O(1)O(1)(已知位置)。


2. 链表的类型

2.1 单链表(Singly Linked List)
  • 每个节点只有一个指针,指向下一个节点。

  • 最后一个节点的指针指向 nullptr

2.2 双向链表(Doubly Linked List)
  • 每个节点有两个指针,分别指向前一个节点和后一个节点。

  • 支持双向遍历。

2.3 循环链表(Circular Linked List)
  • 单链表或双向链表的变种,最后一个节点的指针指向头节点。


3. 链表的基本操作

3.1 定义节点
  • 单链表节点:

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

双向链表节点:

struct ListNode {
    int val;
    ListNode* prev;
    ListNode* next;
    ListNode(int x) : val(x), prev(nullptr), next(nullptr) {}
};
3.2 创建链表
  • 手动创建链表:

ListNode* head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
3.3 遍历链表
  • 使用循环遍历链表:

void printList(ListNode* head) {
    ListNode* curr = head;
    while (curr != nullptr) {
        cout << curr->val << " ";
        curr = curr->next;
    }
}
3.4 插入节点
  • 在链表头部插入节点:

void insertAtHead(ListNode*& head, int val) {
    ListNode* newNode = new ListNode(val);
    newNode->next = head;
    head = newNode;
}

       在链表尾部插入节点:

void insertAtTail(ListNode*& head, int val) {
    ListNode* newNode = new ListNode(val);
    if (head == nullptr) {
        head = newNode;
        return;
    }
    ListNode* curr = head;
    while (curr->next != nullptr) {
        curr = curr->next;
    }
    curr->next = newNode;
}
3.5 删除节点
  • 删除指定值的节点:

void deleteNode(ListNode*& head, int val) {
    if (head == nullptr) return;
    if (head->val == val) {
        ListNode* temp = head;
        head = head->next;
        delete temp;
        return;
    }
    ListNode* curr = head;
    while (curr->next != nullptr && curr->next->val != val) {
        curr = curr->next;
    }
    if (curr->next != nullptr) {
        ListNode* temp = curr->next;
        curr->next = curr->next->next;
        delete temp;
    }
}

4. 链表的优缺点

4.1 优点
  1. 动态大小:链表的大小可以动态调整。

  2. 插入和删除高效:在链表中插入或删除节点的时间复杂度为 O(1)O(1)(已知位置)。

  3. 内存利用率高:节点在内存中不一定连续存储,适合动态分配内存。

4.2 缺点
  1. 随机访问效率低:访问链表中的元素需要从头遍历,时间复杂度为 O(n)O(n)。

  2. 额外空间开销:每个节点需要额外的指针空间。


5. 链表的常见问题

5.1 反转链表
  • 使用迭代法反转链表:

ListNode* reverseList(ListNode* head) {
    ListNode* prev = nullptr;
    ListNode* curr = head;
    while (curr != nullptr) {
        ListNode* next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}
5.2 检测链表是否有环
  • 使用快慢指针检测环:

bool hasCycle(ListNode* head) {
    if (head == nullptr) return false;
    ListNode* slow = head;
    ListNode* fast = head;
    while (fast != nullptr && fast->next != nullptr) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) return true;
    }
    return false;
}
5.3 合并两个有序链表
  • 递归合并两个有序链表:

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    if (l1 == nullptr) return l2;
    if (l2 == nullptr) return l1;
    if (l1->val < l2->val) {
        l1->next = mergeTwoLists(l1->next, l2);
        return l1;
    } else {
        l2->next = mergeTwoLists(l1, l2->next);
        return l2;
    }
}

6. 链表的应用场景

  1. 实现动态数据结构:链表可以用于实现栈、队列、哈希表等数据结构。

  2. 内存管理:链表适合动态分配内存的场景。

  3. 图算法:链表可以用于表示图的邻接表。


7. 总结

链表是一种灵活的数据结构,具有 动态大小 和 高效插入删除 的特点。尽管链表的随机访问效率较低,但它在许多场景中仍然是不可替代的。

;