簡體   English   中英

范圍內的Kth最小值

[英]Kth minimum in a Range

給定一個整數數組和一些查詢操作。
查詢操作有兩種類型
1.將第i個索引的值更新為x。
2.給2個整數找到該范圍內的第k個最小值。(例如,如果2個整數是i和j,我們必須找出i和j之間的第k個最小值)。
我可以使用分段樹找到范圍最小查詢但是對於第k個最小值不能這樣做。 誰能幫我?

這是一個O(polylog n)每個查詢解決方案實際上不假設常數k ,因此k可以在查詢之間變化。 主要思想是使用一個分段樹,其中每個節點代表一個數組索引的間隔,並包含一個代表數組段中值的多集(平衡二叉搜索樹)。 更新操作非常簡單:

  1. 從葉子(您正在更新的數組索引)向上走分段樹。 您將遇到表示包含更新索引的數組索引間隔的所有節點。 在每個節點上,從多集中刪除舊值並將新值插入到多集中。 復雜性: O(log^2 n)
  2. 更新陣列本身。

我們注意到每個數組元素都在O(log n)集合中,因此總空間使用量為O(n log n) 通過多個集合的線性時間合並,我們也可以在O(n log n)構建初始段樹O(n log n)每個級別有O(n)工作)。

查詢怎么樣? 給出范圍[i, j]和秩k並且想要找到a[i..j]a[i..j]第k個最小元素。 我們怎么做?

  1. 使用標准段樹查詢過程查找查詢范圍的不相交的覆蓋范圍。 我們得到O(log n)不相交的節點,其多重集合的聯合正是查詢范圍中多個值的集合。 讓我們稱那些多重集合s_1, ..., s_mm <= ceil(log_2 n) )。 查找s_i需要O(log n)時間。
  2. s_1, ..., s_m的並集執行select(k)查詢。 見下文。

那么選擇算法如何工作呢? 有一個非常簡單的算法可以做到這一點。

我們有s_1, ..., s_nk給想要找最小的xa ,這樣s_1.rank(x) + ... + s_m.rank(x) >= k - 1 ,其中rank回報相應BBST中小於x的元素數量O(log n)如果我們存儲子樹大小,則可以在O(log n)實現)。 我們只是使用二進制搜索來找到x 我們遍歷根的BBST,做幾個排名查詢並檢查它們的總和是否大於或等於k 它是x的謂詞單調,因此二進制搜索有效。 答案是任何s_ix的后繼者的最小值。

復雜性 :每次查詢O(n log n)預處理和O(log^3 n)

因此,對於q查詢,我們總共得到O(n log n + q log^3 n)的運行時。 我確信我們可以通過更聰明的選擇算法將其降低到O(q log^2 n)

更新:如果我們正在尋找可以一次處理所有查詢的離線算法,我們可以使用以下算法得到O((n + q) * log n * log (q + n))

  • 預處理所有查詢,創建一組在數組中發生的所有值。 這些數量最多為q + n
  • 構建一個分段樹,但這次不在數組上,而是在可能的值集上。
  • 段樹中的每個節點都表示值的間隔,並維護一組出現這些值的位置。
  • 要回答查詢,請從段樹的根開始。 檢查根的左子節點中有多少位置位於查詢間隔中(我們可以通過在位置的BBST中進行兩次搜索來實現)。 讓那個數字是m 如果k <= m ,則遞歸到左邊的孩子身上。 否則遞歸到正確的孩子, k減少m
  • 對於更新,從O(log (q + n))節點中移除覆蓋舊值的位置,並將其插入到覆蓋新值的節點中。

這種方法的優點是我們不需要子樹大小,因此我們可以使用平衡二叉搜索樹的大多數標准庫實現(例如,在C ++中set<int> )來實現這一點。

我們可以通過將片段樹更改為權重平衡樹(例如BB [α]樹)來將其轉換為在線算法。 它具有與其他平衡二叉搜索樹相同的對數運算,但允許我們通過將重建成本計入必然導致不平衡的操作來重建不平衡時從頭開始重建整個子樹。

如果這是編程競賽問題,那么您可能能夠使用以下O(n log(n)+ qn ^ 0.5 log(n)^ 1.5)-time算法。 它被設置為使用C ++ STL並且具有比Niklas(之前的?)答案更好的大O常數,因為它使用了更少的空間和間接。

將數組划分為長度為n / k的k個塊。 將每個塊復制到第二個陣列的相應位置並對其進行排序。 要更新:將更改的塊復制到第二個數組並再次排序(時間O((n / k)log(n / k))。要查詢:復制到臨時數組最多2(n / k - 1)屬於與查詢間隔部分重疊的塊的元素。對它們進行排序。使用此問題的答案之一從時間O中選擇已排序的臨時數組和完全重疊的塊的並集中所請求的排名的元素(k log(n / k)^ 2)。理論上k的最優設置是(n / log(n))^ 0.5。使用Frederickson和Johnson的復雜算法可以削減另一個log(n)^ 0.5 。

執行存儲桶排序的修改:創建一個包含所需范圍內的數字的存儲桶,然后僅對該存儲桶進行排序並找到第k個最小值。

該死的,這個解決方案無法更新元素但至少找到了第k個元素,在這里你會得到一些想法,這樣你就可以想到一些提供更新的解決方案。 嘗試基於指針的B樹。

這是O(n log n)空間和O(q log ^ 2 n)時間復雜度。 后來我用每個查詢的O(log n)解釋了相同的內容。

所以,你需要做下一個:

1)在給定數組上創建“分段樹”。

2)對於每個節點,您將存儲整個數組,而不是存儲一個數字。 該數組的大小必須等於它的子數。 該數組(如您所猜測的)必須包含底部節點(子節點或該節點中的數字)的值,但已排序。

3)要制作這樣的數組,你可以從它的兩個兒子合並來自分段樹的兩個數組。 但不僅如此,對於你剛剛制作的數組中的每個元素(通過合並),你需要記住數字在合並數組中插入之前的位置(基本上,它來自的數組,並在其中定位) 。 以及指向未從同一數組插入的第一個下一個元素的指針。

4)使用此結構,您可以在某些段S中檢查有多少數量低於給定值x的數字。您可以找到(使用二進制搜索)根節點數組中的第一個數字> = x。 然后,使用您所做的指針,您可以在O(1)中找到兩個子數組(作為前一個節點的子節點的節點數組)的相同問題的結果。 您停止為每個節點操作此降序,該節點表示在給定段S內部或​​外部的整個段。時間復雜度為O(log n):O(log n)以查找> = x的第一個元素,和O(log n)的所有S分解段。

5)對解決方案進行二進制搜索。

這是每個查詢具有O(log ^ 2 n)的解決方案。 但是你可以減少到O(log n)

1)在完成上面所寫的所有操作之前,您需要轉換問題。 您需要對所有數字進行排序並記住原始數組中每個數字的位置。 現在這些位置代表您正在處理的陣列。 調用該數組P.

如果查詢段的邊界是a和b。 你需要找到P中的第k個元素,它是a和b之間的值(而不是索引)。 該元素表示原始數組中結果的索引。

2)為了找到第k個元素,你會做一些復雜度為O(log n)的反向跟蹤。 您將詢問索引0和(某些其他索引)之間的元素數量,這些元素在a和b之間除以值。

3)假設你知道某個段(0,h)的這個問題的答案。 從最大的一個開始,為從h開始的樹中的所有段獲得相同類型問題的答案。 只要當前答案(來自段(0,h))加上你最后得到的答案大於k,就繼續得到那些答案。 然后更新h。 繼續更新h,直到樹中只有一個以h開頭的段。 那個h是你在所述問題中尋找的數字的索引。

要從樹中獲得某個段的問題的答案,您將花費恰好O(1)的時間。 因為你已經知道它的父節點的答案,並且使用我在第一個算法中解釋的指針,你可以得到O(1)中當前段的答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM