[英]Efficient way to find the a-th to b-th smallest elements across k arrays
最近,我接受了一家社交媒體公司的采訪,被問到以下問題。
有k個長度為m的未排序數組。 目標是在給定a < b < m的情況下,以有效且記憶保守的方式在k個數組中找到第 a至第b的最小元素。 在后續問題中,將“未排序的數組”更改為MySQL數據庫中不同表的列,可以使用什么可能的有效數據結構以及相應的檢索算法。
我提出了兩種可能的解決方案:
第一:蠻力:
要使用quickselect查找第b個最小元素的第一步,平均時間總計為O(km)到O(km * log(m)) 。 步驟2時間復雜度為O(km) 。 最后一步是在C中找到位於第 a個元素與第 b個元素之間的元素,取O((ba)log(kb)) 。 因此,總計需要O(km)到O(km * log(m)) + O((ba)log(kb))的時間,以及O(kb)的空間。
第二:遞歸地彈出最小的元素
對於每個循環,執行
因此,計算復雜度為O(k * log(k)) + O(b * log(k)) ,空間復雜度為O(max(k,ba)) 。 這似乎是最小的空間復雜度。
有哪些更有效的方法? 特別是快速選擇的最壞情況是O(n ^ 2) ,它似乎太大了,並且對於b = m / 2 , 恰好在空間的中值O(kb)或時間上的O(b * log(k))處考慮太大。 對於MySQL數據庫,我建議使用B樹,該B樹在解決方案1中提供了快速的排名選擇,而空間和時間仍然是O(kb) ,其中k個查詢進入數據庫。 在解決方案2中,據說b對MySQL DB的查詢太大,而B樹插入是O(log(m)) ,其中m可能非常大。
一種簡單的方法是創建大小為b的最大堆。 然后運行以下代碼:
for arr in arrays // process each of the k arrays in turn
for i = 0 to length(k)-1
if heap.count < b
heap.push(arr[i])
else if (arr[i] < heap.peek())
heap.pop()
heap.push(arr[i])
這里的想法是用前b個項目填充最大堆。 然后,對於其他所有項目,如果它小於堆中的最大項目,則使用新項目刪除堆中的最大項目。
處理完所有km個項目后,堆上最小的b個項目,由於是最大堆,因此您彈出的前ba個項目將是所有k個數組中的第 a到第 b 個項目。
// all items have been processed, take the first *b - a* items from the max heap
for i = 0 to (b-a-1)
result[i] = heap.pop()
最糟糕的情況是,使用第一個循環的O(km log b),使用第二個循環的O(b log b),使用O(b)附加內存。
如果可以銷毀源數組,則可以編寫一個自定義的quickselect來將k個數組索引為單個數組。 這將是O(km),將O(k)的額外內存用於間接索引。 缺點是索引代碼會稍微慢一些。 而且,當然,這些項將在數組之間移動。 而且您可能想要O(b)額外的內存作為返回值。 漸近地,它比我最初的選擇更有效。 它是否會運行得更快,完全是另一個問題。
另一種可能性。 在k個數組中的每一個上運行build-heap方法。 那將是O(km)。 然后進行合並以選擇前b個項目。 合並將需要:
第二步將是O(b *(log m + log b + log b))。
這樣總共得到O(km + b *(log m + log b + log b)),並且您將使用O(b)額外的內存。 這是否會比最初的建議更快。 這取決於b和m之間的關系。 b的值越大,越快越不可能。 而且代碼編寫起來要復雜得多。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.