數據結構與算法複習-02-鏈表

本文首發於我的Github博客
本文是數據結構與算法複習的第二篇博文,複習

  • 鏈表的概念
  • 常見的鏈表類型和設計取捨
  • 鏈表的反轉操作

鏈表的概念

鏈表可以定義爲:

  • 擁有一個節點,該節點有兩個屬性
    • val,本節點的值
    • next,另一個鏈表

首先,這個定義是單鏈表的定義,但是雙向鏈表也是類似的

其次,從這個定義可以看到,鏈表是可以遞歸定義的

常見的鏈表類型和設計取捨

比較常見的鏈表類型有

  • 單鏈表
  • 雙向鏈表

所謂的設計取捨主要是考慮:

  • 選擇單鏈表還是雙向鏈表?
  • 是否需要衛士節點(sentinel node)?

單鏈表與雙向鏈表

雙向鏈表與單鏈表的區別就在於

  • 單鏈表每個節點只有一個next指向額外的鏈表(也就是後繼元素)
  • 雙鏈表每個節點有兩個屬性指向額外的鏈表(也就是前驅和後繼)

所以雙向鏈表的使用更加靈活

如何選擇

對於單鏈表和雙鏈表的選擇,主要是考慮兩點:

  • 空間,雙鏈表由於多了一個屬性,消耗空間更多
  • 時間,這裏是指
    • 如果有訪問前驅節點的需求,可以考慮
      • 雙鏈表
      • 如果需要訪問的前驅結點是固定的,比如固定是前一個節點或者前兩個節點
        • 就可以直接使用額外的一個節點指針進行存儲

是否需要衛士節點

衛士節點就是指

對於任意的鏈表,都向其中添加一個無用的節點,使得鏈表不會是0節點的鏈表

是否需要衛士節點主要是看個人的使用習慣

  • 使用衛士節點可以在一定程度上規避空指針的問題
    • 畢竟鏈表中一定會有節點
  • 但是會多出一個節點的空間消耗,且編程邏輯和無衛士節點的編程邏輯不同(廢話,當然不同)

鏈表的翻轉操作

鏈表考的最多的就是翻轉(淡然還有單鏈表的快排,不過那個可以留到排序的時候再說)

鏈表的翻轉有兩種實現:

  • 循環實現
  • 遞歸實現

其實就是邏輯煩了點,實在不行代碼背下來

循環實現鏈表翻轉

如下代碼的關鍵在於循環的不變式

每次循環開始前,head指向的是還未反轉的鏈表頭,prev指向的是已經反轉的鏈表頭

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

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null, next = null;
        while (head != null) {
            next = head.next;
            head.next = prev;
            prev = head;
            head = next;
        }
        return prev;
    }
}

遞歸實現鏈表翻轉

以下代碼的關鍵在於函數聲明

reverseList(headA, headB)會將headA的鏈表反轉,然後將headB的鏈表拼接到反轉的headA鏈表後

class Solution {
    public ListNode reverseList(ListNode head) {
        return reverseList(head, null);
    }

    private ListNode reverseList(ListNode headA, ListNode headB) {
        if (headA == null) {
            return headB;
        }
        ListNode next = headA.next;
        headA.next = headB;
        return reverseList(next, headA);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章