在存儲多個元素時,我們最常用的數據結構可能是數組,究其原因可能是數組訪問方便,可以直接通過[]訪問,但是數組也存在一定的缺點,數組的大小是固定,數組在執行插入或者刪除的時候成本很高。
鏈表存儲的是有序的元素集合,和數組不同的是,鏈表中的元素在內存並不是連續放置,每個元素由一個存儲元素本身的節點和一個指向下一個元素的引用組成,結構如下圖所示:
和數組相比,鏈表的優勢在於:添加或者刪除元素不需要移動其他元素,劣勢在與鏈表相對於數組結構更復雜,需要一個指向下一個元素的指針,在訪問鏈表中的某個元素也需要從頭迭代,而不是像數組一樣直接訪問
鏈表的創建
首先讓我們來看一下鏈表的大概骨架,需要定義一些什麼屬性:
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,然後就是最重要的類裏面的方法,接下來就讓我們一起來看這些方法的職責和實現
append(向鏈表尾部追加元素)
鏈表在向尾部追加元素的時候有兩種情況:鏈表爲空,添加的是第一個元素,或者鏈表不爲空,追加元素,下面來看具體實現:
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指針指向node
removeAt(移除元素)
現在讓我們來看看怎麼從指定位置移除元素,移除元素也有兩個場景,第一種是移除第一個元素,第二種是移除第一個以外的任意一個元素,來看具體實現:
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(任意位置插入)
接下來來看在任意位置插入的insert方法,這個方法同樣需要考慮兩種情況,插入位置在頭部和插入位置不在頭部,下面來看一下具體實現:
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
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
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
remove、isEmpty、size、getHead
由於這幾個方法實現比較簡單,直接來看具體實現:
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
}
總結
這篇文章主要對鏈表做了簡單介紹,對鏈表的簡單實現。如果有錯誤或不嚴謹的地方,歡迎批評指正,如果喜歡,歡迎點贊。