js數據結構與算法--雙向鏈表的實現

雙向鏈表也叫雙鏈表,是鏈表的一種,它的每一個數據節點中都有兩個指針,分別指向直接後繼和直接前驅。因此,雙向鏈表中的任意一個節點開始,均可以很方便的訪問它的前驅節點和後繼節點。javascript

雙向鏈表結構圖

雙向鏈表的實現

linkednode.js ,裏面使用了類的繼承extends,使用了super函數。java

/**

 * 鏈表節點,鏈表中的項,鏈表中的節點

 */

export class Node {

  constructor(element, next = null) {

    this.element = element // 鏈表中節點的值

    this.next = next // 指向列表中下一個節點項的指針

  }

}



export class DoublyNode extends Node {

  constructor(element, next = null, prev = null) {

    super(element, next)

    this.prev = prev

  }

}

doublyLinkedList.js 雙向鏈表類,實現了各個功能,功能說明,都在代碼註釋中node

import {

  DoublyNode

} from './linkednode'

/**

 * 雙向鏈表類

 */

export class DoublyLinkedList {

  constructor() {

    /**

     * 鏈表長度

     */

    this.length = 0

    /**

     * 頭指針

     */

    this.head = null

    /**

     * 尾指針

     */

    this.tail = null

  }

  /**

   * 在鏈表末尾添加元素

   * @param {*} element 須要插入的元素

   */

  append(element) {

    let node = new DoublyNode(element)

    if (!this.head) {

      this.head = node

      this.tail = node

    } else {

      this.tail.next = node

      node.prev = this.tail

      this.tail = node

    }

    this.length++

      return true

  }

  /**

   * 在任意位置插入元素

   * @param {Int32Array} position 指定位子

   * @param {*} element 須要插入的元素

   */

  insert(position, element) {

    // 檢查越界值

    if (position >= 0 && position <= this.length) {

      // 實例化一個雙向鏈表的節點

      let node = new DoublyNode(element)

      // 賦初始值

      let current = this.head

      let previous

      let index = 0 // 位置索引



      if (position === 0) { // 在第一個位子添加

        if (!this.head) { // 鏈表無數據的時候,將head和tail都指向新元素

          this.head = node

          this.tail = node

        } else { // 鏈表有數據的時候, head node current

          node.next = current

          current.prev = node

          this.head = node

        }

      } else if (position === this.length) { // 添加到最後一項 current node

        current = this.tail

        current.next = node

        node.prev = current

        this.tail = node

      } else { // 在列表中間位置添加

        // 新鏈表的節點原型是: previous <---> node <---> current

        while (index++ < position) { // 位置索引遞增到指定點以前,找出先後兩個節點

          previous = current // 當前節點設置爲新鏈表中要插入的節點的前一個元素。

          current = current.next // 當前節點以後的元素設置爲新鏈表中要插入的節點的當前元素

        }

        node.next = current

        previous.next = node



        current.prev = node

        node.prev = previous

      }

      this.length++ // 更新列表的長度

      return true

    } else {

      return false

    }

  }



  /**

   * 在任意位置插入元素

   * 在鏈表頭,在鏈表尾,在鏈表前半段,在鏈表後半段

   * @param {Int32Array} position 指定位置

   * @param {*} element 須要插入的元素

   */

  insert_up(position, element) {

    let node = new DoublyNode(element)

    let previous

    let current = this.head



    if (position > -1 && position <= this.length) {

      if (position === 0) {

        if (!this.head) {

          this.head = node

          this.tail = node

        } else {

          node.next = current

          current.prev = node

          this.head = node

        }

      } else if (position === this.length) {

        current = this.tail

        current.next = node

        node.prev = current

        this.tail = node

      } else if (position < this.length / 2) { // 目標在鏈表前半段

        let index = 0

        // 0 1 2 [] 3 4 5 

        while (index++ < position) {

          previous = current

          current = current.next

        }

        previous.next = node

        node.next = current



        node.prev = previous

        current.prev = node

      } else { // 目標在鏈表的後半段

        // 0 1 2 3 4 | 5 6 [] 7 8 9

        let index = this.length

        current = this.tail

        while (index-- > position) {

          previous = current.prev

          current = current

        }

        previous.next = node

        node.next = current



        node.prev = previous

        current.prev = node

      }

      this.length++

        return true

    } else {

      // 若是超出範圍,直接添加到鏈表末尾

      let current = this.tail

      current.next = node

      node.prev = current

      this.tail = node

      this.length++

        return true

    }

  }



  /**

   * 從任意位置移除元素,返回移除的元素

   * 從頭部,從尾部,從鏈表的前半段,從鏈表的後半段

   * @param {*} position 位置索引

   */

  removeAt(position) {

    let current = this.head // 當前項

    let previous // 前一項

    let index = 0 // 索引

    // 越界檢查

    if (position > -1 && position < this.length) {

      if (position === 0) { // 第一項

        this.head = current.next

        // 若是是最後一項要刪除,將tail置爲null,此時head也爲null

        // 若是非最後一項,則將this.head.prev置爲null 

        if (this.length === 1) { // 只有一項的狀況,更新tail

          this.tail = null

        } else {

          this.head.prev = null // 將首項的prev置空 或者 current.next.prev = null 

        }

      } else if (position === this.length - 1) { // 最後一項

        current = this.tail

        previous = current.prev

        this.tail = previous

        this.tail.next = null

      } else if (position <= this.length / 2) { // 索引在鏈表前半段,分開計算,提高性能

        while (index++ < position) {

          previous = current

          current = current.next

        }

        // 將previous與current下一項連起來---跳過current

        previous.next = current.next

        current.next.prev = previous

      } else { // 索引在鏈表後半段

        index = this.length - 1

        current = this.tail

        while (index-- > position) {

          previous = current

          current = current.prev

        }

        // 將previous與current的上一項連起來--跳過current

        previous.prev = current.prev

        current.prev.next = previous

      }

      this.length--

        return current.element

    } else {

      // 超出鏈表安全長度,鏈表有數據,則刪除末尾元素

      if (typeof position === 'number' && this.length > 0) {

        let current = this.tail

        this.tail = current.prev

        this.tail.next = null

        this.length--

          return current.element

      } else {

        return null

      }

    }



  }



  /**

   * 從列表中移除一項

   * 先找出元素的索引項,再根據索引移除元素

   * @param {*} element 列表中的元素

   */

  remove(element) {

    let index = this.indexOf(element)

    return this.removeAt(index)

  }



  /**

   * 返回元素在列表中的索引。若是列表中沒有該元素則返回-1

   * @param {*} element 元素

   */

  indexOf(element) {

    let current = this.head

    let index = 0 // 計算位置數

    while (current) {

      if (element === current.element) {

        return index

      }

      index++

      current = current.next

    }

    return -1

  }



  /**

   * 判斷是否爲空鏈表

   * 空鏈表返回true,非空(鏈表長度大於0)返回false

   */

  isEmpty() {

    return this.size() === 0

  }



  /**

   * 返回鏈表包含的元素個數。與數組的length屬性相似

   */

  size() {

    return this.length

  }



  /**

   * 獲取鏈表的表頭節點

   */

  getHead() {

    return this.head

  }



  /**

   * 獲取鏈表的尾節點

   */

  getTail() {

    return this.tail

  }

  /**

   * 輸出元素的值

   */

  toString() {

    let current = this.head

    let string = 'null'



    while (current) {

      string += "<--->" + current.element + (current.next ? '' : '<--->null')

      current = current.next

    }

    return string

  }

}

以上方法,通過JEST框架測試經過,歡迎查看源碼git

思考

雙向鏈表與單項鍊表的比較:github

  1. 雙向鏈表能夠雙向遍歷。從頭至尾,或者從尾到頭數組

  2. 雙向鏈表能夠訪問一個特定節點的下一個或者前一個元素,而單鏈表只能訪問下一個元素。安全

  3. 雙向鏈表內存佔用比單鏈表的多app

鏈表還有一個雙向循環鏈表,在須要用到的時候,考慮它們各自的不一樣,選擇合適的鏈表來操做。框架

相關文章
相關標籤/搜索