鏈表
要存儲多個元素,數組(或列表)可能是最常用的數據結構。
每種語言都實現了數組。這種數據結構非常方便,提供了一個便利的[]
語法來訪問它的元素。
然而,這種數據結構有一個缺點:在大多數語言中,數組的大小是固定的,從數組的起點或中間插入或移除項的成本很高,因爲需要移動元素;
儘管 JavaScript中的Array
類方法可以幫我們做這些事,但背後的處理機制同樣如此。
鏈表存儲有序的元素集合,但不同於數組,鏈表中的元素在內存中並不是連續放置的。每個 元素由一個存儲元素本身的節點和一個指向下一個元素的引用(也稱指針或鏈接)組成。
下圖展示了鏈表的結構:
相對於傳統的數組,鏈表的一個好處在於,添加或移除元素的時候不需要移動其他元素。然而,鏈表需要使用指針,因此實現鏈表時需要額外注意。·
數組的另一個細節是可以直接訪問任何位置的任何元素,而要想訪問鏈表中間的一個元素,需要從起點(表頭)開始迭代列表直到找到所需的元素。
下面我們使用 JavaScript 創建一個鏈表類:
// 鏈表節點
class Node {
constructor(element) {
this.element = element
this.next = null
}
}
// 鏈表
class LinkedList {
constructor() {
this.head = null
this.length = 0
}
// 追加元素
append(element) {
const node = new Node(element)
let current = null
if (this.head === null) {
this.head = node
} else {
current = this.head
while(current.next) {
current = current.next
}
current.next = node
}
this.length++
}
// 任意位置插入元素
insert(position, element) {
if (position >= 0 && position <= this.length) {
const node = new Node(element)
let current = this.head
let previous = null
let index = 0
if (position === 0) {
this.head = node
} else {
while (index++ < position) {
previous = current
current = current.next
}
node.next = current
previous.next = node
}
this.length++
return true
}
return false
}
// 移除指定位置元素
removeAt(position) {
// 檢查越界值
if (position > -1 && position < length) {
let current = this.head
let previous = null
let index = 0
if (position === 0) {
this.head = current.next
} else {
while (index++ < position) {
previous = current
current = current.next
}
previous.next = current.next
}
this.length--
return current.element
}
return null
}
// 尋找元素下標
findIndex(element) {
let current = this.head
let index = -1
while (current) {
if (element === current.element) {
return index + 1
}
index++
current = current.next
}
return -1
}
// 刪除指定文檔
remove(element) {
const index = this. findIndex(element)
return this.removeAt(index)
}
isEmpty() {
return !this.length
}
size() {
return this.length
}
// 轉爲字符串
toString() {
let current = this.head
let string = ''
while (current) {
string += ` ${current.element}`
current = current.next
}
return string
}
}
鏈表類的使用:
const linkedList = new LinkedList()
console.log(linkedList)
linkedList.append(2)
linkedList.append(6)
linkedList.append(24)
linkedList.append(152)
linkedList.insert(3, 18)
console.log(linkedList)
console.log(linkedList.findIndex(24))
雙向鏈表
雙向鏈表和普通鏈表的區別在於,在鏈表中, 一個節點只有鏈向下一個節點的鏈接,而在雙向鏈表中,鏈接是雙向的:一個鏈向下一個元素, 另一個鏈向前一個元素,如下圖所示:
雙向鏈表提供了兩種迭代列表的方法:從頭到尾,或者反過來。我們也可以訪問一個特定節 點的下一個或前一個元素。在單向鏈表中,如果迭代列表時錯過了要找的元素,就需要回到列表 起點,重新開始迭代。這是雙向鏈表的一個優點。
來實現一個雙向鏈表類:
// 鏈表節點
class Node {
constructor(element) {
this.element = element
this.prev = null
this.next = null
}
}
// 雙向鏈表
class DoublyLinkedList {
constructor() {
this.head = null
this.tail = null
this.length = 0
}
// 任意位置插入元素
insert(position, element) {
if (position >= 0 && position <= this.length){
const node = new Node(element)
let current = this.head
let previous = null
let index = 0
// 首位
if (position === 0) {
if (!head){
this.head = node
this.tail = node
} else {
node.next = current
this.head = node
current.prev = node
}
// 末位
} else if (position === this.length) {
current = this.tail
current.next = node
node.prev = current
this.tail = node
// 中位
} else {
while (index++ < position) {
previous = current
current = current.next
}
node.next = current
previous.next = node
current.prev = node
node.prev = previous
}
this.length++
return true
}
return false
}
// 移除指定位置元素
removeAt(position) {
if (position > -1 && position < this.length) {
let current = this.head
let previous = null
let index = 0
// 首位
if (position === 0) {
this.head = this.head.next
this.head.prev = null
if (this.length === 1) {
this.tail = null
}
// 末位
} else if (position === this.length - 1) {
this.tail = this.tail.prev
this.tail.next = null
// 中位
} else {
while (index++ < position) {
previous = current
current = current.next
}
previous.next = current.next
current.next.prev = previous
}
this.length--
return current.element
} else {
return null
}
}
// 其他方法...
}
循環鏈表
循環鏈表可以像鏈表一樣只有單向引用,也可以像雙向鏈表一樣有雙向引用。循環鏈表和鏈表之間唯一的區別在於,最後一個元素指向下一個元素的指針(tail.next)不是引用null, 而是指向第一個元素(head),如下圖所示。
雙向循環鏈表有指向head元素的tail.next,和指向tail元素的head.prev。
鏈表相比數組最重要的優點,那就是無需移動鏈表中的元素,就能輕鬆地添加和移除元素。因此,當你需要添加和移除很多元素 時,最好的選擇就是鏈表,而非數組。