1. 程式人生 > >資料結構與算法系列14-跳錶

資料結構與算法系列14-跳錶

注:文章出現的log2n中的2表示以2為底,這裡是為了方便這樣寫

什麼是跳錶?

為一個值有序的單鏈表建立多級索引,比如每2個節點提取一個節點到上一級,我們把抽出來的那一級叫做索引或索引層。如下圖所示,其中down表示down指標,指向下一級節點。以此類推,對於節點數為n的連結串列,大約可以建立log2n-1級索引。像這種為單鏈表建立多級索引的資料結構就稱為跳錶。
在這裡插入圖片描述

跳錶的時間複雜度(O(logn))

1.計算跳錶的高度
如果連結串列有n個節點,每2個節點抽取抽出一個節點作為上一級索引的節點,那第1級索引的節點個數大約是n/2,第2級索引的節點個數大約是n/4,依次類推,第k級索引的節點個數就是n/(2^k)。
假設索引有h級別,最高階的索引有2個節點,則有n/(2^h)=2,得出h=log2n-1,包含原始連結串列這一層,整個跳錶的高度就是log2n。
2.計算跳錶的時間複雜度


假設我們在跳錶中查詢某個資料的時候,如果每一層都遍歷m個節點,那在跳錶中查詢一個數據的時間複雜度就是O(m*logn)。那這個m是多少呢?如下圖所示,假設我們要查詢的資料是x,在第k級索引中,我們遍歷到y節點之後,發現x大於y,小於後面的節點z,所以我們通過y的down指標,從第k級下降到第k-1級索引。在第k-1級索引中,y和z之間只有3個節點(包含y和z),所以,我們在k-1級索引中最多隻需要遍歷3個節點,以此類推,每一級索引都最多隻需要遍歷3個節點。所以m=3。
因此在跳錶中查詢某個資料的時間複雜度就是O(logn)。這個查詢的時間複雜度跟二分查詢是一樣的。

跳錶的空間複雜度及其優化

1.計算空間複雜度
如果連結串列有n個節點,每2個節點抽取抽出一個節點作為上一級索引的節點,那每一級索引的節點數分別為:n/2,n/4,n/8,…,8,4,2,等比數列求和n-1,所以跳錶的空間複雜度為O(n)
2.優化空間複雜度
如果連結串列有n個節點,每3或5個節點抽取抽出一個節點作為上一級索引的節點,那每一級索引的節點數分別為(以3為例):n/3,n/9,n/27,…,27,9,3,1,等比數列求和n/2,儘管跳錶的空間複雜度還是O(n),但是和每2個節點抽取一次相比,要減少了一半的索引結點儲存空間。
而且在實際的軟體開發中,我們往往不必太在意索引佔用的儲存空間。因為在實際的軟體開發中,原始連結串列中儲存的有可能是很大的物件,而索引結點只需要儲存關鍵值和幾個指標,並不需要儲存物件,所以當物件比索引結點大很多時,那索引佔用的額外空間就可以忽略了。

跳錶支援高效的動態插入和刪除

因為跳錶本質上就是連結串列,所以它的插入和刪除操時間複雜度就為O(1),但在實際情況中,要插入或刪除某個節點,需要先查詢到指定位置,而這個查詢操作比較費時,但在跳錶中這個查詢操作的時間複雜度是O(logn),所以,跳錶的插入和刪除操作的是時間複雜度也是O(logn)。

跳錶索引動態更新

如果我們不停的往跳錶裡插入資料,而不更新索引,就有可能出現兩個節點之間資料非常多的情況,極端情況下還可能退化為單鏈表。為了解決這樣的問題,我們需要進行索引的動態更新。具體如何做呢?當往跳錶中插入資料的時候,可以選擇同時將這個資料插入到部分索引層中。那麼如何選擇這個索引層呢?可以通過隨機函式來決定將這個節點插入到哪幾級索引中,比如隨機函式生成了值K,那就可以把這個節點新增到第1級到第K級索引中。
總結
跳錶使用空間換時間的設計思路,通過構建多級索引來提高查詢的效率,實現了基於連結串列的“二分查詢”。跳錶是一種動態資料結構,支援快速的插入、刪除、查詢操作,時間複雜度都是O(logn)。
跳錶的空間複雜度是O(n)。不過,跳錶的實現非常靈活,可以通過改變索引構建策略,有效平衡執行效率和記憶體消耗。雖然跳錶的程式碼實現並不簡單,但是作為一種動態資料結構,比起紅黑樹來說,實現要簡單多了。所以很多時候,我們為了程式碼的簡單、易讀,比起紅黑樹,我們更傾向用跳錶。