【演算法導論】 第十二課 跳躍表,演算法導論跳躍

來源:互聯網
上載者:User

【演算法導論】 第十二課 跳躍表,演算法導論跳躍

本節課介紹了一種全新的資料結構——跳躍表


跳躍表是一種簡單又有趣的動態搜尋資料結構,其主要優點在於其易於實現,而且很好的保證了其具有高效的效能,即2*O(lgn)的搜尋效能


在此之前我想首先談談鏈表,鏈表的優點在於其插入和刪除只需要常數項的時間(加上尋找該元素需要額外的O(n)時間),但是其尋找效率只有O(n),這裡順帶補充一下鏈表類的問題,以下先給出兩個BAT公司面試時熱衷於考的兩個鏈表經典問題:


1.如何快速尋找單向鏈表倒數第m個元素

2.如何快速判斷一個單向鏈表是否存在環


對於鏈表類問題,其核心思想不外乎兩點,1是開雙指標(甚至多指標),2是開雙鏈表(甚至多鏈表),其實以上兩個問題開雙指標便能巧妙地解決,第一個問題,先開一個指標走m步,然後再開一個指標同步走,當前一個指標走到鏈表末端時,後一個指標就正好指向倒數第m個元素了,第二個問題,開一個快指標和一個慢指標,快指標每次移動兩步,慢指標每次移動一步,如果存在環,那麼快指標一定會追到慢指標,可以想象兩個人在操場賽跑,快的人跑了很久之後會超慢的人一圈。


接下來我們繼續談跳躍表,其實跳躍表用到的就是第二個思路,開雙鏈表甚至多個鏈表


首先考慮建立兩個鏈表L1和L2,L1為快表,即只儲存部分元素,L2為慢表儲存全部元素,注意,以下提到的鏈表均為排好序的鏈表


當我們要搜尋某一元素時,我們先走快表,因為快表只保留了部分元素,所以是跳躍前進的,直到快表走過該元素,我們再退回快表前一個結點換到慢表繼續走,這樣效率顯然比在慢表上進行線性尋找要好一些,這裡的快慢表就像美國地鐵的快慢線地鐵一樣,快線的地鐵只在幾個站停頓,而慢線會在所有站停頓,乘客可以先乘快線到一個最接近目的地的前一個站,再轉乘慢線到達該地

那麼問題來了?

如何建表L1和 L2呢?L2無疑是一條包含所有結點的單向鏈表,那麼L1應該設定多少個結點最為合理呢,直觀上感受L1應該是均勻分布最好,那麼是以怎樣的密度分布最合適呢?


我們不難得出尋找時間上界為|L1|+|L2|/|L1|+轉乘的常數,這裡|L1|表示L1的長度(最壞情況就是L1走到末端後,走回一個節點然後進入L2,因為L2可以看做被L1分成了L1個段,所以每段長度為|L2|/|L1|,所以為|L1|+|L2|/|L1|+轉乘的常數),因為L2長度為n(包括整個鏈表),轉乘即鏈表L1向下走到L2的時間,為常數,所以我們的目標是要使得|L1|+n/|L1|最小,即|L1|為sqrt(n)時最優(可以求導得出或者通過其他數學方法,證明略),此時時間消耗為2*sqrt(n),即每隔sqrt(n)設立一個快表的結點,共sqrt(n)個快表結點

什嗎?sqrt(n)還不過癮?

那麼我們還能做怎樣的最佳化呢?答案是加更多的鏈表,我們看看三條鏈表應是多少,直覺告訴我們是3*n的1/3次方
其實,可以證明k條鏈表的時候為k*n的1/k次方

因為n是常數,那麼k多大比較合適呢?lgn! 讓我們看看k取lgn的時候為多少,即lgn*n^(1/lgn)為多少,即求lgn*n^logn(2),還記得我們計算遞迴時間複雜度時的換底公式嗎?這裡n^logn(2)即2^logn(n)即2^1,即2,所以整個時間複雜度為2*lgn,這是一個非常好的效能。

這種情況下的跳躍表稱為理想跳躍表,每一層數量減少一半,總共lgn層鏈表,從最上級鏈表開始搜,搜不到就向下,最多下logn層,每層最多搜2個元素,所以搜尋複雜度為O(2lgn)


那麼問題又來了,如何動態維護這樣一個跳躍表呢?

先看刪除功能,刪除功能只要從上級鏈表搜到之後,就可以直接刪除,並向下將所有鏈表的該結點都刪除,這個比較簡單,那麼插入呢?


插入(x) 先search(x)在底表的位置然後插入該元素,是否結束了呢?不,因為在某一段連續插入若干個結點後,這一段會變得非常長,整個跳躍表的平衡結構無疑會被打破,那麼如何維護理想線段表的結構呢?

1.保持每段之間的理想距離,如果距離過大,就從中間分割,然後將中點上升一層結點

這個方法從直觀上看非常巧妙,但是實行起來卻有一定難度,因為你必須即時記錄每一段的長度


2.採用我們最喜歡的隨機化演算法,拋硬幣 如果正面,就把這個結點提升一個level(即把該結點也加入上一級的鏈表中),再拋硬幣(看是否持續提升level),因為兩個相鄰鏈表的長度之比為1:2,而硬幣出正面的機率也是50%,事實證明這樣做是可行的,這裡值得一提的是,老師在這節課發了兩個硬幣給同學,一個利用拋硬幣產生隨機數,一個利用拋硬幣決定當前插入結點是否需要提升level,在課堂上直接做起了實驗,整個課程氛圍也很好,也讓同學們都對該演算法有了直觀的理解,這一種教學方式很值得借鑒


注意,這裡需要考慮一個特殊情況,就是當我們插入的元素為最小的元素時,如果它沒有提升一個level,那麼上級鏈表的開頭就不是第一個元素,這樣也會打亂整個跳躍表的理想結構,因此我們需要打個補丁:即把一個負無窮值插到所有鏈表頭,這樣就算插入了一個最小的元素也能保證每個表是以負無窮開始,即每個鏈表都可以從最左邊開始。

在課堂上,通過實驗表明演算法2似乎在平均情況下可以得到一個很好的跳躍表,其實不僅僅是平均情況可以得到一個好的跳躍表,在絕大多數情況都可以得到一個好的跳躍表.

可以證明,得到一個好的跳躍表的機率P>=1-O(1/n^a)  這裡a是一個介於0到1的參數,與n有關,在課堂的最後,老師花了盡20分鐘的時間來證明,具體證明方式我們這裡略過(其實是我根本沒看懂其證明過程 逃~~)

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.