数据结构之链表

1. 链表的基本概念

  • 节点(Node):链表的基本组成单元。每个节点包含数据和指向下一个节点的指针。
  • 头节点(Head):指向链表的第一个节点,通常用于定位整个链表。
  • 尾节点(Tail) :指向链表的最后一个节点,尾节点的指针域通常为 null(即不再有下一个节点)。
  • 空链表(Empty List) :链表中没有任何节点时,头指针为 null

2. 链表的类型

  • 单向链表(Singly Linked List)

    • 每个节点只包含一个指向下一个节点的引用(即单向引用)。
    • 特点:只能从头节点遍历到尾节点,不能逆向遍历。

    节点结构:

    复制代码

    class Node {
    int data;
    Node next;
    }

  • 双向链表(Doubly Linked List)

    • 每个节点包含两个引用:一个指向下一个节点,一个指向前一个节点。
    • 特点:可以在链表中双向遍历,操作更灵活,但每个节点占用更多内存。

    节点结构:

    复制代码

    class Node {
    int data;
    Node next;
    Node prev;
    }

  • 循环链表(Circular Linked List)

    • 最后一个节点的指针域指向链表的头节点,从而形成一个环。
    • 单向循环链表和双向循环链表都有,适用于一些特殊场景,如任务调度。

3. 链表的常见操作

(1) 遍历

遍历链表就是从头节点开始,依次访问每个节点,直到 null(对于单向链表)或循环到头节点(对于循环链表)。

复制代码

Node current = head;
while (current != null) {
System.out.println(current.data);
current = current.next;
}

(2) 插入
  • 头部插入:将新节点插入链表的头部。时间复杂度为 O(1)。
  • 尾部插入:将新节点插入链表的尾部。对于单向链表,时间复杂度为 O(n)(需要遍历到尾部),但如果有指向尾节点的指针,复杂度为 O(1)。
  • 中间插入:将新节点插入到链表中的任意位置。时间复杂度为 O(n)(需要找到插入点)。
复制代码

// 头部插入示例
Node newNode = new Node(data);
newNode.next = head;
head = newNode;

(3) 删除
  • 删除头节点:直接将头指针指向第二个节点,时间复杂度为 O(1)。
  • 删除尾节点 :需要遍历到倒数第二个节点并将其 next 设置为 null。时间复杂度为 O(n)。
  • 删除中间节点 :找到待删除节点的前一个节点,将前一个节点的 next 指针指向待删除节点的下一个节点,时间复杂度为 O(n)。
复制代码

// 删除头节点示例

if (head != null) {

head = head.next;

}

(4) 查找
  • 按值查找:从头节点开始遍历链表,找到值匹配的节点。时间复杂度为 O(n)。
  • 按位置查找:从头节点遍历链表,直到到达指定位置的节点。时间复杂度为 O(n)。

4. 链表的优缺点

优点
  • 动态大小:链表可以在运行时动态分配和释放内存,不需要提前定义大小,适合频繁插入和删除的场景。
  • 高效的插入和删除:链表在进行插入和删除操作时,只需要调整指针,不需要像数组一样移动元素,效率较高,时间复杂度为 O(1)(头部插入/删除)或 O(n)(中间、尾部插入/删除)。
缺点
  • 随机访问性能差:由于链表不是连续存储的,不能通过索引直接访问某个节点,只能通过遍历链表找到目标节点。时间复杂度为 O(n)。
  • 额外的内存开销 :链表中的每个节点都需要存储指针信息(单向链表存储 next,双向链表还需要存储 prev),因此相对于数组有额外的内存开销。

5. 链表的常见应用

  • 实现队列和栈:链表可以很方便地实现队列(FIFO)和栈(LIFO),因为它支持在头部或尾部进行快速的插入和删除操作。
  • 哈希表中的拉链法:当哈希冲突发生时,可以使用链表将同一个哈希值对应的多个元素串联起来。
  • 图的邻接表:在图的表示中,邻接表通常使用链表来存储每个顶点的邻接顶点。
  • LRU缓存机制:使用双向链表与哈希表结合,可以实现高效的 LRU(Least Recently Used)缓存。

6. 链表的常见面试题

  • 反转链表:要求将单向链表反转,使得原来的头节点变为尾节点,尾节点变为头节点。

    复制代码

    public Node reverseList(Node head) {
    Node prev = null;
    Node current = head;
    while (current != null) {
    Node next = current.next;
    current.next = prev;
    prev = current;
    current = next;
    }
    return prev;
    }

  • 链表中环的检测:使用"快慢指针"方法,快指针一次走两步,慢指针一次走一步。如果链表有环,快慢指针会在某一点相遇。

    复制代码

    public boolean hasCycle(Node head) {

    if (head == null || head.next == null) return false;

    Node slow = head;

    Node fast = head.next;

    while (slow != fast) {

    if (fast == null || fast.next == null) return false;

    slow = slow.next;

    fast = fast.next.next;

    }

    return true;

    }

  • 合并两个有序链表:将两个已经排序的链表合并为一个新的有序链表。

    复制代码

    public Node mergeTwoLists(Node l1, Node l2) {

    Node dummy = new Node(0);

    Node current = dummy;

    while (l1 != null && l2 != null) {

    if (l1.data <= l2.data) {

    current.next = l1;

    l1 = l1.next;

    } else {

    current.next = l2;

    l2 = l2.next;

    }

    current = current.next;

    }

    current.next = (l1 != null) ? l1 : l2;

    return dummy.next;

    }


7. 链表与其他数据结构的比较

  • 与数组
    • 插入/删除:链表比数组高效。链表的插入和删除操作在 O(1) 时间内完成,而数组需要 O(n)。
    • 访问效率:数组支持 O(1) 的随机访问,而链表只能通过 O(n) 的遍历找到某个元素。
  • 与栈/队列
    • 链表可以很好地实现栈和队列的功能,通过头部插入、尾部删除等操作实现先进先出或后进先出。

相关推荐
J_admin29 分钟前
数据结构 之 二叉树的遍历------先根遍历(五)
数据结构
Wils0nEdwards3 小时前
Leetcode 合并 K 个升序链表
算法·leetcode·链表
黑不拉几的小白兔6 小时前
PTA L1系列题解(C语言)(L1_097 -- L1_104)
数据结构·算法·1024程序员节
秋说6 小时前
【数据结构 | PTA】懂蛇语
数据结构·c++
ChaoZiLL7 小时前
关于我的数据结构与算法——初阶第二篇(排序)
数据结构·算法
single5948 小时前
【综合算法学习】(第十篇)
java·数据结构·c++·vscode·学习·算法·leetcode
free_girl_fang9 小时前
夯实根基之MySql从入门到精通(一)
java·数据结构·数据库·mysql
Lonelinessser9 小时前
数据结构——基础知识补充
数据结构
凡尘技术9 小时前
算法实现 - 快速排序(Quick Sort) - 理解版
java·数据结构·算法
wh233z9 小时前
Codeforces Round 981 (Div. 3) (A~F)
c语言·数据结构·c++·算法