從一個序列里面選擇第k大的數在沒有學習算法導論之前我想最通用的想法是給這個數組排序,然后按照排序結果返回第k大的數值。如果使用排序方法來做的話時間復雜度肯定至少為O(nlgn)。
問題是從序列中選擇第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大的元素。這里的划分有三種情況:
1:主元的選擇正好是第i大的元素,那么返回這個元素即可
2:Q里面的元素個數 k=(q-p+1) 大於i,代表第i大的元素還在Q這個集合里,那么繼續這個過程尋找第i小的元素( step 7-8)
3:Q里面的元素個數 k=(q-p+1) 小於i,代表已經找到了k個小的元素,那么第i小的元素一定在W這個集合里,只要在W集合里尋找第(i-k)小的元素即可
下面給出這個算法的java實現:
/** * 根據算法導論的偽代碼,完成快速選擇的代碼。 * @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; } }
