尋找第K小元素


要在一個序列里找出第K小元素,可以用排序算法,然后再找。可以證明,排序算法的上界為O(nlogn)。

在這里,給出兩種可以在線性時間內找出第K小元素的方法。

方法1:

(1) 選定一個比較小的閾值(如44),當序列元素小於閾值時,直接用排序算法來做;

(2) 當序列元素大於閾值時,把元素划分為以5個元素為一組,每一組元素自身作排序,然后挑出每一組元素的中間值,再在所有的中間值中,遞歸調用本算法,挑出中間值,可以認為,此值大約為整個序列的中間值(當序列元素個數不是5的倍數時,最后一組不足5的舍掉,這個對中間值影響不大);

(圖片參考《ALGORITHMS DESIGN TECHNIQUES AND ANALYSIS》 M. H. Alsuwaiyel)

(3) 把元素按中間值划分為三組,第一組小於中間值,第二組等於中間值,第三組大於中間值;

(4) 若第一組的元素個數大於等於K,即第K個元素在第一組內;若第一組和第二組的元素個數大於等於K,即中間值為第K個元素;否則,第K個元素在第三組,再遞歸調用本算法,注意K要減去一二組的元素個數。

    public static int select(int[] A, int k){
        return selectDo(A, 0, A.length-1, k);
    }
    
    private static int selectDo(int[] A, int low, int high, int k){
        //select k min number
        int p = high - low + 1;
        if(p < 44){
            Arrays.sort(A, low, high+1);
            return A[low+k];
        }
        //A divided into q groups, each group 5 elements, and sort them
        int q = p/5;
        int[] M = new int[q];
        for(int i = 0; i < q; i ++){
            Arrays.sort(A, low + 5*i, low + 5*i + 5);
            M[i] = A[low+5*i+2];
        }
        //select mid in M
        int mid = selectDo(A, 0, q-1, (q-1)/2);
        //A divided into 3 groups
        int[] A1 = new int[p];
        int[] A2 = new int[p];
        int[] A3 = new int[p];
        int count1, count2, count3;
        count1 = count2 = count3 = 0;
        for(int i = low; i <= high; i ++){
            if(A[i] < mid)
                A1[count1++] = A[i];
            else if(A[i] == mid)
                A2[count2++] = A[i];
            else
                A3[count3++] = A[i];
        }
        if(count1 >= k)
            return selectDo(A1, 0, count1-1, k);
        if(count1 + count2 >= k)
            return mid;
        return selectDo(A3, 0, count3-1, k-count1-count2);
    }
Java

這個方法雖然可以以最壞時間復雜度O(n),但是系數值會比較大,而且算法比較復雜。

 

方法2:

(1) 隨機挑選一個元素X作為數組的划分,此時數組分為三部分,第一部分是小於X的元素,第二部分只有一個元素就是X,第三部分是大於或等於X的元素;

(2) 當第一部分的元素個數N大於K時,說明第K個元素在第一部分;當第一部分的元素個數N等於K時,說明X就是第K個元素(注意到下標從0開始);否則,第K個元素在第三部分,再遞歸調用本算法,注意K要減去第一部分的元素個數再減1(包括X)。

    public static int randomSelect(int[] A, int k){
        return randomSelectDo(A, 0, A.length-1, k);
    }

    private static int randomSelectDo(int[] A, int low, int high, int k){
        int i = randomPartition(A, low, high);
        //n is the number of < A[i]
        int n = i-low;
        if(n > k)
            return randomSelectDo(A, low, i-1, k);
        else if(n == k)
            return A[i];
        else 
            return randomSelectDo(A, i+1, high, k-n-1);
    }


    private static void swap(int[] A, int i, int j){
        int temp = A[i];
        A[i] = A[j];
        A[j] = temp;
    }

    private static int randomPartition(int[] A, int low, int high){
        //random divide
        Random rand = new Random();
        int r = rand.nextInt(high-low+1) + low;
        swap(A, low, r);
        int i = low;
        int x = A[low];
        for(int j = low+1; j <= high; j ++){
            if(A[j] < x){
                i ++;
                if(i != j){
                    swap(A, i, j);
                }
            }
        }
        swap(A, low, i);
        return i;
    }
Java

此方法和快速排序有點相似,也是需要划分數組的方法;與方法1相比,不同之處在於划分元素的選擇。采用隨機化划分,在實際上可以達到O(n)的要求。而且算法簡潔優美。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM