資料結構與演算法 跳表的實現

2021-10-04 17:56:25 字數 3370 閱讀 6449

我們知道redis、leveldb 都是著名的 key-value 資料庫,redis中 的 sortedset以及leveldb 中的 memtable 都用到了跳表,那麼什麼是跳表呢?跳表又是如何實現的呢?

說跳表之前,先說說有序鍊錶:

乙個有序鍊錶搜尋、新增、刪除的平均時間複雜度是都是o(n)。有序陣列的隨機訪問時間複雜度為o(1),在查詢某個特定的元素的時候能夠進行二分搜尋優化,時間複雜度為o(logn)。因此有序鍊錶的訪問效率低於有序資料。那麼就需要有某種方法來讓有序鍊錶搜尋、新增、刪除的平均時間複雜度降低至 o(logn),跳表這種方法就出現了。

跳表,又叫做跳躍表、跳躍列表,是在有序鍊錶的基礎上增加了「跳躍」的功能,它是由william pugh於2023年發布的,設計的初衷是為了取代平衡樹(比如紅黑樹)。

跳表的結構如下:

從圖中可以看到, 跳躍表主要由以下部分構成:

對比於平衡樹,跳表有以下優點:

① 從收割head節點的有效層數的最高層開始,從左往右搜尋,直至找到乙個大於或等於目標的元素,或者到達當前層鍊錶的尾部。

② 如果該元素等於目標元素,則表明該元素已被找到。

③ 如果該元素大於目標元素或已到達鍊錶的尾部,則退回到當前層的前乙個元素,然後轉入下一層進行搜尋。

比如要查詢17這個數,先從頭結點的頂層開始找,找到21,大於17,則返回頭結點轉入下一層,找到9,小於17,則從9向右查詢到21,大於17,則返回上一節點9,轉入下一層,開始搜尋,發現9的下乙個是17,說明被找到。

① 根據跳表搜尋的方式確定節點新增的位置

② 隨機決定新新增元素的層數

例如,我們需要新增15這個元素:

① 根據跳表搜尋的方式確定節點刪除的位置,如果已經存在這個節點,則刪除

② 刪除乙個元素後,這個元素的所有前驅節點指向這個節點的所有後繼節點

③ 刪除乙個元素後,整個跳表的層數可能會降低

例如,我們需要新增9這個元素:

) //返回舊的值

public v put(k k, v v)

if(comp == 0)

pres[i]

=node;

} //新節點層數

int randomlevel = randomlevel();

//新節點

node newnode = new node<

>

(k, v, randomlevel)

;for

(int i = 0; i

else

} size++;

// 計算跳表的最終層數

level=math.max(level,randomlevel)

;return null;

} /**

* 仿redis構造隨機層數

** @return

*/private int randomlevel(

) return level;

}public v get(k k)

if(comp == 0)

return node.nexts[i].value;

}return null;

} public v remove(k k)

if(comp == 0)

} if(exist)

}return oldvalue;

} public int compare(k k1, k k2)

private void checkkey(k k)

private static class node

@override

public string tostring(

)"+nexts.length;}}

@override

public string tostring(

)"\n");

}return sb.tostring();

}}對跳表和二插搜尋樹treemap同時插入100萬條資料,測試效率。

跳表:

public static void main(string[

] args)

//刪除

for(int i = 0; i < count; i++)

long end = system.currenttimemillis();

double duration = end - begin;

system.out.println(

"耗時:" + duration / 1000.0 + "s(" + duration + "ms)");

}

結果:

新增資料   耗時:0.395s(395.0ms)

刪除資料 耗時:0.014s(14.0ms)

平衡二叉搜尋樹treemap:

public static void main(string[

] args)

//刪除資料

for(int i = 0; i < count; i++)

long end = system.currenttimemillis();

double duration = end - begin;

system.out.println(

"耗時:" + duration / 1000.0 + "s(" + duration + "ms)");

}

新增資料結果:

新增資料   耗時:0.339s(339.0ms)

刪除資料 耗時:0.024s(24.0ms)

總結:由上面的實驗得知,跳表和平衡二叉搜尋樹的增、刪、查的效率差不多。

資料結構與演算法 跳表

回顧上節 上節課中我們學習了二分法查詢,最基本的二分法查詢需要隨機的訪問資料,底層都是基於陣列的儲存結構 1 思考問題,如果底層是基於鍊錶的方式儲存資料.是否能用二分法查詢呢?我們只要對陣列進行稍微改造,基於鍊錶實現,並在鍊錶的基礎上分別建立對於的索引,就可以快速的基於鍊錶的方式進行查詢,而且該種方...

資料結構與演算法 跳表

二分查詢利用靜態陣列隨機訪問的特性,可以實現在有序的陣列中快速找到某個值,但是因為靜態陣列需要申請連續的記憶體空間,所以當資料規模比較大時,在記憶體中可能無法申請到所需的連續空間。因此,基於這一特性,我們考慮能否將二分查詢應用於鍊錶結構,這樣就避免連續空間的限制,但是對於鍊錶結構,怎樣提高它的查詢效...

資料結構與演算法 跳表

解決鍊錶查詢時耗時過長的問題。英文全稱 skip list 鍊錶 多級索引 鍊錶 跳表 顧名思義,跳表的查詢是在多個鍊錶之間跳躍查詢的,其路線類似於走台階,如下圖所示 舉個栗子 某一時刻,想查詢代號為 8 的節點的資料,按照常規鍊錶查詢,需要從最左側挨個查詢至最右側,遍歷次數為 8 時間複雜度為 o...