1. 程式人生 > >關於連結串列中哨兵結點問題的深入剖析

關於連結串列中哨兵結點問題的深入剖析

最近正在學習UC Berkeley的CS61B這門課,主要是採用Java語言去實現一些資料結構以及運用資料結構去做一些project。這門課不僅告訴你這個東西怎麼做,而且一步一步探尋為什麼要這樣做以及為什麼會有這些功能。我們有時在接觸某段程式碼或功能的實現時,可能直接就看到了它最終的面貌,而不知道如何一步步演化而來,其實每一個功能的新增或優化都是對應一個問題的解決。下面就這門課中關於連結串列中哨兵結點的相關問題進行總結。

什麼是哨兵結點

哨兵顧名思義有巡邏、檢查的功能,在我們程式中通過增加哨兵結點往往能夠簡化邊界條件,從而防止對特殊條件的判斷,使程式碼更為簡便優雅,在連結串列中應用最為典型。

單鏈表中的哨兵結點

首先討論哨兵結點在單鏈表中的運用,如果不加哨兵結點在進行頭尾刪除和插入時需要進行特殊判斷。比如在尾部插入結點的程式碼如下:

void addLast(int x) {
    if (first == null) {
        first = new Node(x, null);
        return;
    }
    Node p = first;
    while (p.next != null) {
        p = p.next;
    }
    p.next = new Node(x, null);
}

如上所示需要對結點為空的特殊情況進行判斷,頭部加了一個哨兵結點後就可以不需要判斷了(不會為空)

雙鏈表中的哨兵結點

Version 1: 雙哨兵

在雙鏈表中需要能夠在頭部和尾部分別進行插入刪除操作(可以實現雙端佇列),為了能快速在尾部進行插入刪除,需要引入指向尾部的指標。截圖如下(圖片來自CS61B)

上述增加了一個指向尾部的last結點,從上圖可以看出一個問題,last結點有時指向哨兵結點,有時指向實際結點。這會導致特殊情況的出現,比如在進行addFirst操作時,last指向哨兵結點時插入後需要將last往後移動一個,而第二張圖指向實際結點時在頭部插入結點後並不需要改變last指標。這時需要在尾部後也引入一個哨兵結點,以使其一致。相應示意圖如下:

Version 2:迴圈雙鏈表

上述Version1需要兩個哨兵結點,可以對其進行改進。可以使用頭部結點的prev指標指向尾部,尾部結點的next指標指向哨兵,這樣就只需要一個哨兵結點,使連結串列變成迴圈連結串列,比Version1更為簡潔優雅。

在對如上所示進行插入和刪除操作時一定要格外注意,自己在寫的時候很容易就漏掉某個指標的關係設定,最好在紙上自己畫一遍。(對於要改變的連線可能會影響其他的,這時可將其暫存或最好設定)

在頭部插入的程式碼如下:

   public void addFirst(Item item) {
        Node node = new Node(item);
        node.prev = sentinel;
        node.next = sentinel.next;
        sentinel.next.prev = node;
        sentinel.next = node;
        size++;
    }

尾部插入程式碼如下:

    public void addLast(Item item) {
        Node node = new Node(item);
        node.prev = sentinel.prev;
        node.next = sentinel;
        sentinel.prev.next = node;
        sentinel.prev = node;
        size++;
    }

頭部刪除程式碼如下:

   public Item removeFirst() {
        Item item = sentinel.next.item;
        sentinel.next = sentinel.next.next;
        sentinel.next.prev = sentinel;
        size--;
        return item;
    }

尾部刪除程式碼如下

    public Item removeLast() {
        Item item = sentinel.prev.item;
        Node sl = sentinel.prev.prev;
        sl.next = sl.next.next;
        sl.next.prev = sl;
        size--;
        return item;
    }

總結與感想

(1)雖然看起來很小很簡單的事情,但實現起來卻有很多細小問題可以考慮,學會把一件小事做的很漂亮。(small but smart)

(2)學會分析一個東西的來龍去脈,為什麼會有這部分,以及怎麼改進的。

參考:

2.演算法導論10.2連結串列