本節課介紹了一種全新的資料結構——跳躍表
跳躍表是一種簡單又有趣的動態搜尋資料結構,其主要優點在於其易於實現,而且很好的保證了其具有高效的效能,即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分鐘的時間來證明,具體證明方式我們這裡略過(其實是我根本沒看懂其證明過程 逃~~)
第十二課 OpenGL擴充套件
gpu的效能隨著更新換代一直在提高,支援渲染更多的三角形和畫素點。然而,原始效能不是我們唯一關心的。nvidia,amd和intel也通過增加功能來改善他們的顯示卡。來看一些例子。回溯到2002年,gpu都沒有頂點著色器或片斷著色器 所有的一切都硬編碼在晶元中。這被稱為固定功能流水線 fixed f...
演算法導論第十二章筆記
這裡的二叉樹型別分別包含關鍵字key,left right parent 指標分別指向左孩子 右孩子 雙親。若對應節點不存在就用nullptr空指標代替。這裡的二叉搜尋樹,對於任何節點x,其左子數的關鍵字最大不超過x key,其右子樹的關鍵字不低於x key。二叉樹遍歷有中序,先序,後序三種遍歷方式...
libcurl第十二課 記憶體分析
在實際的測試環境中,內存在不斷的增長,儘管不是很明顯 申請記憶體分析 struct curl multi curl multi handle int hashsize,socket hash int chashsize connection hash 該函式建立了curl muti結構體,並且申請了...