問題:給定線性序集中n個元素和乙個整數k,1≤k≤n,要求找出這n個元素中第k小的元素,演算法的複雜度為o(n)。
思路分析:
首先,假如我們要找最大或者最小的元素,那麼只需遍歷一遍序列即可,複雜度為o(n)。假如要找第k大的元素,k<=n/logn或者k>=n-nlogn時,利用堆排序,時間複雜度仍然可以達到o(n),具體為什麼還沒搞懂。無論如何,如果對於任意的k,(最壞)時間複雜度要想達到o(n),對整個序列進行排序都是不可行的(複雜度至少為nlogn)。
那麼如何將複雜度降低到o(n)呢?由於我們求的僅僅是第k大的數字,那麼可以不用整個對整個序列進行排序,而僅僅保證在第k大的數字前面的數字都小於這個第k大數即可。借助快排的思想,每次找乙個哨兵將序列分為大於哨兵和小於哨兵的兩部分,根據需要求的數是第k**擇乙個子串行遞迴進行排序即可,而非對所有的序列都要排序。
假如能夠做到在任意情況下,所選的哨兵將序列等比地分為兩部分,就可以做到線性地得到結果:設子串行的長度分別為ne和n(1-e),複雜度遞推公式為t(n) = t(ne)+o(n),時間複雜度為o(n)。
如何保證在任意情況下哨兵都會做到等比分割序列呢?傳統的快速排序演算法在選取哨兵時採用序列的第乙個元素,這樣在極端情況下會退化為等差遞減的遞迴,也就是冒泡;隨機快速排序演算法對此進行了一些優化,每次隨機選取哨兵,但是本質上也是一樣。將序列預劃分可以有效地解決這一問題:
1.將序列劃分成n/5個子序列,每個子串行有5個元素,最後乙個不足5個元素的子串行扔掉不要(因為目的只是為了找乙個差不多的基準哨兵),任選一種排序方法對每個子串行進行排序,對每個子串行取中位數
2.取出中位數後,對n/5個中位數再取中位數,這裡其實轉化為中位數中求解第k大的數的子問題
3.以確定的中位數作為哨兵,進行劃分序列,可以證明,當n>=75時,此時子串行近似劃分為n/4和3n/4兩個部分
複雜度遞推公式為t(n)=t(n/5)+t(3n/4)+o(n),n>=75;t(n)=o(1),n<75
**:
// 將序列a重排為mid左邊的元素全部小於mid,mid右邊的元素全部大於mid
// 輸入:序列a,序列起始下標l,終止下標r,哨兵大小mid
// 輸出:mid在序列a中的位置j,據此可確定劃分完成的兩個子串行
int part(int a,int l,int r,int mid)
while(a[j]>=mid)
if(i>=j)else
}return j;
}// 選取序列a中序號從l到r中第k小的元素
// 輸入:序列a,序列起始下標l,序列終止下標r,所求的元素是第k小
// 輸出:第k小的元素大小
int select(int a,int l,int r,int k)else
// 獲取中位數的中位數
int mid = select(a, l, l+(r-l-4)/5, ((r-l-4)/5+1)/2);
// 以mid為基準進行子串行的劃分
int mid_id = part(a, l, r, mid);
int mid_rank = mid_id - l + 1;
// 判斷應該取哪個子串行進行遞迴
if(k == mid_rank)
return a[mid_id];
else if(k > mid_rank)
return select(a, mid_id+1, r, k-mid_rank);
else
return select(a, l, mid_id-1, k);
}}
分治法 線性時間選擇
演算法思想 利用快速排序的方法將a p r 被劃分成兩個子陣列a p i 和a i 1 r 使a p i 中的每個元素都不大於a i 1 r 中每個元素。接著演算法計算子陣列a p i 中元素個數j。如果k j,則第k小的數落在左區間,否則落在右區間,直到k j時,找到第k小的數。對於有重複數字的無...
分治法 線性時間選擇(求第k小數)
給定線性序集中n個元素和乙個整數k,1 k n,要求找出這n個元素中第k小的元素,這裡給定的線性集是無序的 線性時間選擇隨機劃分法可以模仿隨機化快速排序演算法設計。基本思想是對輸入陣列進行遞迴劃分,與快速排序不同的是,它只對劃分出的子陣列之一進行遞迴處理。利用隨機函式產生劃分基準,將陣列a p r ...
演算法 線性時間選擇 C C
給定線性序集中n個元素和乙個整數k,n 2000000,1 k n,要求找出這n個元素中第k小的數。第一行有兩個正整數n,k.接下來是n個整數 0 ai 1e9 輸出第k小的數 6 3 1 3 5 2 4 6 3利用快速排序可以找出第k小的,加上隨機函式改進一下 ac include include...