【剑指offer】面试题30:最小的 K 个数

题目:输入 n 个整数,找出其中最小的 k 个数。例如输入4、51627388 个数字,则最小的 4 个数字是 1234

 这道题最简单的思路莫过于把输入的 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 }
View Code

 

本文完。

 

posted @ 2015-07-07 20:14  Stephen_Hsu  阅读(254)  评论(0编辑  收藏  举报