你們想必都知道,數組和鏈表的搜索操做的時間複雜度都是O(N)的,在數據量大的時候是很是耗時的。對於數組來講,咱們能夠先排序,而後使用二分搜索,就可以將時間複雜度下降到O(logN),可是有序數組的插入是一個O(N)級別的操做。而鏈表的插入性能相對優秀,卻不能使用二分搜索快速查詢。那麼是否有一種數據結構,即可以像鏈表同樣快速插入數據,又支持相似於二分搜索這樣的查詢算法呢?答案是確定的。William Pugh教授在1990發表的論文《Skip Lists: A Probabilistic Alternative to Balanced Trees》中提出的跳錶就是這樣一種有趣的數據結構。java
跳錶的核心思想是經過創建索引層來縮短鏈表的搜索路徑,以達到快速搜索的目的。
假設咱們從鏈表中的每兩個節點中提取出一個創建一級索引,而後再從每兩個一級索引中提取一個創建二級索引,以此類推,就能夠獲得以下圖所示的結構,其中綠色節點表示索引。 node
在William Pugh的論文中使用了數組加鏈表的組合來實現跳錶,就如上圖所示,每一列索引具備相同的key,使用一個數組來表示。還可使用純鏈表的形式來實現跳錶,我以爲這種方式更有助於理解跳錶的原理,以下圖所示。 git
跳錶的搜索須要從高層索引開始向下逐層搜索,每一層的搜索方式和普通鏈表是同樣的,當後繼節點的關鍵字大於搜索關鍵字時結束本層的搜索,進入下一層繼續搜索。下圖展現了跳錶搜索關鍵字 22 的過程,其中紅色部分就是搜索的路徑。 github
從上圖能夠很直觀的看出,跳錶的搜索和二分搜索是同樣的,其時間複雜度也是O(logN)的,咱們不妨簡單證實一下。跳錶的多層索引結構使它的搜索方式很是靈活且強大
好比咱們可能有這樣的需求,若是key不存在,咱們須要知道這個key鄰近的nearKey是什麼,這用跳錶很容易實現算法
跳錶還能夠很容易的搜索一個關鍵字區間[fromKey, toKey]
,這點和B+樹很相似,先搜索fromKey,而後向後遍歷鏈表,取出全部小於等於toKey的數據便可數組
到如今爲止,本文描述的都是理想狀態下的跳錶,事實上,咱們不會嚴格的爲跳錶的每m個低級索引創建高級索引,由於這樣作複雜並且低效。因此William Pugh在他的論文中採用一種隨機算法來爲每一個新增的節點隨機創建索引,下面是我用Java實現的版本。安全
int randomLevel(int m, int maxLevel) {
ThreadLocalRandom r = ThreadLocalRandom.current();
int level = 1;
while (r.nextInt(m) == 0 && level < maxLevel)
level++;
return level;
}
複製代碼
經過這種隨機算法,生成第i級索引的機率爲
因此可以保證每一層索引的數量都接近於 ,這正好符合咱們前面提到的索引層的性質。
Doug Lea大佬在Java的ConcurrentSkipListMap
中使用了另一種更加炫酷的隨機算法的實現方式,使用隨機數末尾連續爲1的位數做爲索引的等級,顯然這種方式生成第i級索引的機率爲 ,代碼以下所示。數據結構
int rn = ThreadLocalRandom.current().nextInt();
// 只有最高位和最低位都爲0時,才創建索引,至關於爲4個node創建一個索引
if ((rn & 0x80000001) == 0) {
int level = 1;
// 創建索引的等級等於rn末尾連續爲1的位數
while (((rn >>>= 1) & 1) != 0)
level++;
}
複製代碼
經過隨機函數生成一個隨機的索引等級以後,建立一個新的索引列,並將每一層的新索引連接到它的前驅索引的後面,若是生成的隨機等級大於當前跳錶的最大索引等級,須要添加一層新的索引。以下圖所示,其中紅色虛線箭頭表示從新創建的連接。 dom
跳錶的刪除操做比較簡單,先查詢刪除的關鍵字,若是在索引層匹配到了關鍵字,就向下刪除全部的索引和數據節點,若是沒有匹配到索引,只須要刪除數據節點便可。其中有一點須要注意的是,在刪除索引後須要檢測一下,若是當前層的HEAD索引的後繼索引爲NIL,則表示這一層已經沒有索引了,須要刪除這個索引層。以下圖所示,紅色箭頭表示從新創建的連接。 函數
跳錶的實現相對AVL樹、紅黑樹等平衡二叉樹來講簡單了不少,William Pugh的論文《Skip Lists: A Probabilistic Alternative to Balanced Trees》中提供了使用數組加鏈表實現跳錶的僞代碼,我寫了一個Java版本的純鏈表實現的跳錶,並上傳到了個人GitHub上,有興趣的朋友能夠看一下。若是你須要在開發中使用跳錶的話,java.util.concurrent.ConcurrentSkipListMap
是一個強大的實現,並且它仍是線程安全的。