我們知道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...