Linux查詢某個指定時間要求的檔案
跳錶(Skip List)是一種隨機化的資料結構,插入、刪除、查詢的複雜度均為O(logN)
簡單說來跳錶也是連結串列的一種,只不過它在連結串列的基礎上增加了跳躍功能,正是這個跳躍的功能,使得在查詢元素時,跳錶能夠提供O(logN)的時間複雜度
基本性質
1.由很多層結構組成
2.每一層都是一個有序的連結串列
3.最底層(Level 1)的連結串列包含所有元素
4.如果一個元素出現在 Level i 的連結串列中,則它在 Level i 之下的連結串列也都會出現
5.每個節點包含兩個指標,一個指向同一連結串列中的下一個元素,一個指向下面一層的元素
跳錶最底層是一個全量的有序連結串列,跳錶是可以實現二分查詢的有序連結串列
如下圖所示,查詢路徑:1、7、9、10,找 10 的效率更高了,這就是跳錶的思想,用“空間換時間”,通過給連結串列建立索引,提高了查詢的效率
插入節點
插入操作了,基本上兩步操作就可以實現:在最底層的資料鏈表中插入資料,然後調整索引
其中每一層的索引連結串列中是否需要增加新增的節點,其實並沒有什麼標準答案,我們儘量做到索引的平均分佈即可,常用的就是隨機判斷決定是否需要新增或調整索引,當有新節點插入的時候,通過概率演算法判斷這個節點需要插入到幾級節點中
比如:
底層資料鏈表有 N 個元素,隨機選擇 N/2 個元素作為 1 級索引,隨機選擇 N/4 個元素作為 2 級索引…一直到頂層索引
新插入資料節點,1/2 概率不插入任何一級索引,1/4 概率返回需要插入 1 級索引,1/8 概率返回需要插入到 2 級索引,以此類推
這裡要注意一點,插入 2 級索引的時候,同時也需要插入 1 級索引;也就是插入 n 級索引的時候,同時也要插入 1~( n-1 ) 級索引
時間複雜度
查詢元素的過程是從最高階索引開始,一層一層遍歷最後下沉到原始連結串列,所以,時間複雜度 = 索引的高度 * 每層索引遍歷元素的個數
先來求跳錶的索引高度
假設每兩個結點會抽出一個結點作為上一級索引的結點,原始的連結串列有n個元素,則一級索引有n/2 個元素、二級索引有 n/4 個元素、k級索引就有 n/2k個元素,最高階索引一般有2個元素,所以 h = log2n - 1,最高階索引 h 為索引層的高度加上原始資料一層,跳錶的總高度 h = log2n
當每級索引都是兩個結點抽出一個結點作為上一級索引的結點時,每一層最多遍歷3個結點
跳錶的索引高度 h = log2n,且每層索引最多遍歷 3 個元素,所以跳錶中查詢一個元素的時間複雜度為 O(3*logn)
,省略常數即:O(logn)
空間複雜度
跳錶通過建立索引,來提高查詢元素的效率,就是典型的“空間換時間”的思想
假如原始連結串列包含 n 個元素,則一級索引元素個數為 n/2、二級索引元素個數為 n/4、三級索引元素個數為 n/8 以此類推,所以,索引節點的總和是:n/2 + n/4 + n/8 + … + 8 + 4 + 2 = n-2
,空間複雜度是 O(n)
和平衡樹、雜湊表比較
雜湊表不是有序的,因此,在雜湊表上只能做單個key的查詢,不適宜做範圍查詢,所謂範圍查詢,指的是查詢那些大小在指定的兩個值之間的所有節點
在做範圍查詢的時候,平衡樹比SkipList操作要複雜,在平衡樹上,我們找到指定範圍的小值之後,還需要以中序遍歷的順序繼續尋找其它不超過大值的節點,如果不對平衡樹進行一定的改造,這裡的中序遍歷並不容易實現,而在SkipList上進行範圍查詢就非常簡單,只需要在找到小值之後,對第1層連結串列進行若干步的遍歷就可以實現
平衡樹的插入和刪除操作可能引發子樹的調整,邏輯複雜,而SkipList的插入和刪除只需要修改相鄰節點的指標,操作簡單又快速
同理紅黑樹的範圍查詢也沒有跳躍表的效率高,跳錶也不需要維護平衡性
使用場景
java.util.concurrent
下的ConcurrentSkipListMap()
Redis sorted set的內部使用HashMap和跳躍表(SkipList)來保證資料的儲存和有序,HashMap裡放的是成員到score的對映,而跳躍表裡存放的是所有的成員,排序依據是HashMap裡存的score,使用跳躍表的結構可以獲得比較高的查詢效率,並且在實現上比較簡單。
那為什麼Redis的作者使用 SkipList 結構而不是紅黑樹?
- 紅黑樹:紅黑樹的查詢效率很高,但是在進行重新平衡時,會涉及到大量節點的變化,因此實現和操作起來都比較複雜。
- 跳躍表:通過簡單的多層索引結構,實現簡單,且能達到近似於紅黑樹的查詢效率,插入節點(多層插入)不需要像紅黑樹那樣有額外操作。而且跳躍表還能實現範圍查詢及輸出,而紅黑樹只支援單個元素查詢,對於範圍查詢效率低
按照區間來查詢資料這個操作,紅黑樹的效率沒有跳錶高。對於按照區間查詢資料這個操作,跳錶可以做到 O(logn) 的時間複雜度定位區間的起點,然後在原始連結串列中順序往後遍歷就可以了。這樣做非常高效