題目:
給定一個無序的整型數組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; } } } }