zoukankan      html  css  js  c++  java
  • 【剑指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("
    ");
     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

    本文完。

  • 相关阅读:
    【BZOJ3489】A simple rmq problem【kd树】
    【BZOJ2716】天使玩偶【kd树】
    Codeforces Round #520 (Div. 2)
    Codeforces Round #521 (Div. 3)
    Educational Codeforces Round 54
    【hdu3507】Print Article 【斜率优化dp】
    【HDU5992】Finding Hotels 【KD树】
    【hdu4347】The Closest M Points 【KD树模板】
    【BZOJ2806】Cheat 【广义后缀自动机+单调队列优化dp+二分】
    SDSC2018 Day1
  • 原文地址:https://www.cnblogs.com/xfxu/p/4626846.html
Copyright © 2011-2022 走看看