【剑指offer】面试题30:最小的 K 个数
题目:输入 n 个整数,找出其中最小的 k 个数。例如输入4、5、1、6、2、7、3、8 这 8 个数字,则最小的 4 个数字是 1、2、3、4。
这道题最简单的思路莫过于把输入的 n 个整数排序,排序之后位于最前面的 k 个数就是最小的 k 个数。这种思路的时间复杂度显然是 O(n*lgN)。
解法二、O(n*lgN) 的算法,特别适合处理海量数据
我们可以先创建一个大小为 k 的数据容器来存储最小的 k 个数字。接下来,我们每次从输入的 n 个整数中读入一个数。
1、如果容器中已有的数字少于 K 个,则直接把这次读入的整数放如容器之中;
2、如果容器已满,此时我们不能再插入新的数字而只能替换已有的数字,并找到已有的 K 个数字中的最大值 KMax。然后与这次待插入的整数和最大值 KMax 进行比较,如果待插入的值比当前已有的最大值小,则用这个数替换当前已有的最大值 KMax;如果待插入的值比当前已有的最大值还要大,那么这个数不可能是最小的 k 个整数之一,于是我们可以抛弃这个整数。
因此当容器满了之后,我们要做 3 件事情:
1)在 k 个整数中找到最大数;
2)有可能在这个容器中删除最大数;(待插入的值比当前已有的最大值小)
3)有可能要插入一个新的数字。(替换删除KMax)
如果利用一个二叉树(如最大堆)来实现这个数据容器,那么我们能在 O(lgN) 时间内实现这三个操作。因此对于 n 个输入数字而言,总的时间效率就是 O(n*lgN)。
在最大堆中,根结点的值总是大于它的子树中的任意结点的值。于是我们每次都可以在 O(1) 得到已有的 k 个数字中的最大值,需要 O(logK)时间完成插入及删除操作。
找到并打印最小的K个数:
1 void printKMinNums(int *arr, int nLen, int Kth) // 打印出最小的k个数 2 { 3 if(arr == NULL || nLen <= 0 || Kth <= 0 || nLen < Kth) 4 return; 5 6 Create_Heap(arr, Kth); // 建立只有k个数的大顶堆 7 8 int i; 9 for(i = Kth; i < nLen; ++i) // 遍历其余数字 10 { 11 if(arr[i] < arr[0]) // 比最大值小,删除最大值,插入当前值 12 { 13 swap(arr, arr + i); // 与当前最大值交换 14 adjustHeap(arr, 0, Kth); // 调整大顶堆,找出大顶堆的最大值 15 } 16 } 17 18 printf("The KMinNums: "); // 打印最小的k个数 19 printArr(arr, Kth); 20 }
建立大顶堆:
1 void Create_Heap(int *arr, int nLen) // 建堆 2 { 3 int i; 4 for(i = nLen/2 - 1; i >= 0; --i) // 从最后一个含有孩子的结点开始 5 { 6 adjustHeap(arr, i, nLen); 7 } 8 }
调整为大顶堆:
1 void adjustHeap(int *arr, int index, int nLen) // 调整 2 { 3 int left = 2 * index + 1; 4 int maxIndex; // 根结点与左右孩子三者之间的最大值的下标 5 6 while(left < nLen) 7 { 8 if(arr[left] > arr[index]) // left Child 9 maxIndex = left; 10 else 11 maxIndex = index; 12 13 if(left + 1 < nLen && arr[left+1] > arr[maxIndex]) // right Child 14 maxIndex = left + 1; 15 16 if(maxIndex != index) // 不是当前根结点 17 { 18 swap(arr + index, arr + maxIndex); 19 index = maxIndex; 20 left = 2 * index + 1; 21 } 22 else 23 break; 24 } 25 }
完整的代码如下:
1 #include "stdio.h" 2 #include "stdlib.h" 3 #include "time.h" 4 5 #define N 9 6 7 void Create_Heap(int *arr, int nLen); // 建堆 8 void adjustHeap(int *arr, int index, int nLen); // 调整 9 10 void swap(int *left, int *right) 11 { 12 int tmp = *left; 13 *left = *right; 14 *right = tmp; 15 } 16 17 void initArr(int *arr, int nLen) 18 { 19 int i; 20 for(i = 0; i < nLen; ++i) 21 arr[i] = rand()%100; 22 } 23 24 void printArr(int *arr, int nLen) 25 { 26 int i; 27 for(i = 0; i < nLen; ++i) 28 printf("%d ", arr[i]); 29 printf("\n"); 30 } 31 32 // ========================================分割线====================================== 33 34 void printKMinNums(int *arr, int nLen, int Kth) // 打印出最小的k个数 35 { 36 if(arr == NULL || nLen <= 0 || Kth <= 0 || nLen < Kth) 37 return; 38 39 Create_Heap(arr, Kth); // 建立只有k个数的大顶堆 40 41 int i; 42 for(i = Kth; i < nLen; ++i) // 遍历其余数字 43 { 44 if(arr[i] < arr[0]) // 比最大值小,删除最大值,插入当前值 45 { 46 swap(arr, arr + i); // 与当前最大值交换 47 adjustHeap(arr, 0, Kth); // 调整大顶堆,找出大顶堆的最大值 48 } 49 } 50 51 printf("The KMinNums: "); // 打印最小的k个数 52 printArr(arr, Kth); 53 } 54 55 56 void adjustHeap(int *arr, int index, int nLen) // 调整 57 { 58 int left = 2 * index + 1; 59 int maxIndex; // 根结点与左右孩子三者之间的最大值的下标 60 61 while(left < nLen) 62 { 63 if(arr[left] > arr[index]) // left Child 64 maxIndex = left; 65 else 66 maxIndex = index; 67 68 if(left + 1 < nLen && arr[left+1] > arr[maxIndex]) // right Child 69 maxIndex = left + 1; 70 71 if(maxIndex != index) // 不是当前根结点 72 { 73 swap(arr + index, arr + maxIndex); 74 index = maxIndex; 75 left = 2 * index + 1; 76 } 77 else 78 break; 79 } 80 } 81 82 void Create_Heap(int *arr, int nLen) // 建堆 83 { 84 int i; 85 for(i = nLen/2 - 1; i >= 0; --i) // 从最后一个含有孩子的结点开始 86 { 87 adjustHeap(arr, i, nLen); 88 } 89 } 90 91 92 int main(int argc, char *argv[]) 93 { 94 srand(time(NULL)); 95 96 int arr[N] = {0}; 97 initArr(arr, N); 98 printf("Before: "); 99 printArr(arr, N); 100 101 printKMinNums(arr, N, 5); // 最小的五个数 102 103 printf("After: "); 104 printArr(arr, N); 105 106 return 0; 107 }
本文完。