由於不必須按順序儲存,鍊錶在插入的時候可以達到o(1)的複雜度,比另一種線性表順序表快得多,但是查詢乙個節點或者訪問特定編號的節點則需要o(n)的時間,而線性表和順序表相應的時間複雜度分別是o(logn)和o(1)。使用鍊錶結構可以克服陣列鍊錶需要預先知道資料大小的缺點,鍊錶結構可以充分利用計算機記憶體空間,實現靈活的記憶體動態管理。但是鍊錶失去了陣列隨機讀取的優點,同時鍊錶由於增加了結點的指標域,空間開銷比較大。
下面為鍊錶的結構示意圖
節點包含了兩部分,一部分是儲存資料的元素區域,一部分是指向下乙個節點的指標區域,上圖中綠色部分表示資料區域,藍色部分表示指標區域,它們共同構成乙個節點。
定義乙個節點:
let node = function(data)
// 建立新的節點
let node1 = new node(1);
let node2 = new node(2);
let node3 = new node(3);
node1.next = node2;
node2.next = node3
複製**
鍊錶中的第乙個節點是首節點,最後乙個節點是尾節點。
無頭鍊錶是指第乙個節點既有資料域,又有指標域,第乙個節點既是首節點又是頭節點。
有頭鍊錶是指第乙個節點只有指標域,而沒有資料域。通常有頭鍊錶的資料域可以存放當前的鍊錶的一些資訊。
在鍊錶定義中展示的就是無頭鍊錶,乙個有頭鍊錶的結構圖如下:
function
linklist()
let length = 0
// 鍊錶長度
let head = null
// 頭節點
let tail = null
// 尾節點
}複製**
insert,在指定位置插入乙個元素
remove,刪除指定位置的節點
get,返回指定索引位置的元素
print,列印整個鍊錶
如果列表不為空,則tail.next = node, 並讓tail指向node
// 建立乙個新的節點
let new_node = new node(data)
// 判斷是否為空鍊錶
if(head === null) else
length ++
}複製**
2.2.2、insert
this.insert = (index. data) => else else
let next_node = curr_node.next // 記錄當前節點下乙個節點
curr_node.next = new_node // 當前節點下乙個節點設為新節點
new_node.next = next_node // 看上面的圖會更能明白}}
length ++
}複製**
2.2.3、remove
刪除指定位置的節點,需要傳入引數index,和insert方法一樣,先考慮索引的範圍是否合法,然後考慮索引在邊界時的操作,關鍵點是找到索引為index-1的這個節點,這個節點的next指向了要刪除的節點。
this.remove = (index) => else
let del_node = curr_node
pre_node.next = curr_node.next // 要刪除節點的前乙個節點的下乙個節點等於要刪除節點的下乙個節點
del_node.next = null
// 要刪除節點的下乙個節點為空
if(curr_node.next === null)
}length --
}複製**
2.2.4、最終**
其他方法比較容易理解
function
linklist()
let length = 0
let head = null
let tail = null
// 在尾部新增節點
// 建立新節點
let new_node = new node(data)
if (head == null) else
length +=1
return
true
} // 列印節點
this.print = () =>
} // 指定位置新增節點
this.insert = (index, data) => else
if (index == length) else else
let next_node = curr_node.next
curr_node.next = new_node
new_node.next = next_node}}
length ++
return
true
} // 刪除指定位置節點
this.remove = (index) => else else
del_node.next = null}}
length --
// return del_node.data
} // 返回指定位置節點
this.get = (index) =>
let node_index = 0
let curr_node = head
while (node_indexreturn 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)
let pre_node = null
let curr_node = head
while (curr_node)
return pre_node
}複製**
遞迴反轉
遞迴的核心之處在於先執行的後執行完,以及遞迴的出口
function
reveser_digui(head)
if (head.next == null)
let new_head = reveser_digui(head.next) // 遞迴呼叫
head.next = null
// return new_head
}複製**
已知有兩個有序鍊錶(鍊錶元素從小到大),請實現函式merge_link,將兩個鍊錶合併成乙個有序鍊錶,並返回新鍊錶,原有的兩個鍊錶不要修改。
思路合併兩個有序鍊錶,是歸併排序在鍊錶上的一種實踐。對兩個鍊錶,各自設定乙個游標節點指向頭節點,對游標節點上的數值進行比較,數值小的那個拿出來放入到合併鍊錶中,同時游標節點向後滑動,繼續比較游標節點數值大小。
為了實現滑動,需要使用乙個while迴圈,當其中乙個游標節點為null時,迴圈終止,這時,可能另乙個游標節點還沒有到達尾節點,那麼把這段還沒有遍歷結束的鍊錶新增到合併列表上。
function
merge_link(head1,head2) else
if (head2 === null)
let merge_head = null
// 合併後的頭節點
let merge_tail = null
// 合併後的尾節點
let curr1 = head1 // 游標
let curr2 = head2
while(curr1&&curr2) else
// 想合併的鍊錶新增節點
if(merge_head === null) else
// 判斷是否有剩餘的部分
let res_link = null
if(curr_1)
if(curr_2)
while (res_link)
}return merge_head
}複製**
鍊錶還有很多其他會被問道的問題比如:
查詢單鏈表中的倒數第k個節點(k > 0):定義兩個游標都指向head,先讓其中乙個走k步,然後兩個一起走,當先走的走到盡頭時此時後走的所在的位置就是倒數第k個。
查詢單鏈表的中間結點:定義兩個節點k1、k2,k1一次走兩步,k2一次走一步,當k2走到盡頭時此時k1所在的位置中間節點。
實現雙向鍊錶:多了乙個前驅指標
在學習了鍊錶之後,發現鍊錶比佇列和棧更加困難,日後要多加複習和練習來鞏固學到的內容。
資料結構學習 鍊錶
將從下面4部分進行介紹 首先介紹鍊錶是什麼,然後介紹為什麼定義鍊錶,接著是鍊錶的分類,最後簡單介紹一下鍊錶結點的插入與刪除方法。首先,在介紹鍊錶之前,我們先介紹一下什麼是順序儲存結構。我們知道資料在計算機中的儲存就像貨物在倉庫中的儲存一樣,不但占用一定的空間,還要有乙個標示儲存位置的位址。計算機通過...
資料結構學習 鍊錶結構
儲存結構定義 struct node typedef struct node ptrtonode typedef ptrtonode list typedef ptrtonode position struct node 書寫 package thedatastructureaboutlinked ...
資料結構學習筆記 鍊錶
表示式的計算 表示式的計算涉及到棧的操作 對於表示式 a b c d e f 演算法 用到兩個棧,分別是符號棧和運算元棧。輸入表示式時,為了表示表示式輸入完畢,在表示式的最後加上 號,也就是說輸入的表示式為 a b c d e f 首先設定各個符號的優先順序,和 的優先順序為0,也就是最低的 和 的...