首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 單向鏈表手寫實現:從節點定義到增刪查遍歷完整教學

     編輯:Java 程式設計入門

資料結構基礎

單向鏈表由節點組成,每個節點包含:

  • 資料域:存儲元素值
  • 指針域:指向下一個節點(尾節點指向 null)

節點類定義

public class Node {
    E data;
    Node next;
    
    public Node(E data) {
        this.data = data;
    }
    
    public Node(E data, Node next) {
        this.data = data;
        this.next = next;
    }
}

鏈表核心操作實現

public class SinglyLinkedList {
    private Node head;  // 虛擬頭節點(哨兵),簡化邊界處理
    private int size;
    
    public SinglyLinkedList() {
        head = new Node<>(null);  // 哨兵節點不存儲數據
        size = 0;
    }
    
    // 獲取長度
    public int size() { return size; }
    public boolean isEmpty() { return size == 0; }
    
    // 索引合法性檢查
    private void checkIndex(int index) {
        if (index < 0 || index >= size) 
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
    }
    private void checkIndexForAdd(int index) {
        if (index < 0 || index > size) 
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
    }
    
    // 獲取 index 位置的前驅節點(哨兵頭節點索引為 -1)
    private Node getPrevNode(int index) {
        Node prev = head;
        for (int i = -1; i < index; i++) {
            prev = prev.next;
        }
        return prev;
    }
    
    // 在 index 處插入元素
    public void add(int index, E element) {
        checkIndexForAdd(index);
        Node prev = getPrevNode(index - 1);
        prev.next = new Node<>(element, prev.next);
        size++;
    }
    
    // 尾部新增
    public void add(E element) {
        add(size, element);
    }
    
    // 獲取 index 位置元素
    public E get(int index) {
        checkIndex(index);
        return getPrevNode(index).next.data;
    }
    
    // 設置 index 位置元素
    public E set(int index, E element) {
        checkIndex(index);
        Node node = getPrevNode(index).next;
        E old = node.data;
        node.data = element;
        return old;
    }
    
    // 刪除 index 位置元素
    public E remove(int index) {
        checkIndex(index);
        Node prev = getPrevNode(index - 1);
        Node removed = prev.next;
        prev.next = removed.next;
        removed.next = null;  // 幫助 GC
        size--;
        return removed.data;
    }
    
    // 查找元素首次出現索引
    public int indexOf(E element) {
        Node cur = head.next;
        for (int i = 0; i < size; i++) {
            if (Objects.equals(element, cur.data)) return i;
            cur = cur.next;
        }
        return -1;
    }
    
    // 清空
    public void clear() {
        head.next = null;
        size = 0;
    }
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        Node cur = head.next;
        while (cur != null) {
            sb.append(cur.data);
            if (cur.next != null) sb.append(", ");
            cur = cur.next;
        }
        sb.append(']');
        return sb.toString();
    }
}

測試驗證

public class LinkedListTest {
    public static void main(String[] args) {
        SinglyLinkedList list = new SinglyLinkedList<>();
        
        // 新增測試
        list.add(10);
        list.add(20);
        list.add(30);
        list.add(1, 15);  // [10, 15, 20, 30]
        System.out.println("插入後: " + list);
        
        // 獲取測試
        System.out.println("索引 2: " + list.get(2));  // 20
        
        // 修改測試
        list.set(1, 99);  // [10, 99, 20, 30]
        System.out.println("修改後: " + list);
        
        // 刪除測試
        list.remove(2);   // [10, 99, 30]
        System.out.println("刪除後: " + list);
        
        // 查找測試
        System.out.println("99 索引: " + list.indexOf(99));  // 1
        System.out.println("100 索引: " + list.indexOf(100)); // -1
        
        // 邊界測試
        list.add(0, 1);   // 頭部插入
        list.add(list.size(), 999); // 尾部插入
        System.out.println("邊界插入: " + list);
    }
}

進階話題

遞歸實現(教學用,生產慎用)

// 遞歸反轉
public void reverse() {
    head.next = reverseRecursive(head.next);
}

private Node reverseRecursive(Node node) {
    if (node == null || node.next == null) return node;
    Node newHead = reverseRecursive(node.next);
    node.next.next = node;
    node.next = null;
    return newHead;
}

快慢指針技巧

  • 找中點:快指針走 2 步,慢指針走 1 步
  • 檢測環:快慢指針相遇即有環
  • 倒數第 k 個:快指針先走 k 步

效能對比:ArrayList vs LinkedList

生產環境建議

  1. 99% 場景用 ArrayList:現代 CPU 緩存讓連續記憶體存取極快
  2. 真正需要 LinkedList 時:極頻繁頭部操作、不可預知大小且記憶體受限、需實現隊列/棧且不想用 ArrayDeque
  3. 隊列/棧首選:ArrayDeque(雙端隊列、陣列實現、無容量限制、非同步、效能優於 LinkedList)

總結

手寫鏈表是理解指針、引用、邊界條件、遞歸的最佳練習。**哨兵節點(虛擬頭節點)是簡化邊界邏輯的關鍵技巧**。生產代碼請直接使用 JDK 集合框架,但面試與底層原理理解請務必親手實現一次。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
操作ArrayListLinkedList
隨機存取 get(i)O(1) ⭐O(n)
頭部插入/刪除O(n)O(1) ⭐
尾部插入/刪除O(1) 擴容均攤O(1) ⭐
中間插入/刪除O(n) 移動元素O(n) 定位 + O(1) 操作
記憶體開銷低(連續陣列)高(節點對象 + 引用)
緩存友好度極高低