哨兵在鏈表中的使用

      首先我想寫這篇文章的原因是我用google搜索,但是沒有找到很多有關哨兵在鏈表中的使用,如果有的話,也只是輕描淡寫的寫了寫代碼,沒有明確說明使用哨兵的好處,這篇文章說明了用哨兵實現有序和無序鏈表的好處。

首先定義一個節點的結構.

struct Node {
    int value;
    Node* next;
    Node() : value(0), next(0) {}
    Node(int v, Node* n) : value(v), next(n) {}
};

     通常我們寫鏈表插入函數的時候,要分兩種情況:

     (1)鏈表頭爲空 

     (2)鏈表頭不爲空

     如果爲空的話,函數就需要改變Head指向的節點,因此,如果insert函數如果有head參數的數的話,那就得必須是head的引用(Node* &head)或指向head的指針(Node** head),這樣才能處理鏈表爲空的情況(當然,如果你把head指針設置爲全局的話,那就可以不必傳參了,一般也不會這樣做,否則要爲每個鏈表寫個insert函數),如果不用哨兵的話,通常的代碼是這樣的。

bool insert(Node** head, int value) {
    if (*head == NULL) {
        *head = new Node(value, 0);
        return true;
    }
    Node** current = head;
    while ((*current)->next != NULL) {
        current = &(*current)->next;
    }
    (*current)->next = new Node(value, 0);
    return true;
}

開頭你就得判斷head是不是爲NULL,當要插入的數據很多的時候,這無疑是一筆很大的開銷。如果不爲空的話,傳入的head也就可以不必爲引用了,上面的例子是鏈表爲空和鏈表不爲空了都是用引用(這裏就不區別指針和引用了)處理的。看下面不用head引用來處理鏈表非空情況:

bool insert(Node** head, int value) {
    if (*head == NULL) {
        *head = new Node(value, 0);
        return true;
    }
    Node* current = *head;
    while (current->next != NULL) {
        current = current->next;
    }
    current->next = new Node(value, 0);
    return true;
}

這裏之所以行得通,是因爲    current->next = new Node(value, 0)  , 這種形式,如果while循環中的條件改爲  while (current != NULL)  ,後面改爲  current = new Node(value, 0)   這樣就不行了(其實很多人還是會寫出這種代碼的,我也寫過,結果就是找不出問題在哪裏)。但是鏈表非空用head引用去這樣處理是沒問題的,也就是while條件爲  while(*current != NULL)   ,後面  *current = new Node(value, 0)  ,原因是因爲這裏是引用,可以改變next指針的值。對於指針可能這裏還是比較難理解,舉個例子吧:

    int* a = new int(2);
    int* b = a;
    b = new int(3);

這個a地址存放的值是2,而不是3,沒有改變a地址存放的值

    int* a = new int(2);
    int** b = &a;
    *b = new int(3);

這個a地址存放的值就是3,而不是原來的2了,懂了這個,也就可以明白上面那個是怎麼回事了。

      扯了好久,都沒有扯上哨兵,這裏引入哨兵的目的就是要解決每次判斷head是否爲空的問題,這樣想吧,如果我讓一個空的鏈表含有一個元素(哨兵元素),這樣是不就不用判斷head爲空的情況了,那麼哨兵的初始值爲多少好呢?如果是無序的鏈表的話,當然就不用考慮哨兵初始值的問題,插入到隊尾就行了(如果無序的話,插入到開始不是更好嗎?我也不知道原因,這裏就用鏈表看作是隊列吧,隊列就是插入到尾部)。看代碼

bool insert(Node* head, int value) {
    while (head->next != NULL) {
        head = head->next;
    }
  head->next = new Node(value, 0);
    return true;
}

這樣head也就不需要傳引用了,但是初始化head的時候必須讓他指向一個哨兵節點,如果爲head指向爲空insert會崩掉。

     下面在說說有序鏈表的問題,有序鏈表的話,插入新節點的位置就不一定是在末尾了,可能在頭部,中間,尾部,似乎要分很多情況,我自己嘗試着不用哨兵節點,結果開頭的空指針判斷不可少,while循環還得加一個判斷條件類似  (current->next != NULL && current->value < value)  這樣子,這樣的while循環多了一個條件無疑增加了開銷,這時哨兵的可以派上用場了,一開始我就讓空的鏈表含有兩個元素分別是:Node* MAX = new Node(10000, 0),  Node* MIN = new Node(-10000, MAX), (用10000表示數很大), head = MIN。insert函數爲:

bool insert(Node* head, int value) {
    Node* current = head;
    Node* pre = head;
    while (current->value < value) {
        pre = current;
        current = current->next;
    }
  pre->next = new Node(value, current);
    return true;
}

這樣的代碼可謂是簡潔明瞭吧,這就是哨兵的好處。如果你要寫print函數的話,根據你用的哨兵的情況的來寫就好了。

其實這種尾部放哨兵的方法還可以用於數組,這裏也附帶說一下,如果要在含n個數的數組array中找個數value,一般是這樣寫:

for (int i = 0; i < n; ++i) {
    if (value == array[i])
        return i;
}

用了哨兵的方式爲:令 array[n] = value;然後代碼爲:

for (int i = 0;; i++) {
    if (value == array[i]) {
        if (i != n)
            return i;
    }
}

好處在哪顯而易見。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章