演算法問題分類---Top-K問題與多路歸併排序
Pro1:尋找前K大數
方法1:K小根堆 後面的值若大於當前根,則替換之,並調整堆
大部分人都推薦的做法是用堆,小根堆。下面具體解釋下:
如果K = 1,那麼什麼都不需要做,直接遍歷一遍,時間複雜度O(N)。
下面討論K 比較大的情況,比如1萬。
建立一個小根堆,則根是當前最小的第K個數。然後讀入N-K個數,每次讀入一個數就與當前的根進行比較,如果大於當前根,則替換之,並調整堆。如果小,則讀入下一個。
時間複雜度O(N*logK)。
方法2:利用快排分割槽思想:
本題還有一個時間複雜度比較好的做法。在程式設計之美上提到過該演算法。
首先找到最大的第K個數。這個時間複雜度可以做到O(N),具體做法如下(利用快排分割槽思想):
從N個數中隨機選擇一個數,掃描一遍,比n大的放在右邊,r個元素,比n小的放左邊,l個元素
如果: a:l = K-1 返回n
b:l > K-1 在l個元素中繼續執行前面的操作。
c:l < K-1 在r個元素中繼續執行前面的操作。
b,c每次只需執行一項,因此平均複雜度大概為:O(n+n/2+n/4...)=O(2n)=O(n)
Pro2: K路合併求Top-K
20路已經有序,20路合併 求Top500
有 20 個數組,每個陣列有 500 個元素,並且是有序排列好的,現在在這 20*500個數中找出排名前 500 的數。
答:
從20個數組中各取一個數,並記錄每個數的來源陣列,建立一個含
Pro3: K路合併排序
請給出一個時間為O(nlgk)、用來將k個已排序連結串列合併為一個排序連結串列的演算法,此處n為所有輸入連結串列中元素的總數。
演算法思想:
1. 從k個連結串列中取出每個連結串列的第一個元素,組成一個大小為k的陣列arr,然後將陣列arr轉換為最小堆,那麼arr[0]就為最小元素了;
2. 取出arr[0],將其放到新的連結串列中,然後將arr[0]元素在原連結串列中的下一個元素補到arr[0]處,即arr[0].next,如果 arr[0].next為空,即它所在的連結串列的元素已經取完了,那麼將堆的最後一個元素補到
Pro4: 整體有序區域性無序問題
一個有100億個元素的整型陣列,它的元素是有序的,現在把它分成若干段,每段不超過20個元素,每段的元素個數不等,現在在每段內將這些元素的順序打亂,然後重新將這100億個元素的陣列排序,請問時間複雜度最小的演算法是什麼?並給出時間複雜度。
分析:
如果每段長度相等,則可以考慮採用上面的K路歸併,但此處長度不相等,需另行考慮其它方法。
解:(直接插入排序)
假設第1到第5n個數已經有序為sort(5n),那麼我們要將5n+1到5n+5這5個數據新增到已排序的陣列中,只需要進行插入排序,將這5個數新增進即可。由於分段的長度不超過5,所以第5n+1個數在插入的時候,最多隻需要搜尋到第5n-4個數就可以了,比較個數不會超過5次。又因為5n+1到5n+5是已經排好序的,所以,後面的數比較次數也不會超過5次(最多比較到前一個插入的位置)。因此,每加入5個數到已排序陣列中,時間複雜度是O(5*5),
假設長度為N,每段長不超過K。則每段插入的時間複雜度即為O(K*K)。
而對於以段為單位插入的操作,需要進行N/K次,所以,總的時間複雜度是O(K*K)*O(N/K)=O(NK)
Pro5:100億個數,求最大的1萬個數,並說出演算法的時間複雜度
建一個堆,先把最開始的1萬個數放進去。以後每進一個,都把最小的趕出來。
上述演算法的可選實現工具:自己建立陣列進行堆排序、使用優先佇列priority_queue、使用集合set multiset
在最大堆中,根結點的值總是大於它的子樹中任意結點的值。於是我們每次可以在O(1)得到已有的k個數字中的最大值18,但需要O(logk)時間完成刪除以及插入操作。
佇列(queue)維護了一組物件,進入佇列的物件被放置在尾部,下一個被取出的元素則取自佇列的首部。priority_queue特別之處在於,允許使用者為佇列中儲存的元素設定優先順序。這種佇列不是直接將新元素放置在佇列尾部,而是放在比它優先順序低的元素前面。優先佇列有兩種,一種是最大優先佇列;一種是最小優先佇列;每次取自佇列的第一個元素分別是優先順序最大和優先順序最小的元素。
在STL中set和multiset都是基於紅黑樹實現的,查詢、刪除和插入操作都只需要O(logk)。