連結串列中的跳錶小結
連結串列之所 以訪問中間節點的效率低,就是因為每個節點只儲存了下一個節點的指標,要沿著這個指標
遍歷每個後續節點才能到達中間節點。那如果我們在節點上增加一個指標,指向更遠的節
點,比如說跳過後一個節點,直接指向後面第二個節點,那麼沿著這個指標遍歷,是不是遍
歷速度就翻倍了呢?
同理,如果我們能增加更多的指標,提供不同步長的遍歷能力,比如一次跳過 4 個節點,
甚至一半的節點,那我們是不是就可以更快速地訪問到中間節點了呢?
這當然是可以實現的。我們可以為連結串列的某些節點增加更多的指標。這些指標都指向不同距
離的後續節點。這樣一來,連結串列就具備了更高效的檢索能力。這樣的資料結構就是
跳錶
(Skip List)。
一個理想的跳錶,就是從連結串列頭開始,用多個不同的步長,每隔 2^n 個節點做一次直接鏈
接(n 取值為 0,1,2……)。跳錶中的每個節點都擁有多個不同步長的指標,我們可以在
每個節點裡,用一個陣列 next 來記錄這些指標。next 陣列的大小就是這個節點的層數,
next[0]就是第 0 層的步長為 1 的指標,next[1]就是第 1 層的步長為 2 的指標,next[2]就
是第 2 層的步長為 4 的指標,依此類推。你會發現,不同步長的指標,在連結串列中的分佈是
非常均勻的,這使得整個連結串列具有非常平衡的檢索結構。
舉個例子,當我們要檢索 k=a
6
時,從第一個節點 a
1
開始,用最大步長的指標開始遍歷,直
接就可以訪問到中間節點 a
5
。但是,如果沿著這個最大步長指標繼續訪問下去,下一個節
點是大於 k 的 a
9
,這說明 k 在 a
5
和 a
9
之間。那麼,我們就在 a
5
和 a
9
之間,用小一個級別
的步長繼續查詢。這時候,a
5
的下一個元素是 a
7
,a
7
依然大於 k 的值,因此,我們會繼續
在 a
5
和 a
7
之間,用再小一個級別的步長查詢,這樣就找到 a
6
了。這個過程其實就是二分查
找。時間代價是 O(log n)。
跳錶的檢索空間平衡方案
不知道你有沒有注意到,我在前面強調了一個詞,那就是“理想的跳錶”。為什麼要叫
它“理想”的跳錶呢?難道在實際情況下,跳錶不是這樣實現的嗎?的確不是。當我們要在
跳錶中插入元素時,節點之間的間隔距離就被改變了。如果要保證理想連結串列的每隔 2^n 個
節點做一次連結的特性,我們就需要重新修改許多節點的後續指標,這會帶來很大的開銷。
所以,在實際情況下,我們會在檢索效能和修改指標代價之間做一個權衡。為了保證檢索性
能,我們不需要保證跳錶是一個“理想”的平衡狀態,只需要保證它在大概率上是平衡的就
可以了。因此,當新節點插入時,我們不去修改已有的全部指標,而是僅針對新加入的節點
為它建立相應的各級別的跳錶指標。具體的操作過程,我們一起來看看。
首先,我們需要確認新加入的節點需要具有幾層的指標。我們通過隨機函式來生成層數,比
如說,我們可以寫一個函式 RandomLevel(),以 (1/2)^n 的概率決定是否生成第 n 層。這
樣,通過簡單的隨機生成指標層數的方式,我們就可以保證指標的分佈,在大概率上是平衡
的。
在確認了新節點的層數 n 以後,接下來,我們需要將新節點和前後的節點連線起來,也就
是為每一層的指標建立前後連線關係。其實每一層的指標連結,你都可以看作是一個獨立的
單連結串列的修改,因此我們只需要用單連結串列插入節點的方式完成指標連線即可。
這麼說,可能你理解起來不是很直觀,接下來,我通過一個具體的例子進一步給你解釋一
下。
我們要在一個最高有 3 層指標的跳錶中插入一個新元素 k,這個跳錶的結構如下圖所示。
假設我們通過跳錶的檢索已經確認了,k 應該插入到 a
6
和 a
7
兩個節點之間。那接下來,我
們要先為新節點隨機生成一個層數。假設生成的層數為 2,那我們就要修改第 0 層和第 1
層的指標關係。對於第 0 層的連結串列,k 需要插入到 a
6
和 a
7
之間,我們只需要修改 a
6
和 a
7
的第 0 層指標;對於第 1 層的連結串列,k 需要插入到 a
5
和 a
7
之間,我們只需要修改 a
5
和 a
7
的第 1 層指標。這樣,我們就完成了將 k 插入到跳錶中的動作。
通過這樣一種方式,我們可以大大減少修改指標的代價。當然,由於新加入節點的層數是隨
機生成的,因此在節點數目較少的情況下,如果指標分佈的不合理,檢索效能依然可能不
高。但是當節點數較多的時候,指標會趨向均勻分佈,查詢空間會比較平衡,檢索效能會趨
向於理想跳錶的檢索效率,接近 O(log n)
相關文章
- 跳錶 | 會跳的連結串列真的非常diao
- 資料結構:跳躍連結串列資料結構
- 連結串列中環的入口結點
- 連結串列-迴圈連結串列
- 連結串列-雙向連結串列
- 連結串列 - 單向連結串列
- 【連結串列問題】刪除單連結串列的中間節點
- 876. 連結串列的中間結點
- 連結串列4: 迴圈連結串列
- 連結串列-單連結串列實現
- 連結串列-雙向通用連結串列
- 資料結構-單連結串列、雙連結串列資料結構
- [連結串列]leetcode1019-連結串列中的下一個更大節點LeetCode
- 反轉連結串列、合併連結串列、樹的子結構
- 連結串列-雙向非通用連結串列
- 【LeetCode】->連結串列->通向連結串列自由之路LeetCode
- 連結串列入門與插入連結串列
- Leetcode_86_分割連結串列_連結串列LeetCode
- 跳錶--怎麼讓一個有序連結串列能夠進行"二分"查詢?
- 結點插入到單連結串列中
- C++連結串列小冊子C++
- 連結串列
- 資料結構之跳錶資料結構
- 【連結串列問題】打卡3:刪除單連結串列的中間節點
- LeetCode-Python-86. 分隔連結串列(連結串列)LeetCodePython
- 連結串列的ADT
- oracle 左錶連結Oracle
- 資料結構實驗之連結串列七:單連結串列中重複元素的刪除資料結構
- JZ-055-連結串列中環的入口結點
- 資料結構實驗之連結串列三:連結串列的逆置資料結構
- 資料結構實驗之連結串列五:單連結串列的拆分資料結構
- 資料結構實驗之連結串列六:有序連結串列的建立資料結構
- 資料結構之連結串列篇(單連結串列的常見操作)資料結構
- 單連結串列建立連結串列出現問題
- 演算法題中的連結串列演算法
- [連結串列】2.輸入一個連結串列,反轉連結串列後,輸出新連結串列的表頭。[多益,位元組考過]
- 資料結構之連結串列:206. 反轉連結串列資料結構
- 資料結構與演算法——連結串列 Linked List(單連結串列、雙向連結串列、單向環形連結串列-Josephu 問題)資料結構演算法