在存儲多個元素時,咱們最經常使用的數據結構多是數組,究其緣由多是數組訪問方便,能夠直接經過[]訪問,可是數組也存在必定的缺點,數組的大小是固定,數組在執行插入或者刪除的時候成本很高。
鏈表存儲的是有序的元素集合,和數組不一樣的是,鏈表中的元素在內存並非連續放置,每一個元素由一個存儲元素自己的節點和一個指向下一個元素的引用組成,結構以下圖所示:
和數組相比,鏈表的優點在於:添加或者刪除元素不須要移動其餘元素,劣勢在與鏈表相對於數組結構更復雜,須要一個指向下一個元素的指針,在訪問鏈表中的某個元素也須要從頭迭代,而不是像數組同樣直接訪問node
首先讓咱們來看一下鏈表的大概骨架,須要定義一些什麼屬性:數組
function LinkedList() { let Node = function (element) { this.element = element this.next = null } let length = 0 let head = null this.append = function (element) { } this.insert = function (position, element) { } this.removeAt = function (position) { } this.remove = function (element) { } this.indexOf = function (element) { } this.isEmpty = function () { } this.size = function () { } this.getHead = function () { } this.toString = function () { } this.print = function () { } }
首先LinkedList裏面須要定義一個Node輔助類,表示要加入鏈表的項,包含一個element屬性和一個指向下一個節點的指針next,接下來定義了一個指針的頭head和指針的長度length,而後就是最重要的類裏面的方法,接下來就讓咱們一塊兒來看這些方法的職責和實現數據結構
鏈表在向尾部追加元素的時候有兩種狀況:鏈表爲空,添加的是第一個元素,或者鏈表不爲空,追加元素,下面來看具體實現:app
this.append = function (element) { let node = new Node(element), current if (head === null) { head = node // 鏈表爲空時,插入 } else { current = node while (current.next) { // 循環鏈表,直到最後一項 current = current.next } current.next = node // 找到最後一項,將新增元素鏈接 } length++ }
如今讓咱們先來看第一個種狀況,鏈表爲空時,插入就直接是頭,所以直接將node賦值給head就行,第二種狀況,當鏈表有元素時,就須要先循環鏈表,找到最後一項,而後將最後一項的next指針指向nodethis
如今讓咱們來看看怎麼從指定位置移除元素,移除元素也有兩個場景,第一種是移除第一個元素,第二種是移除第一個之外的任意一個元素,來看具體實現:spa
this.removeAt = function (position) { if (position > -1 && position < length) { // 判斷邊界 let previous, index = 0, current = head if (position === 0) { // 移除第一項 head = current.next } else { while (index++ < position) { previous = current current = current.next } previous.next = current.next } length-- return current.element } else { return null } }
接下來一塊兒來分析一下上面的代碼,首先判斷要刪除的位置是否是有效的位置,而後來看第一種狀況,當移除的元素是第一項是時,此時直接將head指向第二個元素就好了,第二種狀況就會稍微複雜一點,首先須要一個index控制遞增,previous記錄前一個位置,移除當前元素,就是將前一個元素的next指向下一個元素,來看一個示意圖:
所以在while循環中始終用previous記錄上一個位置元素,current記錄下一個元素,跳出循環時,上一個元素的next指針指向當前元素的next指針指向的元素,就將當前元素移出鏈表指針
接下來來看在任意位置插入的insert方法,這個方法一樣須要考慮兩種狀況,插入位置在頭部和插入位置不在頭部,下面來看一下具體實現:code
this.insert = function (position, element) { if (position > -1 && position <= length) { let node = new Node(element),previous, index = 0, current = head if (position === 0) { node.next = current head = node } else { while (index++ < position) { previous = current current = current.next } previous.next = node node.next = current } length++ return true } else { return false } }
先來看第一種狀況,鏈表起點添加一個元素,將node.next指向current,而後再將node的引用賦值給head,這樣就再鏈表的起點添加了一個元素,第二種狀況,在其餘位置插入一個元素,previous是插入元素的前一個元素,current爲插入元素的後一個元素,想要插入一個元素,就須要將前一個元素的next指向要插入的元素,要插入元素的next指向下一個元素,來看示意圖:
如上圖所示:將新項node插入到previous和current之間,須要將previous.next指向node,node.next指向current,這樣就在鏈表中插入了一個新的項對象
toString方法會把LinkedList對象轉換成一個字符串,下面來看具體實現:索引
this.toString = function () { let current = head, string = '' while (current) { string += current.element + (current.next ? 'n' : '') current = current.next } return string }
循環遍歷全部元素,以head爲起點,當存在下一個元素時,就將其拼接到字符串中,直到next爲null
indexOf方法返回對應元素的位置,存在就返回對應的索引,不存在返回-1,來看具體的實現:
this.indexOf = function (element) { let current = head, index = 0 while (current) { if (current.element === element) { return index } index++ current = current.next } return -1 }
遍歷鏈表,當前元素的值與目標值一致時返回元素的位置index,遍歷完鏈表還沒找到則返回-1
因爲這幾個方法實現比較簡單,直接來看具體實現:
this.remove = function (element) { // 移除指定元素 let index = this.indexOf(element) return this.removeAt(index) } this.isEmpty = function () { // 判斷鏈表是否爲空 return length === 0 } this.size = function () { // 獲取鏈表長度 return length } this.getHead = function () { // 獲取鏈表頭 return head }
這篇文章主要對鏈表作了簡單介紹,對鏈表的簡單實現。若是有錯誤或不嚴謹的地方,歡迎批評指正,若是喜歡,歡迎點贊。