输入n 个整数,输出其中最小的k 个。
例如输入1,2,3,4,5,6,7 和8 这8 个数字,则最小的4 个数字为1,2,3 和4。
经典的TopK问题,因为需要最小的K个元素,所以只需创建并维持一个拥有K个元素的最大堆即可。
创建一个拥有K个元素的最大堆的代码为:
1 void CreateMaxHeap(int *MaxHeap, int K) 2 { 3 assert (MaxHeap != NULL); 4 5 assert (K > 0); 6 7 // 从最后一个有子孩子的节点开始往下调整,一直到根节点,则这个最大堆就创建好了 8 for (int i = (K >> 1) - 1; i >= 0; --i) 9 { 10 MaxHeapFixDown (MaxHeap, i, K); 11 } 12 }
向下调整最大堆得函数代码为:
1 void MaxHeapFixDown(int *MaxHeap, int l, int K) 2 { 3 assert (MaxHeap != NULL); 4 5 assert (l >= 0); 6 7 assert (l < K); 8 9 int nTemp = MaxHeap[l]; 10 int i = l; 11 int j = 2 * i + 1; 12 13 while (j < K) 14 { 15 if (((j + 1) < K) && (MaxHeap[j+1] > MaxHeap[j])) 16 { 17 // 获取两个子孩子中值最大的那个 18 ++j; 19 } 20 21 if (MaxHeap[j] <= nTemp) 22 { 23 // 如果最大的子孩子都小与nTemp,则nTemp的正确位置就是这两个孩子父亲的位置 24 break; 25 } 26 27 // 大值往上冒 28 MaxHeap[i] = MaxHeap[j]; 29 i = j; 30 j = 2 * i + 1; 31 } 32 33 // 确定nTemp的正确位置 34 MaxHeap[i] = nTemp; 35 }
基于上,我们只需将数组的前K个元素调整成最大堆,然后从第K+1个元素开始与最大堆的根节点比较(最大堆的根节点是堆中的最大值),若比最大值小,则需将这个元素与堆的根节点交换位置,然后调整这个最大堆,反之则不需做任何处理,一直到数组中第N个元素。最后数组中最小的K个元素就是数组的前K个元素,直接将其输出即可。
基于上述分析,获取最小K个元素的代码如下:
1 void GetKMinNumber(int *a, int N, int K) 2 { 3 assert (a != NULL); 4 5 assert (K > 0); 6 7 assert (K <= N); 8 9 if (K == N) 10 { 11 // 最小的K歌元素就保存在数组a的前K个数据块中 12 cout << "最小的 " << K << " 个元素为: "; 13 for (int i = 0; i < K; ++i) 14 { 15 cout << a[i] << " "; 16 } 17 18 cout << endl; 19 20 return; 21 } 22 23 // 创建一个最大堆 24 CreateMaxHeap (a, K); 25 26 for (int i = K; i < N; ++i) 27 { 28 if (a[i] < a[0]) 29 { 30 Swap (a[i], a[0]); 31 32 // 更新一个元素后就要维护这个堆,使其保持为最大堆 33 MaxHeapFixDown (a, 0, K); 34 } 35 } 36 37 // 最小的K歌元素就保存在数组a的前K个数据块中 38 cout << "最小的 " << K << " 个元素为: "; 39 for (int i = 0; i < K; ++i) 40 { 41 cout << a[i] << " "; 42 } 43 44 cout << endl; 45 }
给出一个测试结果如下: