算法学习——链表(理论基础)

目录

 一、认识链表

1. 链表的定义

2. 链表的优缺点

3. 链表的与数组的对比

二、单向链表

1. 单向链表的模拟实现

1.1 创建链表

1.2 打印链表

1.3 求链表长度

2. 单向链表的常见方法

2.1 头插法

2.2 尾插法

2.3 插入指定位置

2.4 查找值为 key 的节点

2.5 修改值为 key 的节点

2.6 删除值为 key 的第一个节点

2.7 删除值为 key 的所有节点

2.8 自动排序添加

2.9 反转链表

2.10 清空链表

三、双向链表

1. 双向链表的模拟实现

2. 双向链表的常见方法

 2.1 头插法

2.2 尾插法

2.3 插入指定位置

2.4 清空链表

四、循环链表

约瑟夫(Josephu)环问题

五、双向循环链表

六、LinkedList 类

1. 什么是LinkedList

2. LinkedList的构造方法

3. LinkedList的常用方法

4. LinkedList 与 ArrayList 的区别


 一、认识链表

1. 链表的定义

链表是由称为节点的独立内存单元通过指针连接形成的线性结构。节点在内存中的地址是不连续的,每个节点都包含存储实际数据的数据域和下一个结点的地址信息的指针域

2. 链表的优缺点

  • 优点:
  1. 动态内存分配:链表的大小可以在运行时动态调整,不需要预先分配固定大小的内存空间

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

  3. 内存利用率高:链表的内存分配是按需分配的,不会浪费过多的存储空间
  • 缺点:
  1. 随机访问效率低:链表不支持随机访问,访问某个节点需要从头遍历,时间复杂度为 O(n)

  2. 内存开销大:链表中的节点占用的内存比数组多,因为每个节点有一个简单变量和一个指针变量,指针变量占用4字节内存。

3. 链表的与数组的对比

特点链表数组
内存分配动态分配,非连续内存静态分配,连续内存
插入/删除效率O(1),只需修改指针

O(n),需要移动元素 

随机访问效率O(n),需要遍历O(1),直接通过索引随机访问
内存利用率高,按需分配可能存在浪费

二、单向链表

单向链表的每个节点包含数据和指向下一个节点的指针。

1. 单向链表的模拟实现

1.1 创建链表
class ListNode {
    int val;
    ListNode next;

    ListNode(int val) {
        this.val = val;
        this.next = null;
    }
}

class LinkedList {
    ListNode head; // 头节点

    public LinkedList() {
        this.head = null;
    }
}
1.2 打印链表
public void printList() {
    ListNode current = head;
    while (current != null) {
        System.out.print(current.val + " -> ");
        current = current.next;
    }
    System.out.println("null");
}
1.3 求链表长度
public int getLength() {
    int length = 0;
    ListNode current = head;
    while (current != null) {
        length++;
        current = current.next;
    }
    return length;
}

2. 单向链表的常见方法

2.1 头插法
public void insertAtHead(int val) {
    ListNode newNode = new ListNode(val);
    newNode.next = head;
    head = newNode;
}
2.2 尾插法
public void insertAtTail(int val) {
    ListNode newNode = new ListNode(val);
    if (head == null) {
        head = newNode;
        return;
    }
    ListNode current = head;
    while (current.next != null) {
        current = current.next;
    }
    current.next = newNode;
}
2.3 插入指定位置
public void addAtIndex(int index, int val) {
    if (index < 0 || index > size) return;
    
    if (index == 0) {
        addFirst(val);
        return;
    }
    
    ListNode prev = head;
    for (int i = 0; i < index-1; i++) {
        prev = prev.next;
    }
    
    ListNode newNode = new ListNode(val);
    newNode.next = prev.next;
    prev.next = newNode;
    size++;
}
2.4 查找值为 key 的节点
public boolean contains(int key) {
    ListNode current = head;
    while (current != null) {
        if (current.val == key) return true;
        current = current.next;
    }
    return false;
}
2.5 修改值为 key 的节点
public void update(int key, int newVal) {
    ListNode current = head;
    while (current != null) {
        if (current.val == key) {
            current.val = newVal;
            return;
        }
        current = current.next;
    }
}
2.6 删除值为 key 的第一个节点
public void deleteByKey(int key) {
    if (head == null) {
        return;
    }
    if (head.val == key) {
        head = head.next;
        return;
    }
    ListNode current = head;
    while (current.next != null && current.next.val != key) {
        current = current.next;
    }
    if (current.next != null) {
        current.next = current.next.next;
    }
}
2.7 删除值为 key 的所有节点
public void deleteAll(int key) {
    while (head != null && head.val == key) {
        head = head.next;
    }
    ListNode current = head;
    while (current != null && current.next != null) {
        if (current.next.val == key) {
            current.next = current.next.next;
        } else {
            current = current.next;
        }
    }
}
2.8 自动排序添加
// 插入节点时保持链表的有序性
public void insertSorted(int val) {
    ListNode newNode = new ListNode(val);
    if (head == null || head.val >= val) {
        newNode.next = head;
        head = newNode;
        return;
    }
    ListNode current = head;
    while (current.next != null && current.next.val < val) {
        current = current.next;
    }
    newNode.next = current.next;
    current.next = newNode;
}
2.9 反转链表
public void reverseList() {
    if (head == null || head.next == null) {
        return;
    }

    ListNode prev = null;
    ListNode current = head;
    ListNode next = null;

    while (current != null) {
        next = current.next;  // 暂存下一个节点
        current.next = prev;  // 反转当前节点的指针
        prev = current;       // 将 prev 向前移动
        current = next;       // 将 current 向前移动
    }
    head = prev;  // 更新头节点为新的头节点
}
2.10 清空链表
public void clear() {
    head = null;
}

三、双向链表

双向链表的每个节点包含两个指针,分别指向前一个节点和后一个节点。

1. 双向链表的模拟实现

class DoublyListNode {
    int val;
    DoublyListNode prev;
    DoublyListNode next;

    DoublyListNode(int val) {
        this.val = val;
    }
}

2. 双向链表的常见方法

 2.1 头插法
public void insertAtHead(int val) {
    DoublyListNode newNode = new DoublyListNode(val);
    if (head == null) {
        head = newNode;
        tail = newNode;
    } else {
        newNode.next = head;
        head.prev = newNode;
        head = newNode;
    }
}
2.2 尾插法
public void insertAtTail(int val) {
    DoublyListNode newNode = new DoublyListNode(val);
    if (tail == null) {
        head = newNode;
        tail = newNode;
        return;
    }
    tail.next = newNode;
    newNode.prev = tail;
    tail = newNode;
}
2.3 插入指定位置
public void insertAtPosition(int val, int position) {
    if (position < 0) {
        System.out.println("Invalid position");
        return;
    }
    if (position == 0) {
        insertAtHead(val);
        return;
    }
    DoublyListNode newNode = new DoublyListNode(val);
    DoublyListNode current = head;
    for (int i = 0; current != null && i < position - 1; i++) {
        current = current.next;
    }
    if (current == null) {
        System.out.println("Position out of bounds");
        return;
    }
    newNode.next = current.next;
    newNode.prev = current;
    if (current.next != null) {
        current.next.prev = newNode;
    }
    current.next = newNode;
}
2.4 清空链表
public void clear() {
    head = null;
    tail = null;
}

四、循环链表

循环链表是一种特殊的链表,其尾节点的指针指向头节点,形成一个环形结构。

约瑟夫(Josephu)环问题

约瑟夫环问题描述如下:有 n 个人围成一个圈,从第一个人开始报数,报到 k 的人出圈,然后从下一个人重新开始报数,直到所有人都出圈。我们可以用循环链表来模拟这个问题。

// 创建循环链表
public CircularListNode createCircularList(int n) {
    CircularListNode head = new CircularListNode(1);
    CircularListNode current = head;
    for (int i = 2; i <= n; i++) {
        CircularListNode newNode = new CircularListNode(i);
        current.next = newNode;
        current = newNode;
    }
    current.next = head; // 形成环
    return head;
}

public void josephu(int n, int k) {
    CircularListNode head = createCircularList(n);
    CircularListNode current = head;
    while (current.next != current) {
        for (int i = 1; i < k; i++) {
            current = current.next;
        }
        System.out.println("Out: " + current.next.val);
        current.next = current.next.next;
        current = current.next;
    }
    System.out.println("Last: " + current.val);
}

五、双向循环链表

双向循环链表的每个节点不仅包含指向下一个节点的指针,还包含指向前一个节点的指针,且头尾节点相互连接,形成一个闭环。

每个节点包含三个部分:两个地址部分(分别指向前后两个节点)+一个数据部分。

六、LinkedList 类

1. 什么是LinkedList

LinkedList 是 Java 标准库中的一个类,既可以作为链表使用,也可以作为队列或栈使用。LinkedList的底层是双向链表结构,支持高效的插入和删除操作。

2. LinkedList的构造方法
import java.util.LinkedList; // 使用前需要引入它
LinkedList<Integer> list = new LinkedList<>();
3. LinkedList的常用方法
  • 添加元素add(Element)add(index, Element)addFirst(Element)addLast(Element)

  • 获取元素get(index)getFirst()getLast()

  • 删除元素remove(Element)removeFirst()removeLast()

  • 清空链表clear()

  • 判断是否包含元素contains(Element)

4. LinkedList 与 ArrayList 的区别
特性ArrayListLinkedList
底层结构数组双向链表
访问效率随机访问,效率高顺序访问,效率低
插入/删除效率需要移动大量元素,效率低只需修改指针,效率高
空间分配预分配固定大小,可能浪费空间动态分配,空间利用率高

链表是一种非常灵活而强大的数据结构,在许多算法和程序设计中扮演着重要角色。

本篇全面介绍了数组的相关知识,并结合Java代码示例进行讲解,希望能帮大家更好地掌握链表的理论基础和基本操作。

下期将深入探讨LeetCode上常用的链表练习题,并提供详细的解题思路和代码示例。这些练习题将帮助你巩固链表的知识,提升算法思维和编程能力。


谢谢大家的支持~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值