使用JS一步步理解並實現鏈表,帶你瞭解數據結構

在這裏插入圖片描述

使用JS一步步理解並實現鏈表

一、數組和鏈表優缺點

1.1、數組(Array)

1.1.1 數組的優點

線性表的一種。高級數據語言中,對數組內部的元素類型沒有嚴格的要求,這在語言中稱爲泛型,可以放入任何單元類型。數組的底層的硬件實現,存在一個內存管理器,每當申請一個數組的時候,計算機會在內存中開闢一段連續的地址,每一個地址可以通過內存管理器進行訪問,數組訪問第一個元素和其他任何一個元素的時間複雜度是相同的,都是O(1),即常數級別。由於數組可以隨機訪問任何一個元素,所以它的時間效率快,這是數組的優勢之一。

1.1.2 數組的缺點

數組的問題出現於它增加、刪除某些元素的時候。

比如現在有個數組,要在中間插入一個元素F,那麼元素C、D、E就要相應的向後移動一個位置,這樣一來數組插入操作的時間複雜度趨於O(1)-O(n)之間。 數組刪除也是同理。
在這裏插入圖片描述
所以在增加、刪除操作比較頻繁的情況下,數組的缺點就會顯露出來。

下面是數組中各個操作對應的時間複雜度:
在這裏插入圖片描述

1.2、鏈表(LinkedList)

1.2.1單鏈表

在這裏插入圖片描述

1.2.2 雙向鏈表

在這裏插入圖片描述

1.2.3 單循環鏈表

在這裏插入圖片描述

1.2.1 、鏈表的優點

相比於數組,鏈表在增加節點和刪除節點時候,並不會引起其他節點的羣移,這樣的話增加、刪除操作的時間複雜度爲O(1),下面是單鏈表插入某個節點的示意圖,我們可以看到只需要更改當前節點和前置節點和的next指針,即可完成節點的插入操作。
下面是單鏈表的節點插入操作示意圖:
在這裏插入圖片描述

1.2.2 、鏈表的缺點

與數組相比,在鏈表中訪問任一元素的位置,就沒那麼容易了,需要從鏈表的head開始,一步步的向後查詢,這種情況下時間複雜度爲O(1)-O(n)之間。 下面是鏈表中各個操作對應的時間複雜度:
在這裏插入圖片描述

1.2.3 、跳錶

由於鏈表的search操作時間複雜度爲O(n),爲了彌補鏈表的缺陷,我們可以思考給鏈表增加多個指針去作爲起始指針,這樣的話search某個節點就會更有效率,從而減少search的時間複雜度。

由此引出了跳錶的思想,而多個起始指針則晉升爲索引的概念,通過增加維度,以空間換時間來進行時間度優化,跳錶中search的時間複雜度爲O(logn)。
下面是跳錶中一級索引的示意圖:
在這裏插入圖片描述

二、使用JS實現鏈表

理解了鏈表的幾種通用形態,我們可以用js一步步實現鏈表這個數據結構。

2.1、單鏈表

實現單鏈表的原理在於,要不斷更新節點的next指針,使整個鏈表串聯起來。

class Node {
  constructor (element) {
    this.element = element
    this.next = null
  }
}

class LinkedList {

  constructor () {
    // 初始化鏈表長度
    this.length = 0
    // 初始化鏈表第一個節點
    this.head = null
  }

  append (element) {
    let node = new Node(element)
    let current
    // 鏈表爲空情況
    if (this.head === null) {
      this.head = node
    } else {
      current = this.head
      while (current.next) {
        current = current.next
      } 
      current.next = node
    }
    this.length ++
  }

  insert (element, point) {
    if (point >=0 && point <= this.length) {
      let node = new Node(element)
      let current = this.head
      let previous
      let index = 0
      if (point === 0) {
        node.next = current
        this.head = node
      } else {
        while (index++ < point) {
          previous = current
          current = current.next
        }
        previous.next = node
        node.next = current
      }
      this.length++
      return true
    } else {
      return false
    }
  }

  removeAt (point) {
    if (point > -1 && point < this.length) {
      let current = this.head
      let index = 0
      let previous
      if (point === 0) {
        this.head = current.next
      } else {
        while (index++ < point) {
          previous = current
          current = current.next
        }
        previous.next = current.next
      }
      this.length--
      return current.element
    } else {
      return null
    }
  }

  remove (element) {
    let index = this.find(element)
    // 刪除後返回已刪除的節點
    return this.removeAt(index)
  }

  find (element) {
    let current = this.head
    let index = 0
    if (element == current.element){
        return 0;
    }
    while (current.next) {
      if(current.element === element) {
        return index
      }
      index++
      current = current.next
    }
    if (element == current.element){
        return index;
    }
    return -1
  }

  isEmpty () {
    return this.length === 0
  }

  size () {
    return this.length
  }

  print () {
    let current = this.head
    let result = ''
    while (current) {
      result += current.element + (current.next ? '->' : '')
      current = current.next
    }
    return result
  }
}

let l1 = new LinkedList()
...


2.2、雙向鏈表

實現雙向鏈表的原理在於,每次更新鏈表要同時考慮到next和prev兩個指針,並保證更新指針的指向。

class Node {
  constructor (element) {
    this.element = element
    this.next = null
    this.prev = null
  }
}

class DoubleLinkedList {

  constructor () {
    this.length = 0
    this.head = null
    // 定義尾部節點
    this.tail = null
  }

  append (element) {
    let node = new Node(element)
    let tail = this.tail
    if (this.head === null) {
      this.head = node
      this.tail = node
    } else {
      tail.next = node
      node.prev = tail
      this.tail = node
    }
    this.length++
  }

  insert (element, point) {
    if(point >= 0 && point <= this.length) {
      let node = new Node(element)
      let current = this.head
      let tail = this.tail
      let index = 0
      let previous
      if (point === 0) {
        if (!this.head) {
          this.head = node
          this.tail = node
        } else {
          node.next = current
          current.prev = node
          this.head = node
        }
      } else if (point === this.length) {
        current = tail
        current.next = node
        node.prev = current
        this.tail = node
      } else {
        while (index++ < point) {
          previous = current
          current = current.next
        }
        // 將原來的鏈表斷開,重新使用指針串接起來
        node.next = current
        node.prev = previous
        previous.next = node
        current.prev = node
      }
      this.length++
      return true
    } else {
      return false
    }
  }

  removeAt (point) {
    if (point > -1 && point < this.length) {
      let current = this.head
      let index = 0
      let previous
      let tail = this.tail
      if (point === 0) {
        // remove第一項的情況
        this.head = current.next
        if (this.length === 1) {
          this.tail = null
        } else {
          this.head.prev = null
        }
      } else if (point === this.length -1) {
        current = tail
        this.tail = current.prev
        this.tail.next = null
      } else {
        while (index++ < point) {
          previous = current
          current = current.next
        }
        previous.next = current.next
        current.next.prev = previous
      }
      this.length--
      return current.element
    } else {
      return null
    }
  }

  find (element) {
    let current = this.head
    let index = 0
    if (element == current.element){
        return 0;
    }
    while (current.next) {
      if(current.element === element) {
        return index
      }
      index++
      current = current.next
    }
    // 爲了保證最後一位被找到
    if (element == current.element){
        return index;
    }
    return -1
  }

  remove (element) {
    let index = this.find(element)
    return this.removeAt(index)
  }

  isEmpty () {
    return this.length === 0
  }

  size () {
    return this.length
  }

  print () {
    let current = this.head
    let result = ''
    while (current) {
      result += current.element + (current.next ? '->' : '')
      current = current.next
    }
    return result
  }

}

let l1 = new DoubleLinkedList()


2.3、單向循環鏈表

單向循環鏈表和單鏈表大致相同,唯一區別是,尾節點tail的next指針要指向head,使鏈表的頭尾串聯在一起,形成循環。

2.4、雙向循環鏈表

雙向循環鏈表和單向循環原理上大概一致,區別在於,雙向循環鏈表同時擁有2個指針prev和next,並在head和tail兩個臨界點進行指針更新處理,並保持鏈表的首尾相連。

class Node {
  constructor (element) {
    this.element = element
    this.next = null
  }
}

class CircleLinkedList {

  constructor () {
    // 初始化鏈表長度
    this.length = 0
    // 初始化鏈表第一個節點
    this.head = null
  }

  append (element) {
    let node = new Node(element)
    let head = this.head
    let current
    // 鏈表爲空情況
    if (this.head === null) {
      this.head = node
    } else {
      current = this.head
      while (current.next && current.next !== head) {
        current = current.next
      } 
      current.next = node
    }
    // 保持首尾相連
    node.next = head
    this.length ++
  }

  insert (element, point) {
    if (point >=0 && point <= this.length) {
      let node = new Node(element)
      let current = this.head
      let previous
      let index = 0
      if (point === 0) {
        node.next = current
        while (current.next && current.next !== this.head) {
          current = current.next
        }
        this.head = node
        current.next = this.head
      } else {
        while (index++ < point) {
          previous = current
          current = current.next
        }
        previous.next = node
        // 首尾相連
        node.next = current === null ? head : current
      }
      this.length++
      return true
    } else {
      return false
    }
  }

  removeAt (point) {
    if (point > -1 && point < this.length) {
      let current = this.head
      let index = 0
      let previous
      if (point === 0) {
        this.head = current.next
        while (current.next && current.next !== this.head) {
          current = current.next
        }
        current.next = this.head
      } else {
        while (index++ < point) {
          previous = current
          current = current.next
        }
        previous.next = current.next
      }
      this.length--
      return current.element
    } else {
      return null
    }
  }

  remove (element) {
    let index = this.find(element)
    // 刪除後返回已刪除的節點
    return this.removeAt(index)
  }

  find (element) {
    let current = this.head
    let index = 0
    if (element == current.element){
        return 0;
    }
    while (current.next && current.next !== this.head) {
      if(current.element === element) {
        return index
      }
      index++
      current = current.next
    }
    if (element == current.element){
        return index;
    }
    return -1
  }

  isEmpty () {
    return this.length === 0
  }

  size () {
    return this.length
  }

  print () {
    let current = this.head
    let result = ''
    while (current.next && current.next !== this.head) {
      result += current.element + (current.next ? '->' : '')
      current = current.next
    }
    result += current.element
    return result
  }
}

let l1 = new CircleLinkedList()


三、小結

以上是我對鏈表數組相關數據結構的淺薄認知,如有紕漏,還望指出~~
🍺🍺🍺🍺🍺
在這裏插入圖片描述

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