JavaScript數據結構之鏈表--設計

上一篇文章中介紹了幾種常見鏈表的含義,今天介紹下如何寫出正確的鏈表代碼。node


如何表示鏈表

咱們通常設計的鏈表有兩個類。Node 類用來表示節點,LinkedList 類提供了一些輔助方法,好比說結點的增刪改查,以及顯示列表元素等方法。 接下來看看如何用 js 代碼表示一個鏈表。git

代碼演示:github

{
  var Node = function(data) {
    this.data = data;
    this.next = null;
  };
  var node1 = new Node(1);
  var node2 = new Node(2);
  var node3 = new Node(3);

  node1.next = node2;
  node2.next = node3;
  console.log(node1.data);
  console.log(node1.next.data);
  console.log(node1.next.next.data);
}
複製代碼

Node 類包含兩個屬性:data 用來保存節點上的數據,next 用來保存指向下一個節點的連接。算法

{
    var LList = function() {
        this.head = new Node('head');
        this.find = find;
        this.insert = insert;
        this.remove = remove;
        this.display = display;
    }
}
複製代碼

LList 類提供了對鏈表進行操做的方法。該類中使用一個 Node 類來保存鏈表中的頭結點。當有新元素插入時,next 就會指向新的元素。bash

怎麼樣,簡單吧,你已經學會鏈表了數據結構

但,這只是基本的鏈表表示法,水還很深。app

注意事項

把上一篇文章中的單鏈表圖搬過來,方便參考post

重點一:理解指針或引用的含義

這裏的指針或者引用,他們的意思都是同樣的,都是存儲所指對象的內存地址。this

將某個變量賦值給指針,實際上就是將某個變量的地址賦值給指針,或者反過來講,指針中存儲了這個變量的內存地址,指向了這個變量,經過指針就能找到這個變量。spa

看下面的僞代碼表示什麼意思:

p -> next = q;
複製代碼

這行代碼就是說 p 結點中的 next 指針存儲了 q 結點的內存地址。

再看下面的代碼表示什麼:

p -> next = p -> next -> next;
複製代碼

這行代碼表示,p 結點的 next 指針存儲了 p 結點的下下一個結點的內存地址。

如今應該能有所體會指針或者引用的概念了吧。

重點二:警戒指針丟失和內存泄露

在寫鏈表代碼的時候,尤爲是咱們的指針,會不斷的改變,指來指去的。因此在寫的時候,必定注意不要弄丟了指針。

以下圖中單鏈表的插入操做

咱們但願在結點 B 和相鄰的結點 C 之間插入 D 結點。假設當前指針 p 指向 B 結點。若是你將代碼實現成下面這個樣子,就會發生指針丟失和內存泄露。

// 僞代碼
p -> next = d; // 將 p 的 next 指針指向 D 結點;
d -> next = p -> next // 將 D 的結點的 next 指針指向 C? 結點。
複製代碼

咱們來分析下:p -> next 指針在完成第一步操做後,已經再也不指向結點 C 了,而是指向新增長的結點 D。第二行的代碼至關於將 D 賦值給 d->next,本身指向本身。所以,整個鏈表也就被截斷了。

因此咱們添加結點時,必定要注意操做的順序,要先將結點 D 的 next 指針指向結點 C,再把結點 B 的指針指向 D,這樣纔不會丟失指針。對於剛纔的那段代碼,你知道怎麼修改纔是正確的了吧。

重點三:重點留意邊界條件處理

首先來回顧下剛纔所說的單鏈表的插入操做。若是在 p 結點後面增長一個新的結點,只須要關注如下兩步便可。

new_node -> next = p -> next;
p -> next = new_node;
複製代碼

可是,當咱們向一個空鏈表中插入第一個結點時,就須要特殊處理了。當鏈表爲空時,也就是鏈表的head爲空,那直接賦值便可,以下:

if(head == null) {
    head = new_node;
}
複製代碼

看一段完整的添加節點代碼:

// 添加一個新結點 tail:表示尾結點
  append(data) {
    const node = new Node(data);
    if (!this.head) {
      this.head = node;
      this.tail = this.head;
    } else {
      this.tail.next = node;
      this.tail = node;
    }
  }
複製代碼

若是頭結點不存在的話,頭結點等於尾結點。若是頭結點存在的話,利用尾結點來擴充鏈表的數據,別忘了再移動 tail 成爲尾結點。

再來看單鏈表結點的刪除操做。若是在p結點後刪除一個結點,只須要關注一步便可:

p -> next = p -> next -> next;
複製代碼

可是,當鏈表中只剩一個結點head時,也須要特殊處理才能夠,以下:

if(head -> next == null){
    head = null;
}
複製代碼

刪除的代碼邏輯請查看 github-鏈表-remove

我提供的方法比較繁瑣,當閱讀了《數據結構與算法JavaScript描述》的時候,發現書上寫的方法特別簡潔。在此分享一下。

如下是此書中的刪除結點代碼:

{
/* 首先根據要刪除的元素,檢查每個結點的下一個結點中是否存儲着要刪除的數據。 若是找到,則返回該結點,即前一個結點。 */
  function findPvevious(item) {
    var currNode = this.head;
    while(!(currNode.next == null) && (currNode.next.element != item)) {
      currNode = currNode.next;
    }
    return currNode;
  }
 // 而後找到前一個結點後,利用上文提到的單鏈表刪除操做進行刪除。
  function remove(item) {
    var prevNode = this.findPvevious(item);
    if(!(prevNode.next == null)) {
      prevNode.next = prevNode.next.next;
    }
  }
}
複製代碼

因此寫鏈表代碼時,要常常注意邊界條件是否考慮到了:

  • 若是鏈表爲空時,代碼是否能正常工做?
  • 若是鏈表只有一個結點時,代碼是否能正常工做?
  • 若是在處理頭結點和尾結點時,代碼是否能正常工做?

重點四:要學會畫圖輔助思考

好比一些單鏈表的增刪改查等,指針老是不斷的改變。這個時候若是大腦思考不過來的話,能夠簡單的畫個示意圖輔助一下。好比說單鏈表的增長結點操做,能夠畫出增長先後的鏈表變化。

看圖寫代碼,是否是會比較清楚指針接下來的指向呢。

代碼演示

請前往 github 查看 鏈表常見的方法

參考書籍

《數據結構與算法JavaScript描述》

有你才完美

鏈表這種數據結構,確實比較容易懂,可是想寫出好相關的操做代碼,確實不易,指針或者引用也是個人薄弱環節,指來指去便記不清該怎麼指啦!動物的關節對於動物來說很是的重要,指針感受就是鏈表的關節,鏈表像一輛火車,每一個車箱的鏈接全靠車箱間的鏈接軸。

傳送門

  1. JavaScript數據結構之棧
  2. JavaScript數據結構之隊列
  3. JavaScript 數據結構之隊棧互搏
  4. JavaScript數據結構之鏈表--介紹
  5. JavaScript 算法之複雜度分析
  6. JavaScript 算法之最好、最壞時間複雜度分析
相關文章
相關標籤/搜索