目錄數組
1、什麼是Top K問題dom
2、Top K的實際應用場景ui
3、Top K問題的代碼實現及其效率對比spa
1.用堆來實現Top Kcode
2.用快排來實現Top Kblog
3.用堆或用快排來實現 TopK 的效率對比排序
正文element
1、什麼是Top K問題?get
給一個無序的數組,長度爲N, 請輸出最小 (或最大)的K個數。it
2、Top K的實際應用場景
排行榜:用戶數量有幾百萬, 可是隻須要前100名的用戶成績。 要顯示出來, 且這個排行榜是實時變化的。
3、Top K問題的代碼實現
需求:給一個無序的數組,長度爲N, 請輸出最小的5個數。
1. 用堆來實現Top K——小頂堆
(1)步驟梳理:
①建立一個結點個數爲 k 的小頂堆;
②當數據量 < k 時,將數據直接放到這個小頂堆中,此時堆的頂結點是最小值;
③當數據量 >= k時,每產生一個新數據都與堆的頂結點進行比較:
若是新數據 > 頂結點數據,則將頂結點刪除,將新數據放到堆中,此時堆會進行排序,且維護了堆的總結點數爲k;
(2)中心思想:使堆的總結點數維持在 k 個。
(3)代碼實現:
1 @Test 2 public void getTopKByHeapInsertTopKElement() { 3 int arrayLength = 10000000 + 10; 4 int topK = 5; 5 6 // 準備一個長度爲arrayLength的無序數組: 7 int[] array = A03TopKByQuickSortAndNewArray.getDisorderlyArray(arrayLength); 8 9 // 準備一個總結點數爲topK的小頂堆: 10 PriorityQueue<Integer> heap = new PriorityQueue<>(topK); 11 12 long start = System.currentTimeMillis(); 13 14 // 始終維持一個總結點個數爲k的堆: 15 insertButmaintainTheHeapAtTopK(heap, array, topK); 16 17 //得到最大topK: 18 printHeap(heap); 19 20 long end = System.currentTimeMillis(); 21 System.out.println("得到最大top5總耗時: " + (end - start)); 22 } 23 24 /** 25 * 用小頂堆來獲取topK:當數據量超過topK後,新產生的數據直接和heap的頂結點進行比較。 26 */ 27 private static void insertButmaintainTheHeapAtTopK(PriorityQueue<Integer> heap, int[] array, int topK) { 28 for (int i = 0; i < array.length; i++) { 29 if (i < topK) { 30 heap.add(array[i]); 31 } else {// 怎麼維持堆的總結點個數,下面的代碼是關鍵: 32 if (null != heap.peek() && array[i] > heap.peek()) { 33 heap.poll(); 34 heap.add(array[i]); 35 } 36 } 37 } 38 } 39 40 /** 41 * 獲取最大TopK 42 * @param heap 43 */ 44 static void printHeap(PriorityQueue<Integer> heap) { 45 Iterator<Integer> iterator = heap.iterator(); 46 while (iterator.hasNext()) { 47 System.out.println(iterator.next()); 48 } 49 }
2. 用快排來實現Top K
(1)步驟梳理:
①經過快排,先將無序數組array進行排序;
②取出最小Top 5,並放到topArray中;【關鍵】
③超過arrayLength個數據後,又產生了insertNumber個新數據:直接和topArray數組比較,要放也是放到topArray中了;【關鍵】
(2)時間複雜度:
①排序的時間複雜度:O(N*logN);
②取出top k的時間複雜度:O(1),就是遍歷數組。
(3)代碼實現:
1 @Test 2 public void testGetTopKByQuickSortToNewArray() { 3 int topK = 5; 4 int arrayLength = 10000000; 5 6 //準備一個無序數組 7 int[] array = getDisorderlyArray(arrayLength); 8 9 long start = System.currentTimeMillis(); 10 11 //1.經過快排,先將無序數組array進行排序 12 quickSort(array, 0, array.length-1); 13 14 //2.取出最小Top 5,並放到topArray中: 15 int[] topKArray = insertToTopArrayFromDisorderlyArray(array, topK); 16 17 //3.超過arrayLength個數據後,又產生了insertNumber個新數據:直接和topArray[topKArray.length-1]比較,要放也是放到topArray中了 18 insertToTopKArray(topKArray, 10, 100, topKArray.length-1);//生成10個100之內的隨機數做爲新數據,和topKArray[topKArray.length-1] 19 20 long end = System.currentTimeMillis(); 21 System.out.println("得到最大top5總耗時: " + (end - start)); 22 } 23 24 /** 25 * 產生新的數據後,再和topKArray數組進行比較,看新數據時候須要插入到topKArray中,若須要插入,則堆topKArray進行從新快排。 26 * 27 * @param topKArray topK數組 28 * @param insertNumber 新產生的數據的個數 29 * @param randomIntRange 在什麼範圍內產生新數據,如生成10之內的隨機數。 30 * @param topK 在topKArray中,肯定要替換的元素的下標。得到最小topK,則topK是從小到大排序的topKArray的最後一個元素。 31 */ 32 private static void insertToTopKArray(int[] topKArray, int insertNumber, int randomIntRange, int topK) { 33 Random random = new Random(); 34 int randomInt; 35 for(int i = 0; i < insertNumber; i++) { 36 randomInt = random.nextInt(100); 37 if(randomInt < topKArray[topK]) {//新數據若是小於topArray[topK],則直接用該數去替換topArray,而後再將topArray進行從新排序。 38 topKArray[topK] = randomInt; 39 quickSort(topKArray, 0, topKArray.length-1); 40 } 41 } 42 } 43 44 /** 45 * 從有序數組中取出須要的TopK,放到TopK數組中。 46 * 47 * @param sourceArray 有序數組 48 * @param topK 須要獲取到Top K 49 * @return TopK數組 50 */ 51 private static int[] insertToTopArrayFromDisorderlyArray(int[] sourceArray, int topK) { 52 int[] topArray = new int[topK]; 53 for(int i = 0; i < 5; i++) { 54 topArray[i] = sourceArray[i]; 55 } 56 return topArray; 57 } 58 59 /** 60 * 快排 61 * @param target 62 * @param left 63 * @param right 64 */ 65 static void quickSort(int[] target, int left, int right) { 66 if (left >= right) { 67 return; 68 } 69 int pivot = target[left];// 基準點 70 int temp; 71 int i = left; 72 int j = right; 73 while (i < j) { 74 while (target[j] >= pivot && i < j) { 75 j--; 76 } 77 while (target[i] <= pivot && i < j) { 78 i++; 79 } 80 if (i < j) { 81 temp = target[i]; 82 target[i] = target[j]; 83 target[j] = temp; 84 } 85 } 86 // left和right相遇了: 87 // ①將相遇點的元素和pivot作交換: 88 target[left] = target[j]; 89 target[j] = pivot; 90 // ②基準點兩邊的元素的分別再作排序: 91 quickSort(target, left, j - 1); 92 quickSort(target, j + 1, right); 93 } 94 95 /** 96 * 準備一個無序數組 97 * 98 * @param arrayLength 99 * @return int[] 100 */ 101 static int[] getDisorderlyArray(int arrayLength) { 102 int[] disorderlyArray = new int[arrayLength]; 103 Random random = new Random(); 104 for (int i = 0; i < arrayLength; i++) { 105 disorderlyArray[i] = random.nextInt(arrayLength); 106 } 107 return disorderlyArray; 108 } 109 110 /** 111 * 遍歷數組 112 */ 113 static void showArray(int[] target) { 114 for (Integer element : target) { 115 System.out.println(element); 116 } 117 }
3. 用堆來實現TopK 和 用快排來實現TopK 的效率對比:
「小頂堆」 | 「快排」
數據量爲100萬+10時: 11毫秒 | 124毫秒
數據量爲1000萬+10時: 28毫秒 | 1438毫秒