1. 程式人生 > >在無序陣列中取最大的K個數

在無序陣列中取最大的K個數

這是面試中最大概率會問的一道題,總結之前的面經,加上借鑑牛人的觀點,總結如下:

一、當無序陣列元素個數數量級不高的情況下,對陣列進行排序,如果用最快的排序快速排序,時間複雜度為O(nlogn),排序結束後取其前K個元素,時間複雜度為常量,總體時間複雜度為O(nlogn)。這是最容易想到的解決方法。但要求取前K個最大元素,並沒有要求其有序,在上述方法中,是對陣列所有元素進行了排序的,包括N-K、和K個元素。這樣的話就可以,採取選擇排序或者交換排序對陣列進行部分排序,直接排出最大的K個元素。
二、選擇排序:對陣列進行選擇排序,時間複雜度為O(n*k)。
1,2相比來說,在K>logN 的情況下,選擇1,方法比較好,反之。

無序陣列元素數量級很大時,機器記憶體不能一次運算時,可以採取最大堆的方式,當加入堆中有K個元素時,即為最大的K個元素;在不斷向堆中加元素時,將其與原堆中的最小值比較,如果大於,則交換,記憶體中永遠只有堆的空間。

舉例:尋找 N 個數中最大的 K 個數,本質上就是尋找最大的 K 個數中最小的那個,也就是第 K 大的數。可以使用二分搜尋的策略來尋找 N 個數中的第 K 大的數。對於一個給定的數 p,可以在 O(N)的時間複雜度內找出所有不小於 p 的數。假如 N 個數中最大的數為 Vmax,最小的數為 Vmin,那麼這 N 個數中的第 K 大數一定在區間[Vmin, Vmax]之間。那麼,可以在這個區間內二分搜尋 N 個數中的第 K大數 p。虛擬碼如下:
while(Vmax-Vmin > delta)
{
Vmid = Vmin + (Vmax – Vmin) * 0.5;
if(f(arr, N, Vmid) >= K)
Vmin = Vmid;
else
Vmax = Vmid;
}
虛擬碼中 f(arr, N, Vmid)返回陣列 arr[0, …, N-1]中大於等於 Vmid 的數的個數。
上述虛擬碼中,delta 的取值要比所有 N 個數中的任意兩個不相等的元素差值之最小值小。如果所有元素都是整數,delta 可以取值 0.5。迴圈執行之後,得到一個區間(Vmin, Vmax),這個區間僅包含一個元素(或者多個相等的元素)。

另一種方法是,找出最大K個元素中的最小值,然後進行時間複雜度為O(n)的操作,找出比最小值大的K個元素。但前提是瞭解陣列元素的最大值和最小值。在尋找最大K個元素中的最小值時,採用二分法查詢即可。

if(X > h[0])
{
h[0] = X;
p = 0;
while(p < K)
{
q = 2 * p + 1;
if(q >= K)
break;
if((q < K-1) && (h[q + 1] < h[q]))
q = q + 1;
if(h[q] < h[p])
{
t = h[p];
h[p] = h[q];
h[q] = t;
p = q;
}
else
break; } }

另外在陣列元素範圍有限的情況下,也可以直接使用計數排序。

for(sumCount = 0, v = MAXN-1; v >= 0; v–)
{
sumCount += count[v];
if(sumCount >= K)
break;
}
return v;

注:計數排序是一個非基於比較的排序演算法,該演算法於1954年由 Harold H. Seward 提出。它的優勢在於在對一定範圍內的整數排序時,它的複雜度為Ο(n+k)(其中k是整數的範圍),快於任何比較排序演算法。[1-2] 當然這是一種犧牲空間換取時間的做法,而且當O(k)>O(n*log(n))的時候其效率反而不如基於比較的排序(基於比較的排序的時間複雜度在理論上的下限是O(n*log(n)), 如歸併排序,堆排序)