學習數據結構與算法之鏈表

本系列全部文章:
第一篇文章:學習數據結構與算法之棧與隊列
第二篇文章:學習數據結構與算法之鏈表
第三篇文章:學習數據結構與算法之集合
第四篇文章:學習數據結構與算法之字典和散列表
第五篇文章:學習數據結構與算法之二叉搜索樹javascript

簡單介紹鏈表

鏈表一種常見的數據結構,能夠存儲有序的元素集合。不一樣於數組,鏈表中元素在內存中不是連續放置,同時每個鏈表元素中除了自己的節點還保存了指向下一個元素的引用,這些特色使得鏈表元素在動態增長和刪除時沒必要移動其餘元素,可是訪問鏈表元素時必須從起點開始迭代列表直到找到目標元素。java

下面是兩種鏈表的JavaScript實現:node

單向鏈表

下圖就是一個典型的單向鏈表,這裏注意:通常鏈表中的第一個元素會有一個head指針指向它,這也是咱們操做鏈表必不可少的一環。git

單向鏈表

(圖片來源谷歌,侵刪)github

用JavaScript實現單向鏈表

與以前實現棧和隊列同樣,這裏先聲明一個構造函數:算法

function LinkedList () {
  var Node = function (element) {
    this.element = element
    // 保存指向下個元素的引用,默認爲null
    this.next = null
  }
  
  // 鏈表長度
  var length = 0
  // head保存指向第一個元素的引用
  var head = null
}

鏈表須要實現如下方法:segmentfault

  • append(element):向鏈表尾部添加元素
  • insert(position, element):向鏈表特定位置插入元素
  • removeAt(position):從鏈表特定位置移除一項
  • remove(element):在鏈表中移除某元素
  • indexOf(element):返回元素在鏈表中的索引,若不存在則返回-1
  • isEmpty():若是鏈表不包含任何元素就返回true,不然爲false
  • size():返回鏈表長度
  • toString():返回元素的值轉成字符串

實現append

相似數組的push方法,可是隻能添加一個元素。實現方法的時候分兩種狀況考慮:1. 鏈表爲空時添加第一個元素;2. 鏈表不爲空時在尾部添加元素數組

this.append = function (element) {

  var node = new Node(element),
      current

  if (head === null) { // 當鏈表爲空時
    // 將head指向新增的元素
    head = node
  } else { // 鏈表不爲空
    // 使用一個current變量從head開始迭代鏈表
    current = head

    // 迭代鏈表,直到找到最後一項
    while (current.next) {
      current = current.next
    }

    // 找到最後一項,將其next賦爲node,創建連接
    current.next = node
  }

  // 更新列表長度
  length++
}

實現removeAt

在鏈表中特定位置移除元素,實現時也須要考慮兩種狀況:1. 移除第一個元素;2. 移除其餘元素(包括最後一個)數據結構

this.removeAt = function (position) {
  // 判斷位置是否越界
  if (position > -1 && position < length) {
    var current = head,
        previous,
        index = 0

    // 若是刪除了第一個元素,把head指向下一個元素就好了
    if (position === 0) {
      head = current.next
    } else {
      // 根據輸入的位置查找要刪除的元素
      while (index++ < position) {
        previous = current
        current = current.next
      }
      // 將上一個元素的next指向current的下一項,跳過current,實現移除current
      previous.next = current.next
    }

    // 更新列表長度
    length--

    // 返回刪除的元素
    return current.element
  } else {
    return null
  }
}

實現insert

與removeAt相似的實現,你們能夠先不看源碼,本身按着removeAt的思路實現一遍app

this.insert = function (position, element) {
  // 檢查位置是否越界
  if (position >= 0 && position <= length) {
    var node = new Node(element),
        index = 0,
        previous,
        current = head

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

      node.next = current
      head = node

    } else {
      while (index++ < position) {
        previous = current
        current = current.next
      }

      node.next = current
      previous.next = node
    }

    // 更新列表長度
    length++

    return true
} else {
  return false
}
}

實現indexOf

根據元素查找在鏈表中的位置,沒找到就返回-1

this.indexOf = function (element) {
  var current = head,
      index = 0

  while (current) {
    if (element === current.element) {
      return index
    }
    index++
    current = current.next
  }

  return -1
}

實現其餘方法

// 返回全部元素的值轉成字符串
this.toString = function () {
  var current = head,
      string = ''

  while (current) {
    string += current.element
    current = current.next
  }

  return string
}

// 移除特定元素
this.remove = function (element) {
  var index = this.indexOf(element)
  return this.removeAt(index)
}

// 判斷鏈表是否爲空
this.isEmpty = function () {
  return length === 0
}

// 返回鏈表長度
this.size = function () {
  return length
}

// 返回第一個元素
this.getHead = function () {
  return head
}

以上是單向鏈表的實現,有興趣的能夠下載源碼來看:

單向鏈表的實現-源代碼

雙向鏈表

雙向鏈表和單向鏈表的區別就是每個元素是雙向的,一個元素中包含兩個引用:一個指向前一個元素;一個指向下一個元素。除此以外,雙向鏈表還有一個指向最後一個元素的tail指針,這使得雙向鏈表能夠從頭尾兩個方向迭代鏈表,所以更加靈活。以下圖:

雙向鏈表

(圖片來源谷歌搜索,侵刪)

用JavaScript實現雙向鏈表

同單向鏈表同樣,先聲明一個構造函數

function DoubleLinkedList () {
  var Node = function (element) {
    this.element = element
    this.prev = null // 新增了一個指向前一個元素的引用
    this.next = null
  }

  var length = 0
  var head = null
  var tail = null //新增了tail指向最後一個元素
}

雙向鏈表須要有如下方法:

  • append(element):向鏈表尾部添加元素
  • insert(position, element):向鏈表特定位置插入元素
  • removeAt(position):從鏈表特定位置移除一項
  • showHead():獲取雙向鏈表的頭部
  • showLength():獲取雙向鏈表長度
  • showTail():獲取雙向鏈表尾部

實現insert

同單向鏈表相似,只不過狀況更復雜了,你不只須要額外考慮在第一個元素的位置插入新元素,還要考慮在最後一個元素以後插入新元素的狀況。此外若是在第一個元素插入時,鏈表爲空的狀況也須要考慮。

this.insert = function (position, element) {
  // 檢查是否越界
  if (position >= 0 && position <= length) {
    var node = new Node(element),
        current = head,
        previous,
        index = 0

    if (position === 0) { // 第一個元素的位置插入
      // 若是鏈表爲空
      if (!head) {
        head = node
        tail = node
      } else {
        node.next = current
        current.prev = node
        head = node
      }
    } else if (position === length) { // 在最後一個元素以後插入
      current = tail
      node.prev = current
      current.next = node
      tail = node
    } else { // 在中間插入
      while (index++ < position) {
        previous = current
        current = current.next
      }

      node.next = current
      previous.next = node

      current.prev = node
      node.prev = previous
    }

    length++

    return true
  } else {
    return false
  }
}

實現removeAt

與insert相似,這裏很少解釋直接貼代碼

this.removeAt = function (position) {
  // 檢查是否越界
  if (position > -1 && position < length) {
    var current = head,
        previous,
        index = 0

    if (position === 0) { // 第一個元素
      head = current.next
      // 若是隻有一個元素
      if (length === 1) {
        tail = null
      } else {
        head.prev = null
      }
    } else if (position === length - 1) { // 最後一個元素
      current = tail
      tail = current.prev
      tail.next = null
    } else {
      while (index++ < position) {
        previous = current
        current = current.next
      }

      previous.next = current.next
      current.next.prev = previous
    }

    length--

    return current.element
  } else {
    return null
  }
}

實現append

和單向鏈表的同樣,只不過多了tail有一些不一樣

this.append = function (element) {
  var node = new Node(element),
      current = tail

  if (head === null) {
    head = node
    tail = node
  } else {
    node.prev = current
    current.next = node
    tail = node
  }

  length++
}

其餘的代碼都很簡單,我就放上源代碼地址,你們自行查閱吧~

雙向鏈表的實現-源代碼

小結

一開始寫的時候有點不習慣,可是實現了一兩個方法以後發現不少思路是相似的,因而觸類旁通地寫出來,而後跟書上對比以後,發現仍是有點差距的。

你們有興趣也動手去實踐一下再對比源碼,能夠認識到本身有哪些地方不足。

鏈表暫時就這樣了,明天繼續攻克集合!

相關文章
相關標籤/搜索