從一個序列裏面選擇第k大的數在沒有學習算法導論以前我想最通用的想法是給這個數組排序,而後按照排序結果返回第k大的數值。若是使用排序方法來作的話時間複雜度確定至少爲O(nlgn)。java
問題是從序列中選擇第k大的數徹底沒有必要來排序,能夠採用分治法的思想解決這個問題。Randomize select 算法的指望時間複雜度能夠達到O(n),這正是這個算法的迷人之處。具體的算法分析能夠在《算法導論》這本書裏查看。算法
貼出僞代碼:數組
RANDOMIZED-SELECT(A, p, r, i) 1 if p = r 2 then return A[p] 3 q ← RANDOMIZED-PARTITION(A, p, r) 4 k ← q - p + 1 5 if i = k ▹ the pivot value is the answer 6 then return A[q] 7 elseif i < k 8 then return RANDOMIZED-SELECT(A, p, q - 1, i) 9 else return RANDOMIZED-SELECT(A, q + 1, r, i - k)
這個算法的思想其實跟quik-sort有些類似,採用分治法的思想來解決。首先選擇一個主元pirvot: q,將序列中的元素分爲兩個集合Q,W,Q裏面的元素都小於主元pirvot,W裏面的元素都大於pirvot。而後遞歸的調用這個過程能夠獲得咱們想要的第i大的元素。這裏的劃分有三種狀況:dom
1:主元的選擇正好是第i大的元素,那麼返回這個元素便可學習
2:Q裏面的元素個數 k=(q-p+1) 大於i,表明第i大的元素還在Q這個集合裏,那麼繼續這個過程尋找第i小的元素( step 7-8)ui
3:Q裏面的元素個數 k=(q-p+1) 小於i,表明已經找到了k個小的元素,那麼第i小的元素必定在W這個集合裏,只要在W集合裏尋找第(i-k)小的元素便可spa
下面給出這個算法的java實現:.net
/** * 根據算法導論的僞代碼,完成快速選擇的代碼。 * @author 截取自:http://blog.csdn.net/zy825316/article/details/19486167 */ public class randomizedSelect { /** * @param args */ public static void main(String[] args) { int a[]={2,5,3,0,2,3,0,3}; int result=randomizedSelect(a,0,a.length-1,3);//產生第三小的數 System.out.print("\n"+result); } private static int partition(int[] a, int p, int r) { int x=a[r]; int i=p-1; for(int j=p;j<r;j++){ if(a[j]<=x){ i=i+1; swap(a, i, j); } } swap(a, i+1, r); return i+1; } private static int randomizedPartition(int[] a,int p,int r){ java.util.Random random = new java.util.Random(); int i=Math.abs(random.nextInt() % (r-p+1)+p);//產生指定範圍內的隨機數 swap(a,i,r); return partition(a,p,r); } /** * * @param a 數組 * @param p 數組的第一個元素 * @param r 數組的最後一個元素 * @param i 須要求第幾小的元素 * @return */ private static int randomizedSelect(int[] a,int p,int r,int i){ if(p==r){ return a[p];//這種狀況就是數組內只有一個元素 } int q=randomizedPartition(a,p,r); int k=q-p+1;//拿到上一句中做爲樞紐的數是第幾小的數 if(i==k){ return a[q]; }else if(i<k){ return randomizedSelect(a,p,q-1,i); }else{ return randomizedSelect(a,q+1,r,i-k); } } private static void swap(int[] a, int i, int j) { int temp=a[i]; a[i]=a[j]; a[j]=temp; } }