Java数据结构之链表(LinkedList)

Java数据结构之链表(LinkedList)

文章目录

  • Java数据结构之链表(LinkedList)
    • 1. 简介
    • 2. 单链表
      • 2.1 单链表的基本结构
      • 2.2 单链表的插入操作
        • 2.2.1 在链表头部插入新节点
        • 2.2.2 在链表尾部插入新节点
        • 2.2.3 在指定位置插入新节点
      • 2.3 单链表的删除操作
        • 2.3.1 删除链表头节点
        • 2.3.2 删除链表尾节点
        • 2.3.3 删除指定位置的节点
      • 2.4 单链表的查找操作
      • 2.5 单链表的时间复杂度
    • 3. 双链表
      • 3.1 双链表的基本结构
      • 3.2 双链表的插入操作
        • 3.2.1 在链表头部插入新节点
        • 3.2.2 在链表尾部插入新节点
        • 3.2.3 在指定位置插入新节点
      • 3.3 双链表的删除操作
        • 3.3.1 删除链表头节点
        • 3.3.2 删除链表尾节点
        • 3.3.3 删除指定位置的节点
      • 3.4 双链表的查找操作
        • 3.4.1 从前往后查找
        • 3.4.2 从后往前查找
      • 3.5 双链表的时间复杂度
    • 4. 循环链表
      • 4.1 循环链表的基本结构
      • 4.2 循环链表的插入操作
        • 4.2.1 在链表头部插入新节点
        • 4.2.2 在链表尾部插入新节点
        • 4.2.3 在指定位置插入新节点
      • 4.3 循环链表的删除操作
        • 4.3.1 删除链表头节点
        • 4.3.2 删除链表尾节点
        • 4.3.3 删除指定位置的节点
      • 4.4 循环链表的查找操作
      • 4.5 循环链表的时间复杂度
    • 5. 常见问题
      • 5.1 链表与数组的比较
      • 5.2 如何避免链表中的内存泄漏?
      • 5.3 如何判断链表中是否存在环?
    • 6. 总结

1. 简介

链表是一种常见的数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的引用。链表具有灵活性和高效的插入和删除操作,因此在很多场景下都比数组更加适用。本文将详细介绍Java中链表的各种类型以及它们的操作和应用。

2. 单链表

2.1 单链表的基本结构

单链表是最简单的链表类型,它由一系列节点组成,每个节点包含数据和指向下一个节点的引用。在Java中,可以使用如下的类定义单链表的节点:

class ListNode {
    int data;
    ListNode next;
    
    public ListNode(int data) {
        this.data = data;
        this.next = null;
    }
}

单链表的头节点是链表的入口,通常使用一个指向头节点的引用来操作整个链表。以下是单链表的基本操作。

2.2 单链表的插入操作

在单链表中插入一个新节点,通常有三种情况:

2.2.1 在链表头部插入新节点
public void insertAtBeginning(int data) {
    ListNode newNode = new ListNode(data);
    newNode.next = head;
    head = newNode;
}
2.2.2 在链表尾部插入新节点
public void insertAtEnd(int data) {
    ListNode newNode = new ListNode(data);
    if (head == null) {
        head = newNode;
    } else {
        ListNode current = head;
        while (current.next != null) {
            current = current.next;
        }
        current.next = newNode;
    }
}
2.2.3 在指定位置插入新节点
public void insertAtIndex(int data, int index) {
    if (index < 0) {
        throw new IllegalArgumentException("Invalid index");
    }
    ListNode newNode = new ListNode(data);
    if (index == 0) {
        newNode.next = head;
        head = newNode;
    } else {
        ListNode current = head;
        int i = 0;
        while (i < index - 1 && current != null) {
            current = current.next;
            i++;
        }
        if (current == null) {
            throw new IndexOutOfBoundsException("Index out of range");
        }
        newNode.next = current.next;
        current.next = newNode;
    }
}

2.3 单链表的删除操作

单链表的删除操作通常有以下几种情况:

2.3.1 删除链表头节点
public void deleteAtBeginning() {
    if (head != null) {
        head = head.next;
    }
}
2.3.2 删除链表尾节点
public void deleteAtEnd() {
    if (head == null) {
        return;
    }
    if (head.next == null) {
        head = null;
        return;
    }
    ListNode current = head;
    while (current.next.next != null) {
        current = current.next;
    }
    current.next = null;
}
2.3.3 删除指定位置的节点
public void deleteAtIndex(int index) {
    if (index < 0 || head == null) {
        return;
    }
    if (index == 0) {
        head = head.next;
        return;
    }
    ListNode current = head;
    int i = 0;
    while (i < index - 1 && current != null) {
        current = current.next;
        i++;
    }
    if (current == null || current.next == null) {
        return;
    }
    current.next = current.next.next;
}

2.4 单链表的查找操作

查找操作用于查找链表中是否存在某个特定的元素,通常实现如下:

public boolean contains(int data) {
    ListNode current = head;
    while (current != null) {
        if (current.data == data) {
            return true;
        }
        current = current.next;
    }
    return false;
}

2.5 单链表的时间复杂度

  • 插入和删除操作的时间复杂度为O(n),其中n是链表的长度。
  • 查找操作的时间复杂度也为O(n)。

单链表适用于需要频繁插入和删除元素的场景,但不适用于需要快速随机访问元素的场景。

3. 双链表

3.1 双链表的基本结构

双链表与单链表不同之处在于,每个节点除了包含数据和指向下一个节点的引用外,还包含指向前一个节点的引用。以下是双链表节点的定义:

class DoubleListNode {
    int data;
    DoubleListNode next;
    DoubleListNode prev;
    
    public DoubleListNode(int data) {
        this.data = data;
        this.next = null;
        this.prev = null;
    }
}

双链表支持双向遍历

,因此在某些场景下比单链表更加灵活。以下是双链表的基本操作。

3.2 双链表的插入操作

双链表的插入操作与单链表类似,但需要同时更新前后节点的引用。

3.2.1 在链表头部插入新节点
public void insertAtBeginning(int data) {
    DoubleListNode newNode = new DoubleListNode(data);
    newNode.next = head;
    if (head != null) {
        head.prev = newNode;
    }
    head = newNode;
}
3.2.2 在链表尾部插入新节点
public void insertAtEnd(int data) {
    DoubleListNode newNode = new DoubleListNode(data);
    if (head == null) {
        head = newNode;
    } else {
        DoubleListNode current = head;
        while (current.next != null) {
            current = current.next;
        }
        current.next = newNode;
        newNode.prev = current;
    }
}
3.2.3 在指定位置插入新节点
public void insertAtIndex(int data, int index) {
    if (index < 0) {
        throw new IllegalArgumentException("Invalid index");
    }
    DoubleListNode newNode = new DoubleListNode(data);
    if (index == 0) {
        newNode.next = head;
        if (head != null) {
            head.prev = newNode;
        }
        head = newNode;
    } else {
        DoubleListNode current = head;
        int i = 0;
        while (i < index - 1 && current != null) {
            current = current.next;
            i++;
        }
        if (current == null) {
            throw new IndexOutOfBoundsException("Index out of range");
        }
        newNode.next = current.next;
        if (current.next != null) {
            current.next.prev = newNode;
        }
        current.next = newNode;
        newNode.prev = current;
    }
}

3.3 双链表的删除操作

双链表的删除操作也与单链表类似,需要同时更新前后节点的引用。

3.3.1 删除链表头节点
public void deleteAtBeginning() {
    if (head != null) {
        head = head.next;
        if (head != null) {
            head.prev = null;
        }
    }
}
3.3.2 删除链表尾节点
public void deleteAtEnd() {
    if (head == null) {
        return;
    }
    if (head.next == null) {
        head = null;
        return;
    }
    DoubleListNode current = head;
    while (current.next.next != null) {
        current = current.next;
    }
    current.next = null;
}
3.3.3 删除指定位置的节点
public void deleteAtIndex(int index) {
    if (index < 0 || head == null) {
        return;
    }
    if (index == 0) {
        head = head.next;
        if (head != null) {
            head.prev = null;
        }
        return;
    }
    DoubleListNode current = head;
    int i = 0;
    while (i < index - 1 && current != null) {
        current = current.next;
        i++;
    }
    if (current == null || current.next == null) {
        return;
    }
    current.next = current.next.next;
    if (current.next != null) {
        current.next.prev = current;
    }
}

3.4 双链表的查找操作

双链表的查找操作与单链表类似,可以从前往后或从后往前遍历链表。

3.4.1 从前往后查找
public boolean contains(int data) {
    DoubleListNode current = head;
    while (current != null) {
        if (current.data == data) {
            return true;
        }
        current = current.next;
    }
    return false;
}
3.4.2 从后往前查找
public boolean containsReverse(int data) {
    DoubleListNode current = head;
    while (current != null && current.next != null) {
        current = current.next;
    }
    while (current != null) {
        if (current.data == data) {
            return true;
        }
        current = current.prev;
    }
    return false;
}

3.5 双链表的时间复杂度

  • 插入和删除操作的时间复杂度为O(n),其中n是链表的长度。
  • 查找操作的时间复杂度仍然为O(n),但从后往前查找的效率更高。

双链表适用于需要双向遍历的场景,以及需要在某些情况下从后往前查找元素的场景。

4. 循环链表

4.1 循环链表的基本结构

循环链表是一种特殊的链表,它的尾节点指向头节点,形成一个闭环。以下是循环链表节点的定义:

class CircularListNode {
    int data;
    CircularListNode next;
    
    public CircularListNode(int data) {
        this.data = data;
        this.next = this;
    }
}

循环链表通常用于模拟循环队列等数据结构。以下是循环链表的基本操作。

4.2 循环链表的插入操作

循环链表的插入操作与单链表类似,但需要考虑尾节点的特殊情况。

4.2.1 在链表头部插入新节点
public void insertAtBeginning(int data) {
    CircularListNode newNode = new CircularListNode(data);
    if (head == null) {
        head = newNode;
    } else {
        newNode.next = head.next;
        head.next = newNode;
    }
}
4.2.2 在链表尾部插入新节点
public void insertAtEnd(int data) {
    CircularListNode newNode = new CircularListNode(data);
    if (head == null) {
        head = newNode;
    } else {
        newNode.next = head.next;
        head.next = newNode;
        head = newNode;
    }
}
4.2.3 在指定位置插入新节点
public void insertAtIndex(int data, int index) {
    if (index < 0) {
        throw new IllegalArgumentException("Invalid index");
    }
    CircularListNode newNode = new CircularListNode(data

);
    if (index == 0) {
        if (head == null) {
            head = newNode;
        } else {
            newNode.next = head.next;
            head.next = newNode;
        }
    } else {
        CircularListNode current = head;
        int i = 0;
        while (i < index && current != null) {
            current = current.next;
            i++;
        }
        if (current == null) {
            throw new IndexOutOfBoundsException("Index out of range");
        }
        newNode.next = current.next;
        current.next = newNode;
    }
}

4.3 循环链表的删除操作

循环链表的删除操作与单链表类似,但需要特别处理尾节点的引用。

4.3.1 删除链表头节点
public void deleteAtBeginning() {
    if (head != null) {
        if (head.next == head) {
            head = null;
        } else {
            head.next = head.next.next;
        }
    }
}
4.3.2 删除链表尾节点
public void deleteAtEnd() {
    if (head != null) {
        CircularListNode current = head;
        while (current.next != head) {
            current = current.next;
        }
        if (current == head) {
            head = null;
        } else {
            current.next = head.next;
            head = current;
        }
    }
}
4.3.3 删除指定位置的节点
public void deleteAtIndex(int index) {
    if (index < 0 || head == null) {
        return;
    }
    if (index == 0) {
        if (head.next == head) {
            head = null;
        } else {
            head.next = head.next.next;
        }
        return;
    }
    CircularListNode current = head;
    int i = 0;
    while (i < index - 1 && current != null) {
        current = current.next;
        i++;
    }
    if (current == null) {
        return;
    }
    current.next = current.next.next;
}

4.4 循环链表的查找操作

循环链表的查找操作与单链表类似,可以从头节点开始遍历。

public boolean contains(int data) {
    if (head == null) {
        return false;
    }
    CircularListNode current = head;
    do {
        if (current.data == data) {
            return true;
        }
        current = current.next;
    } while (current != head);
    return false;
}

4.5 循环链表的时间复杂度

  • 插入和删除操作的时间复杂度为O(n),其中n是链表的长度。
  • 查找操作的时间复杂度也为O(n)。

循环链表适用于需要循环操作的场景,如模拟循环队列等。

5. 常见问题

5.1 链表与数组的比较

链表和数组都是常见的数据结构,它们各自有自己的优点和缺点。链表适用于需要频繁插入和删除元素的场景,因为插入和删除操作的时间复杂度为O(1),而数组的时间复杂度为O(n)。但链表的查找操作相对较慢,时间复杂度为O(n),而数组的查找操作时间复杂度为O(1)。因此,在选择数据结构时,需要根据具体的应用场景来决定使用链表还是数组。

5.2 如何避免链表中的内存泄漏?

内存泄漏是一种常见的问题,特别是在使用链表等动态数据结构时。为了避免链表中的内存泄漏,可以采取以下几种措施:

  • 确保及时释放不再使用的节点,尤其是在删除节点时。
  • 使用弱引用(WeakReference)来引用链表中的节点,以便在不再被引用时能够被垃圾回收。
  • 定期检查链表中的节点,识别不再需要的节点并进行释放。

5.3 如何判断链表中是否存在环?

要判断链表中是否存在环,可以使用快慢指针法。具体步骤如下:

  1. 初始化两个指针,一个慢指针(slow)每次移动一个节点,一个快指针(fast)每次移动两个节点。
  2. 如果链表中存在环,快指针和慢指针最终会相遇。
  3. 如果链表中不存在环,快指针将会先到达链表的末尾(即快指针为null)。

以下是Java代码示例:

public boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) {
        return false;
    }
    
    ListNode slow = head;
    ListNode fast = head.next;
    
    while (slow != fast) {
        if (fast == null || fast.next == null) {
            return false;
        }
        slow = slow.next;
        fast = fast.next.next;
    }
    
    return true;
}

6. 总结

本文详细介绍了Java中链表的各种类型,包括单链表、双链表和循环链表,以及它们的基本操作和时间复杂度。链表是一种常见的数据结构,具有灵活的插入和删除操作,适用于不同的应用场景。选择合适的链表类型取决于具体的需求,需要根据应用的特点来做出决策。

你可能感兴趣的:(不知道有什么意义的专栏,数据结构,java,链表)