文章目錄
使用JS一步步理解並實現鏈表
一、數組和鏈表優缺點
1.1、數組(Array)
1.1.1 數組的優點
線性表的一種。高級數據語言中,對數組內部的元素類型沒有嚴格的要求,這在語言中稱爲泛型,可以放入任何單元類型。數組的底層的硬件實現,存在一個內存管理器,每當申請一個數組的時候,計算機會在內存中開闢一段連續的地址,每一個地址可以通過內存管理器進行訪問,數組訪問第一個元素和其他任何一個元素的時間複雜度是相同的,都是O(1),即常數級別。由於數組可以隨機訪問任何一個元素,所以它的時間效率快,這是數組的優勢之一。
1.1.2 數組的缺點
數組的問題出現於它增加、刪除某些元素的時候。
比如現在有個數組,要在中間插入一個元素F,那麼元素C、D、E就要相應的向後移動一個位置,這樣一來數組插入操作的時間複雜度趨於O(1)-O(n)之間。 數組刪除也是同理。
所以在增加、刪除操作比較頻繁的情況下,數組的缺點就會顯露出來。
下面是數組中各個操作對應的時間複雜度:
1.2、鏈表(LinkedList)
1.2.1單鏈表
1.2.2 雙向鏈表
1.2.3 單循環鏈表
1.2.1 、鏈表的優點
相比於數組,鏈表在增加節點和刪除節點時候,並不會引起其他節點的羣移,這樣的話增加、刪除操作的時間複雜度爲O(1),下面是單鏈表插入某個節點的示意圖,我們可以看到只需要更改當前節點和前置節點和的next指針,即可完成節點的插入操作。
下面是單鏈表的節點插入操作示意圖:
1.2.2 、鏈表的缺點
與數組相比,在鏈表中訪問任一元素的位置,就沒那麼容易了,需要從鏈表的head開始,一步步的向後查詢,這種情況下時間複雜度爲O(1)-O(n)之間。 下面是鏈表中各個操作對應的時間複雜度:
1.2.3 、跳錶
由於鏈表的search操作時間複雜度爲O(n),爲了彌補鏈表的缺陷,我們可以思考給鏈表增加多個指針去作爲起始指針,這樣的話search某個節點就會更有效率,從而減少search的時間複雜度。
由此引出了跳錶的思想,而多個起始指針則晉升爲索引的概念,通過增加維度,以空間換時間來進行時間度優化,跳錶中search的時間複雜度爲O(logn)。
下面是跳錶中一級索引的示意圖:
二、使用JS實現鏈表
理解了鏈表的幾種通用形態,我們可以用js一步步實現鏈表這個數據結構。
2.1、單鏈表
實現單鏈表的原理在於,要不斷更新節點的next指針,使整個鏈表串聯起來。
class Node {
constructor (element) {
this.element = element
this.next = null
}
}
class LinkedList {
constructor () {
// 初始化鏈表長度
this.length = 0
// 初始化鏈表第一個節點
this.head = null
}
append (element) {
let node = new Node(element)
let current
// 鏈表爲空情況
if (this.head === null) {
this.head = node
} else {
current = this.head
while (current.next) {
current = current.next
}
current.next = node
}
this.length ++
}
insert (element, point) {
if (point >=0 && point <= this.length) {
let node = new Node(element)
let current = this.head
let previous
let index = 0
if (point === 0) {
node.next = current
this.head = node
} else {
while (index++ < point) {
previous = current
current = current.next
}
previous.next = node
node.next = current
}
this.length++
return true
} else {
return false
}
}
removeAt (point) {
if (point > -1 && point < this.length) {
let current = this.head
let index = 0
let previous
if (point === 0) {
this.head = current.next
} else {
while (index++ < point) {
previous = current
current = current.next
}
previous.next = current.next
}
this.length--
return current.element
} else {
return null
}
}
remove (element) {
let index = this.find(element)
// 刪除後返回已刪除的節點
return this.removeAt(index)
}
find (element) {
let current = this.head
let index = 0
if (element == current.element){
return 0;
}
while (current.next) {
if(current.element === element) {
return index
}
index++
current = current.next
}
if (element == current.element){
return index;
}
return -1
}
isEmpty () {
return this.length === 0
}
size () {
return this.length
}
print () {
let current = this.head
let result = ''
while (current) {
result += current.element + (current.next ? '->' : '')
current = current.next
}
return result
}
}
let l1 = new LinkedList()
...
2.2、雙向鏈表
實現雙向鏈表的原理在於,每次更新鏈表要同時考慮到next和prev兩個指針,並保證更新指針的指向。
class Node {
constructor (element) {
this.element = element
this.next = null
this.prev = null
}
}
class DoubleLinkedList {
constructor () {
this.length = 0
this.head = null
// 定義尾部節點
this.tail = null
}
append (element) {
let node = new Node(element)
let tail = this.tail
if (this.head === null) {
this.head = node
this.tail = node
} else {
tail.next = node
node.prev = tail
this.tail = node
}
this.length++
}
insert (element, point) {
if(point >= 0 && point <= this.length) {
let node = new Node(element)
let current = this.head
let tail = this.tail
let index = 0
let previous
if (point === 0) {
if (!this.head) {
this.head = node
this.tail = node
} else {
node.next = current
current.prev = node
this.head = node
}
} else if (point === this.length) {
current = tail
current.next = node
node.prev = current
this.tail = node
} else {
while (index++ < point) {
previous = current
current = current.next
}
// 將原來的鏈表斷開,重新使用指針串接起來
node.next = current
node.prev = previous
previous.next = node
current.prev = node
}
this.length++
return true
} else {
return false
}
}
removeAt (point) {
if (point > -1 && point < this.length) {
let current = this.head
let index = 0
let previous
let tail = this.tail
if (point === 0) {
// remove第一項的情況
this.head = current.next
if (this.length === 1) {
this.tail = null
} else {
this.head.prev = null
}
} else if (point === this.length -1) {
current = tail
this.tail = current.prev
this.tail.next = null
} else {
while (index++ < point) {
previous = current
current = current.next
}
previous.next = current.next
current.next.prev = previous
}
this.length--
return current.element
} else {
return null
}
}
find (element) {
let current = this.head
let index = 0
if (element == current.element){
return 0;
}
while (current.next) {
if(current.element === element) {
return index
}
index++
current = current.next
}
// 爲了保證最後一位被找到
if (element == current.element){
return index;
}
return -1
}
remove (element) {
let index = this.find(element)
return this.removeAt(index)
}
isEmpty () {
return this.length === 0
}
size () {
return this.length
}
print () {
let current = this.head
let result = ''
while (current) {
result += current.element + (current.next ? '->' : '')
current = current.next
}
return result
}
}
let l1 = new DoubleLinkedList()
2.3、單向循環鏈表
單向循環鏈表和單鏈表大致相同,唯一區別是,尾節點tail的next指針要指向head,使鏈表的頭尾串聯在一起,形成循環。
2.4、雙向循環鏈表
雙向循環鏈表和單向循環原理上大概一致,區別在於,雙向循環鏈表同時擁有2個指針prev和next,並在head和tail兩個臨界點進行指針更新處理,並保持鏈表的首尾相連。
class Node {
constructor (element) {
this.element = element
this.next = null
}
}
class CircleLinkedList {
constructor () {
// 初始化鏈表長度
this.length = 0
// 初始化鏈表第一個節點
this.head = null
}
append (element) {
let node = new Node(element)
let head = this.head
let current
// 鏈表爲空情況
if (this.head === null) {
this.head = node
} else {
current = this.head
while (current.next && current.next !== head) {
current = current.next
}
current.next = node
}
// 保持首尾相連
node.next = head
this.length ++
}
insert (element, point) {
if (point >=0 && point <= this.length) {
let node = new Node(element)
let current = this.head
let previous
let index = 0
if (point === 0) {
node.next = current
while (current.next && current.next !== this.head) {
current = current.next
}
this.head = node
current.next = this.head
} else {
while (index++ < point) {
previous = current
current = current.next
}
previous.next = node
// 首尾相連
node.next = current === null ? head : current
}
this.length++
return true
} else {
return false
}
}
removeAt (point) {
if (point > -1 && point < this.length) {
let current = this.head
let index = 0
let previous
if (point === 0) {
this.head = current.next
while (current.next && current.next !== this.head) {
current = current.next
}
current.next = this.head
} else {
while (index++ < point) {
previous = current
current = current.next
}
previous.next = current.next
}
this.length--
return current.element
} else {
return null
}
}
remove (element) {
let index = this.find(element)
// 刪除後返回已刪除的節點
return this.removeAt(index)
}
find (element) {
let current = this.head
let index = 0
if (element == current.element){
return 0;
}
while (current.next && current.next !== this.head) {
if(current.element === element) {
return index
}
index++
current = current.next
}
if (element == current.element){
return index;
}
return -1
}
isEmpty () {
return this.length === 0
}
size () {
return this.length
}
print () {
let current = this.head
let result = ''
while (current.next && current.next !== this.head) {
result += current.element + (current.next ? '->' : '')
current = current.next
}
result += current.element
return result
}
}
let l1 = new CircleLinkedList()
三、小結
以上是我對鏈表數組相關數據結構的淺薄認知,如有紕漏,還望指出~~
🍺🍺🍺🍺🍺