TopK問題:什么是TopK問題?用堆和快排這兩種方式來實現TopK


  目錄

一、什么是Top K問題

二、Top K的實際應用場景

三、Top K的代碼實現及其效率對比

  1.用堆來實現Top K

  2.用快排來實現Top K

  3.用堆或用快排來實現 TopK 的效率對比

 

  正文

一、什么是Top K問題?

  給一個無序的數組,長度為N,  請輸出最小 (或最大)的K個數。

 

二、Top K的實際應用場景

  排行榜:用戶數量有幾百萬, 但是只需要前100名的用戶成績。 要顯示出來, 且這個排行榜是實時變化的。

 

三、Top K的代碼實現

  需求:給一個無序的數組,長度為N, 請輸出最大的5個數。 

  1. 用堆來實現Top K——PriorityQueue(小頂堆)

  (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毫秒

 


免責聲明!

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



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