1. 程式人生 > >利用劃分樹求解整數區間內第K大的值

利用劃分樹求解整數區間內第K大的值

  如何快速求出(在log2n的時間複雜度內)整數區間[x,y]中第k大的值(x<=k<=y)?

  其實我剛開始想的是用快排來查詢,但是其實這樣是不行的,因為會破壞原序列,就算另外一個數組來儲存,多次詢問的條件下,同樣滿足不了。

  劃分樹主要是針對上述問題而設計的。 劃分樹是一種基於線段樹的資料結構,其基本思想就是將待查詢的區間分為兩個子區間:不大於數列中間 值的元素被分配到左兒子的子區間,簡稱左子區間;大於數列中間值的元素被分配到右兒子的子區間,簡稱右子區間。

  顯然,左子區間的數小於右子區間的數。建樹 的時候要注意,對於被分到同一子樹的元素,元素間的相對位置不能改變。例如構建整數序列[1 5 2 3 6 4 7 3 0 0]的劃分樹:

  整數序列經排序後得到[0 0 1 2 3 3 4 5 6 7],中間值是3。劃分出下一層的左子區間[1 2 3 0 0],中間值為1;下一層的由子區間[5 6 4 7 3],中間值為5;以中間值為界,劃分出當前層每個區間的下層左右子區間······以此類推,直至劃分出所有子區間含單個整數為止。

  這裡可能有人就會提問了,為什麼兩個子區間是這樣的呢??  其實我剛開始也這麼想,下面給出解釋:

1.如果有和中間值相等的元素,插入進去的前提是  小於中間值的元素不夠填滿一個區間,差多少就補多少和中間值相等的元素(很好理解,小於肯定優先嘛)。

2.相對位置不能改變。

下面給出一個圖,還是以[1 5 2 3 6 4 7 3 0 0]為例:

如圖可見,劃分樹是一顆完全二叉樹,樹葉為單元素區間。若數列含有n個整數,則對應的劃分樹有[log 2 n]+1層。

查詢的時候通過記錄進入左子樹的數的個數,確定下一個查詢區間,直至查詢範圍縮小到單元素為止。  此時,區間內的第K大值就找到了。

  整個演算法分兩步:

1.建樹-------離線構建整個查詢區間的劃分樹

2.查詢-------在劃分樹中查詢某個子區間中的第K大值。

  在查詢之前,我們先離線構建整個查詢區間的劃分樹。  建樹過程比較簡單,對於區間[l,r],首先通過對原數列排序找到這個區間的中間值的位置mid(mid=[(l+r)/2]),不大於中間值的數劃入左子樹[l,mid], 大於中間值的數劃入右子樹[mid+1,r] 。同時,對於第i 個數,記錄在[l,i] 區間內有多少整數被劃入左子樹。  最後繼續對它的左子樹[l,mid]  和右子樹[mid+1,r]  遞迴建樹,直至劃分出最後一層的葉節點為止,顯然,建樹過程是自上而下的。

  具體實現辦法:

  先將讀入的資料排序成sorted[]   (從小到大排序) ,取區間[l,r]的中間值sorted[mid],然後遍歷[l,r]區間,依次將每個整數劃分到左子區間或者右子區間中去。  注意,被分到每個子樹的整數是相對有序的。  注意,這裡要記錄區間能插入多少個和中間值相等的元素。    另外,在這個過程中,要記錄一個類似字首和的東西,即  l  到  i  這個區間內有多少整數被劃分到左子樹。設:

tree [p][i] ------第p層第i個位置的整數值,初始序列為tree[0][]。

sorted[]---------排序序列,即儲存tree[0][]排序後的結果

toleftp[][]--------toleft[p][i],表示第p層前i