鏈表是動態的數據結構,它的每一個元素由一個存儲元素自己的節點和一個指向下一個元素的引用(也稱指針或連接)組成。javascript
現實中,有一些鏈表的例子。java
第一個就是尋寶的遊戲。你有一條線索,這條線索是指向尋找下一條線索的地點的指針。你順着這條連接去下一個地點,獲得另外一條指向下一處的線索。獲得列表中間的線索的惟一辦法,就是從起點(第一條線索)順着列表尋找。node
第二個例子是火車。一列火車是由一些車箱組成的。每節車箱都是相互鏈接。你很容易分離一節車皮,改變它的位置,添加或移除它。每節車箱都是列表的元素,車箱間的鏈接就是指針。git
鏈表有多種不一樣的類型,單鏈表,雙向鏈表,循環鏈表等。github
咱們先實現單鏈表。單鏈表是一種鏈式存取的數據結構。數組
linkednode.js文件,裏面包含了鏈表中節點的項數據結構
/** * 鏈表節點,鏈表中的項,鏈表中的節點 */ export class Node { constructor(element, next = null) { this.element = element // 鏈表中節點的值 this.next = next // 指向列表中下一個節點項的指針 } }
linkedlist.js文件,鏈表各類方法的實現app
import { Node } from './linkednode' /** * 鏈表類 */ export default class LinkedList { constructor() { this.length = 0 // 存儲鏈表中列表項的數量 this.head = null // 存儲鏈表中第一個節點的引用 } /** * 向列表尾部添加一個新的項 * @param {*} element 須要添加至鏈表中的節點項 */ append(element) { let node = new Node(element) // 將新項建立爲符合鏈表結構的列表項 if (this.head === null) { // 鏈表中的元素爲空 this.head = node } else { let current = this.head // 將第一個節點的引用賦值給當前項 // 循環列表,直到找到最後一項 while (current.next) { current = current.next } // 找到最後一項,將其next賦爲node,創建連接 current.next = node } this.length++ // 更新鏈表的長度 } /** * 向列表的特定位置插入一個新的項 * @param {*} position 插入鏈表中的位置 * @param {*} element 須要添加的節點 */ insert(position, element) { let node = new Node(element) // 檢查越界 if (position > -1 && position <= this.length) { let current = this.head let previous let index = 0 // 在第一個位子添加 if (position === 0) { node.next = current this.head = node } else { // 須要找到特定的位子,將想要插入的元素放在previous節點和current節點之間 while (index++ < position) { // previous 將是對想要插入新元素的位置以前一個元素的引用 previous = current // current 對想要插入新元素的位置以後一個元素的引用 current = current.next } node.next = current previous.next = node } this.length++ return true } else { return false } } /** * 移除指定位置的節點元素,並返回移除的項 * 若是超出了鏈表的範圍,則返回null * @param {Int32Array} position 鏈表中的位置 */ removeAt(position) { // 檢查越界 if (position > -1 && position < this.length) { let current = this.head let previous // 先前一個節點 let index = 0 // 當前節點的位子 // 移除第一項 if (position === 0) { this.head = current.next } else { while (index++ < position) { previous = current current = current.next } // 將previous 與 current 的下一項連接起來:跳過current,從而移除它 // 當前元素會丟棄在計算機內存中,等待GC清除 previous.next = current.next } this.length-- // 鏈表元素數量減1 return current.element // 返回移除的項 } else { return null // 若是超出了鏈表的範圍,則返回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 } /** * 獲取鏈表的表頭節點 * head變量是LinkedList類的私有變量。 * 可是,咱們須要實如今類的外部循環訪問列表。 * 此時,就須要提供獲取類的第一個元素的方法 */ getHead() { return this.head } /** * 因爲列表項使用了Node類,須要重寫toString方法,讓其只輸出元素的值。 */ toString() { let current = this.head let string = '' while (current) { string += current.element + (current.next ? '-->' : '') current = current.next } return string } }
咱們先比較下JavaScript中鏈表與數組的區別:測試
鏈表中的元素是不連續的,而數組中的元素是連續的。
鏈表添加或移除元素的時候不須要移動其餘元素,而數組須要。
鏈表須要指針,而數組不須要。
鏈表須要從表頭開始迭代列表直到找到所須要的元素。數組則能夠直接訪問任何位置的任何元素。
二者都具備動態性。關於數組動態性。數組在js中是一個能夠修改的對象,添加移除元素,它會動態的變化,可是成本很高,須要移動元素。在大多數語言中(好比C和Java),數組的大小是固定的,想添加元素就要建立一個全新的數組,不能簡單地往其中添加所需的元素。
要存儲多個元素,數組多是最經常使用的數據結構。可是,若是要存儲數據,而且會有移除或者添加大量數據時候,鏈表比數組更實用。