尋找第K小的數

2021-06-21 02:05:28 字數 3706 閱讀 5867

尋找第k小的數屬於順序統計學範疇,通常我們可以直接在o(nlgn)的時間內找到第k小的數,使用歸併排序或者堆排序對輸入資料按從小到大進行排序,然後選擇第k個即可。然而,我們還有更好的演算法。

首先來看乙個簡單的問題,在乙個有n個元素的集合中,需要多少次比較才能確定其最小值呢?這可以很容易退出需要n-1次這個上界。偽**如下所示:

minimum(a)  

min = a[1]

for i=2 to n

if a[i] < min

min = a[i]

return min

尋找最小值需要進行n-1次比較,這已經是最優結果。如果需要同時找出最大值和最小值,可以直接進行兩次查詢,一次最大值一次最小值,共需要2(n-1)次比較。而事實上,我們可以只通過3*[n/2]次比較就足以同時找到最大值和最小值。通過成對的處理元素,先將一對輸入元素比較,找到較大值和較小值。然後將較大值與當前最大值比較,較小值與當前最小值比較,這樣每兩個元素需要比較3次,一共需要3*[n/2]次比較。我們可以用遞迴演算法寫乙個尋找最大值和最小值的程式,**如下:

void max_min(int a, int l, int r, int &minvalue, int &maxvalue)  

if (l + 1 == r) else

return;

} int lmax, lmin;

int m = (l + r) / 2;

max_min(a, l, m, lmin, lmax); //找出左邊的最大值和最小值

int rmax, rmin;

max_min(a, m+1, r, rmin, rmax); //找出右邊的最大值和最小值

maxvalue = max(lmax, rmax); //最終的最大值

minvalue = min(lmin, rmin); //最終的最小值

}

題目:最壞情況下如何利用n+

答案:在最壞情況下利用 n +

次比較,找出

n個元素中第二小的元素。

其方法叫做

tournament method, 

演算法實現如下:

對陣列a[1…n]

中元素成對的做比較,每次比較後講較小的數拿出,形成的陣列再繼續這樣處理,直到剩下最後的乙個,就是陣列中最小的那個。將這個過程以乙個樹的形式表現出來

,如下圖:

在這個過程中,非樹葉節點就是比較的次數,一共進行了

n-1次比較,樹根即為最小的元素。

而第二小的元素一定是在這個過程中與根節點進行過比較的元素。即上圖中5,

3和2。這樣的節點最多有

-1次比較。因此總共所需的比較次數為

n-1 + 

-1 =n+

-2次。

尋找最大的數和最小的數很容易實現,但是尋找第k小的數不是那麼簡單。陳皓老大曾經寫過一篇文章批判純演算法題的面試,說的是他們公司在面試新員工時出的演算法題是找陣列中第二小的數,當時提出排序思想的都直接被刷了,但是在實際應用中,可能排序又是用的最多的方法,因為實際應用往往需要多次查詢第k小的值,而不是單純的一次。如果給陣列排序,則以後每次找第k小的數複雜度都為o(1)了。吐槽歸吐槽,還是從演算法角度來看看這個題目究竟能優化到什麼程式,

1)排序,然後選擇第k個即可。時間複雜度為o(nlgn+k)。

2)用大小為k的陣列存前k個數,然後找出這k個數中最大的數設為kmax,用時o(k)。遍歷後面的n-k個數,如果有小於kmax的數,則將其替換kmax,並從新的k個數中重新選取最大的值kmax,更新用時o(k)。所以總的時間為o(nk)。

3)乙個更好的方法是建堆。建立乙個包含k個元素的最大堆,將後續的n-k每個數與堆頂元素比較,如果小於堆頂元素,則將其替換堆頂元素並調整堆得結構維護最大堆的性質,最後堆中包含有最小的k個元素,從而堆頂元素就是第k小的數。建堆的時間為o(k),每次調整最大堆結構時間為o(lgk),從而總的時間複雜度為o(k + (n-k)lgk)。

4)類快速排序方法。採用類此快速排序的方法,從陣列中隨機選擇乙個數將陣列s劃分為兩部分sa和sb,如果sa大小等於k,則直接返回,否則根據大小情況在兩部分進行遞迴查詢。

前兩種方法比較簡單,重點是後兩種方法,在下面詳細分析。

關於建堆詳細過程參見自己動手寫二叉堆,我們用前面的k個元素建立乙個k個元素的最大堆,而後遍歷陣列後面的n-k個數,若有小於堆頂的元素,則將其與堆頂元素替換並調整堆的結構,維持最大堆的性質。

/*得到第k小的數*/  

int mink(int a, int n, int k)

} return heap[1];

}

/*採用插入方法建立堆*/

void build_max_heap(int heap, int a, int k)

} /*將key插入到堆中,並保證最大堆的性質*/

void max_heap_insert(int heap, int key, int &heapsize)

/*將堆中heap[i]的關鍵字增大為key*/

void heap_increase_key(int heap, int i, int key)

}

隨機選擇乙個元素作為樞紐元,然後將陣列分為左右兩部分,根據左右兩部分的大小來決定遞迴的區間,

平均時間複雜度為o(n),但是最壞時間還是o(n^2)。

這裡跟快速排序有所不同,快速排序平均時間複雜度為o(nlgn),因為它需要遞迴呼叫兩個部分,而尋找第k小的元素只需要考慮其中的一半。演算法偽**如下:

rand-select(a, l, u, k) ⊳ k th smallest of a[l .. u]   

if l = u then return a[l]

p ← rand-partition(a, l, u)

i ← p – l + 1 ⊳ k = rank(a[p])

if i = k then return a[p]

if i < k

then return rand-select(a, p+1, u, k-i )

else return rand-select(a, l, p-1, k)

演算法思想:採用類似快速排序的方法隨機選擇乙個樞紐元進行劃分,陣列以p為界分成兩個部分。如果左邊部分的數目i(包括a[p])等於k,則返回a[p]。否則如果左邊部分元素數目小於k,則遞迴呼叫該函式從右邊即[p+1, u]選擇第k-i小元素。如果左邊元素數目大於k,則從範圍[l, p-1]中選擇第k小元素。

int random_partition(int a, int l, int u)  

while (a[i] < t && i <= u);

do while (a[j] > t);

if (i > j) break;

swap(a, i, j);

} swap(a, l, j);

return j;

}

int random_select(int a, int l, int u, int k)

尋找第K小的數

description輸入n個數,求其中第k小的數。思路 採用快排求解,如下 include include include include partition的作用是將小於povit的元素放左邊,大於povit的元素放右邊 intpartition int arr,int left,int rig...

尋找區間內第k小的數

這是最直接暴力的方法,時間複雜度為 o nlog n 直接排序,輸出第k小的值即可 include include using namespace std const int n 1e5 10 int n,k int a n int main 時間複雜度的計算主要關注左右兩個指標的移動,總次數為 n...

尋找第K大的數

題目描述 要求在n個不重複的整數中,找出第k大的整數 其中0輸入第一行為兩個正整數n k 第二行為n個整數,輸入保證這n個整數兩兩相異,每個整數的範圍在 1000000到1000000之間 輸出輸出第k大的整數值 樣例輸入 5 33 2 4 5 1 樣例輸出 3 如下 include include...