[算法]找到無序數組中最小的K個數


題目:

給定一個無序的整型數組arr,找到其中最小的k個數。

方法一:

將數組排序,排序后的數組的前k個數就是最小的k個數。

時間復雜度:O(nlogn)

方法二:

時間復雜度:O(nlogk)

維護一個有k個數的大根堆,這個堆代表目前選出的k個最小的數。在堆的k個元素中堆頂元素是最小的k個數中最大的那個。

接下來要遍歷整個數組,遍歷的過程中看當前數是否比堆頂元素小。如果是,就把堆頂元素替換成當前數,然后調整堆。如果不是,則不做任何操作,繼續遍歷下一個數。在遍歷完成后,堆中的k個數就是所有數組中最小的k個數。

程序:

public static int[] getMinKNumsByHeap(int[] arr, int k) {
        if (k < 1 || k > arr.length) {
            return arr;
        }
        int[] heap = new int[k];
        for (int i = 0; i != k; i++) {
            heapInsert(heap, arr[i], i);
        }
        for (int i = k; i < arr.length; i++) {
            if (arr[i] < heap[0]) {
                heap[0] = arr[i];
                heapify(heap, 0, k);
            }
        }
        return heap;
    }
    private static void heapInsert(int[] heap, int value, int index) {
        heap[index] = value;
        while (index != 0) {
            int parent = (index - 1) / 2;
            if (heap[parent] < heap[index]) {
                swap(heap, parent, index);
                index = parent;
            } else {
                break;
            }
        }
    }
    private static void heapify(int[] heap, int index, int heapSize) {
        int left = index * 2 + 1;
        int right = index * 2 + 2;
        int largest = index;
        while (left < heapSize) {
            if (heap[left] > heap[index]) {
                largest = left;
            }
            if (right < heapSize && heap[right] > heap[largest]) {
                largest = right;
            }
            if (largest != index) {
                swap(heap, largest, index);
            } else {
                break;
            }
            index = largest;
            left = index * 2 + 1;
            right = index * 2 + 2;
        }
    }
    private static void swap(int[] heap, int parent, int index) {
        int tmp = heap[index];
        heap[index] = heap[parent];
        heap[parent] = tmp;
    }

方法三:

時間復雜度:O(n)

這里用到了一個經典算法----BFPRT算法。

1973 年, Blum 、 Floyd 、 Pratt 、 Rivest 、 Tarjan 集體出動,合寫了一篇題為 “Time bounds for selection” 的論文,給出了一種在數組中選出第 k 大元素的算法,俗稱"中位數之中位數算法"。依靠一種精心設計的 pivot 選取方法,該算法從理論上保證了最壞情形下的線性時間復雜度,打敗了平均線性、最壞 O(n^2) 復雜度的傳統算法。一群大牛把遞歸算法的復雜度分析玩弄於股掌之間,構造出了一個當之無愧的來自聖經的算法。

   算法步驟:

     step1:將n個元素每5個一組,分成n/5(上界)組,最后的一個組的元素個數為n%5,有效的組數為n/5。

     step2:取出每一組的中位數,最后一個組的不用計算中位數,任意排序方法,這里的數據比較少只有5個,

                  可以用簡單的冒泡排序或是插入排序。

     setp3 :  將各組的中位數與數組開頭的數據在組的順序依次交換,這樣各個組的中位數都排在了數據的左邊。

            遞歸的調用中位數選擇算法查找上一步中所有組的中位數的中位數,設為x,偶數個中位數的情況下設定為選取中間小的一個。

     setp4:   按照x划分,大於或者等於x的在右邊,小於x的在左邊,關於setp4數據的划分,中位數放在左邊或是右邊會有些影響。

                  后面的代碼調試將會看到。

     step5:setp4中划分后數據后返回一個下表i,i左邊的元素均是小於x,i右邊的元素包括i都是大於或是等於x的。

                  若i==k,返回x;

                  若i<k,在小於x的元素中遞歸查找第i小的元素;

                  若i>k,在大於等於x的元素中遞歸查找第i-k小的元素。

public static int[] getMinKNumsByBFPRT(int[] arr, int k) {
        if (k < 1 || k > arr.length) {
            return arr;
        }
        int minKth = getMinKthByBFPRT(arr, k);
        int[] res = new int[k];
        int index = 0;
        for (int i = 0; i != arr.length; i++) {
            if (arr[i] < minKth) {
                res[index++] = arr[i];
            }
        }
        for (; index != res.length; index++) {
            res[index] = minKth;
        }
        return res;
    }
    public static int getMinKthByBFPRT(int[] arr, int K) {
        int[] copyArr = copyArray(arr);
        return select(copyArr, 0, copyArr.length - 1, K - 1);
    }
    public static int[] copyArray(int[] arr) {
        int[] res = new int[arr.length];
        for (int i = 0; i != res.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }
    public static int select(int[] arr, int begin, int end, int i) {
        if (begin == end) {
            return arr[begin];
        }
        int pivot = medianOfMedians(arr, begin, end);
        int[] pivotRange = partition(arr, begin, end, pivot);
        if (i >= pivotRange[0] && i <= pivotRange[1]) {
            return arr[i];
        } else if (i < pivotRange[0]) {
            return select(arr, begin, pivotRange[0] - 1, i);
        } else {
            return select(arr, pivotRange[1] + 1, end, i);
        }
    }
    public static int medianOfMedians(int[] arr, int begin, int end) {
        int num = end - begin + 1;
        int offset = num % 5 == 0 ? 0 : 1;
        int[] mArr = new int[num / 5 + offset];
        for (int i = 0; i < mArr.length; i++) {
            int beginI = begin + i * 5;
            int endI = beginI + 4;
            mArr[i] = getMedian(arr, beginI, Math.min(end, endI));
        }
        return select(mArr, 0, mArr.length - 1, mArr.length / 2);
    }
    public static int[] partition(int[] arr, int begin, int end, int pivotValue) {
        int small = begin - 1;
        int cur = begin;
        int big = end + 1;
        while (cur != big) {
            if (arr[cur] < pivotValue) {
                swap(arr, ++small, cur++);
            } else if (arr[cur] > pivotValue) {
                swap(arr, cur, --big);
            } else {
                cur++;
            }
        }
        int[] range = new int[2];
        range[0] = small + 1;
        range[1] = big - 1;
        return range;
    }
    public static int getMedian(int[] arr, int begin, int end) {
        insertionSort(arr, begin, end);
        int sum = end + begin;
        int mid = (sum / 2) + (sum % 2);
        return arr[mid];
    }
    public static void insertionSort(int[] arr, int begin, int end) {
        for (int i = begin + 1; i != end + 1; i++) {
            for (int j = i; j != begin; j--) {
                if (arr[j - 1] > arr[j]) {
                    swap(arr, j - 1, j);
                } else {
                    break;
                }
            }
        }
    }


免責聲明!

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



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