鏈表是物理存儲單元上非連續的、非順序的存儲結構,不一樣於棧和隊列。鏈表由一系列節點組成,每一個結點包括兩個部分:一個是存儲數據元素的數據域,另外一個是存儲下一個結點地址的指針域。javascript
因爲沒必要須按順序存儲,鏈表在插入的時候能夠達到O(1)的複雜度,比另外一種線性表順序錶快得多,可是查找一個節點或者訪問特定編號的節點則須要O(n)的時間,而線性表和順序表相應的時間複雜度分別是O(logn)和O(1)。使用鏈表結構能夠克服數組鏈表須要預先知道數據大小的缺點,鏈表結構能夠充分利用計算機內存空間,實現靈活的內存動態管理。可是鏈表失去了數組隨機讀取的優勢,同時鏈表因爲增長告終點的指針域,空間開銷比較大。java
下面爲鏈表的結構示意圖 node
節點包含了兩部分,一部分是存儲數據的元素區域,一部分是指向下一個節點的指針區域,上圖中綠色部分表示數據區域,藍色部分表示指針區域,它們共同構成一個節點。數組
定義一個節點:app
let Node = function(data) {
this.data = data // 數據
this.next = null // 指針
}
// 建立新的節點
let node1 = new Node(1);
let node2 = new Node(2);
let node3 = new Node(3);
node1.next = node2;
node2.next = node3
複製代碼
鏈表中的第一個節點是首節點,最後一個節點是尾節點。函數
無頭鏈表是指第一個節點既有數據域,又有指針域,第一個節點既是首節點又是頭節點。學習
有頭鏈表是指第一個節點只有指針域,而沒有數據域。一般有頭鏈表的數據域能夠存放當前的鏈表的一些信息。ui
在鏈表定義中展現的就是無頭鏈表,一個有頭鏈表的結構圖以下:this
function LinkList() {
let Node = function(data) {
this.data = data
this.next = null
}
let length = 0 // 鏈表長度
let head = null // 頭節點
let tail = null // 尾節點
}
複製代碼
this.append = (data) => {
// 建立一個新的節點
let new_node = new Node(data)
// 判斷是否爲空鏈表
if(head === null) {
head = new_node
tail = head
} else {
tail.next = new_node // 尾節點指向新建立的節點
tail = new_node // 讓尾節點等於新建立的節點
}
length ++
}
複製代碼
append只能在鏈表的末尾添加元素,而insert能夠在指定位置插入一個元素,新增數據的方式更加靈活,insert方法須要傳入參數index,指明要插入的索引位置。該方法的關鍵是找到索引爲index-1的節點,只有找到這個節點,才能將新的節點插入到鏈表中spa
this.insert = (index. data) => {
if(index<0 || index > length) return // index無效值
if(index === length) { // 當index等於length調用append方法
return this.append(data)
} else {
let new_node = new Node(data)
if(index === 0) { //
new_node.next = head
head = new_node
} else {
let insert_index = 1
let curr_node = head
while(insert_index < index) {
insert_index ++
curr_node = curr_node.next
}
let next_node = curr_node.next // 記錄當前節點下一個節點
curr_node.next = new_node // 當前節點下一個節點設爲新節點
new_node.next = next_node // 看上面的圖會更能明白
}
}
length ++
}
複製代碼
刪除指定位置的節點,須要傳入參數index,和insert方法同樣,先考慮索引的範圍是否合法,而後考慮索引在邊界時的操做,關鍵點是找到索引爲index-1的這個節點,這個節點的next指向了要刪除的節點。
this.remove = (index) => {
if(index<0 || index>length) return
if(index === 0) {
let del_node = head
head = head.next
del_node.next = null
} else {
let del_index = 0
let pre_node = null // 要刪除節點的前一個節點
let curr_node = head // 要刪除的節點
while(del_index<index) { // 依此循環找到
del_index++
pre_node = curr_node
curr_node = curr_node.next
}
let del_node = curr_node
pre_node.next = curr_node.next // 要刪除節點的前一個節點的下一個節點等於要刪除節點的下一個節點
del_node.next = null // 要刪除節點的下一個節點爲空
if(curr_node.next === null) { // 若是刪除的是尾節點
tail_node = pre_node
}
}
length --
}
複製代碼
其餘方法比較容易理解
function LinkList() {
let Node = function (data) {
this.data = data
this.next = null
}
let length = 0
let head = null
let tail = null
// 在尾部添加節點
this.append = (data) => {
// 建立新節點
let new_node = new Node(data)
if (head == null) {
head = new_node
tail = new_node
} else {
tail.next = new_node
tail = new_node
}
length +=1
return true
}
// 打印節點
this.print = () => {
let curr_node = head
while (curr_node) {
console.log(curr_node.data)
curr_node = curr_node.next
}
}
// 指定位置添加節點
this.insert = (index, data) => {
if (index > length || index < 0) {
return
} else if (index == length) {
return this.append(data)
} else {
let new_node = new Node(data)
if (index == 0) {
new_node.next = head
head = new_node
} else {
let insert_index = 1
let curr_node = head
while (insert_index < index) {
insert_index ++
curr_node = curr_node.next
}
let next_node = curr_node.next
curr_node.next = new_node
new_node.next = next_node
}
}
length ++
return true
}
// 刪除指定位置節點
this.remove = (index) =>{
if (index<0||index>=length) {
return false
} else {
let del_node = null
if (index == 0) {
del_node = head
head = head.next
del_node.next = null
} else {
let del_index = 0
let pre_node = null
let curr_node = head
while (del_index<index) {
del_index++
pre_node = curr_node
curr_node = curr_node.next
}
del_node = curr_node
pre_node.next = curr_node.next
if (curr_node.next == null) {
tail = pre_node
}
del_node.next = null
}
}
length --
// return del_node.data
}
// 返回指定位置節點
this.get = (index) => {
if (index>=length || index<0) {
return false
}
let node_index = 0
let curr_node = head
while (node_index<index) {
node_index++
curr_node = curr_node.next
}
return curr_node.data
}
複製代碼
假設鏈表中間的某個點爲curr_node
,它的前一個節點是pre_node
,後一個節點是next_node
,如今把思路聚焦到這個curr_node
節點上,只考慮在這一個點上進行翻轉:curr_node.next = pre_node
;只須要這簡單的一個步驟就能夠完成對curr_node
節點的翻轉,對於頭節點來講,它沒有上一個節點,讓 pre_node=null
,表示它的上一個節點是一個空節點。在遍歷的過程當中,每完成一個節點的翻轉,都讓curr_node = next_node
,找到下一個須要翻轉的節點。同時,pre_node
和next_node
也跟隨curr_node
一塊兒向後滑動。
function reveser(head) {
if (!head) {
return false
}
let pre_node = null
let curr_node = head
while (curr_node) { // 循環結束條件爲當前節點爲空
let next_node = curr_node.next // 記錄當前節點下一個節點
curr_node.next = pre_node // 當前節點的下一個節點變爲前一個節點
pre_node = curr_node // 向下遍歷
curr_node = next_node
}
return pre_node
}
複製代碼
遞歸的核心之處在於先執行的後執行完,以及遞歸的出口
function reveser_digui(head) {
if (!head) {
return false
}
if (head.next == null) { // 出口
return head
}
let new_head = reveser_digui(head.next) // 遞歸調用
head.next.next = head // 下一個節點指向上一個節點
head.next = null //
return new_head
}
複製代碼
已知有兩個有序鏈表(鏈表元素從小到大),請實現函數merge_link,將兩個鏈表合併成一個有序鏈表,並返回新鏈表,原有的兩個鏈表不要修改。
合併兩個有序鏈表,是歸併排序在鏈表上的一種實踐。對兩個鏈表,各自設置一個遊標節點指向頭節點,對遊標節點上的數值進行比較,數值小的那個拿出來放入到合併鏈表中,同時遊標節點向後滑動,繼續比較遊標節點數值大小。
爲了實現滑動,須要使用一個while循環,當其中一個遊標節點爲null時,循環終止,這時,可能另外一個遊標節點尚未到達尾節點,那麼把這段尚未遍歷結束的鏈表添加到合併列表上。
function merge_link(head1,head2) {
if(head1 === null && head2 === null) return
if(head1 === null) {
return head2
} else if (head2 === null){
return head1
}
let merge_head = null // 合併後的頭節點
let merge_tail = null // 合併後的尾節點
let curr1 = head1 // 遊標
let curr2 = head2
while(curr1&&curr2) {
let min_node = null // 最小的節點
if(curr1.data<curr2.data) { // 找到最小的節點
min_node = curr1.data
curr1 = curr1.next // 向後滑動
} else {
min_node = curr2.data
curr2 = curr2.next
}
// 想合併的鏈表添加節點
if(merge_head === null) { // 鏈表爲空
merge_head = new Node(min_node)
merge_tail = merge_head
} else { // 不爲空
let new_node = new Node(min_node)
merge_tail.next = new_node
merge_tail = new_node
}
// 判斷是否有剩餘的部分
let res_link = null
if(curr_1){
rest_link = curr_1;
}
if(curr_2){
rest_link = curr_2;
}
while (res_link) { // 依此將剩餘的加到合併鏈表
let new_node = new Node(res_link.data)
merge_tail.next = new_node
merge_tail = new_node
res_link = res_link.next
}
}
return merge_head
}
複製代碼
鏈表還有不少其餘會被問道的問題好比:
在學習了鏈表以後,發現鏈表比隊列和棧更加困難,往後要多加複習和練習來鞏固學到的內容。