【问题标题】:Algorithm to find k smallest numbers in array of n items在 n 个项目的数组中找到 k 个最小数字的算法
【发布时间】:2011-03-21 16:28:28
【问题描述】:

我正在尝试编写一种算法,该算法可以在 O(n) 时间内打印 n 大小数组中的 k 个最小数字,但我无法将时间复杂度降低到 n。我该怎么做?

【问题讨论】:

  • 我认为有必要澄清一下。您是否在 N 个数字的数组中寻找 K 个最小的数字?
  • 不,这就是练习中写的所有解释......我想我必须在数组中显示所有 k 个小数字...... :(
  • @Jessica 在这里提出了类似的问题:gateoverflow.in/27194/tifr2014-b-9

标签: arrays algorithm selection


【解决方案1】:

我之前在一次采访中做过这个,最优雅/最有效的方法之一是

O(n log k). 
with space: O(k) (thanks, @Nzbuu)

基本上,您将使用大小限制为 k 的最大堆。对于数组中的每个项目,检查它是否小于最大值(仅 O(1))。如果是,则删除最大值并将其放入堆中(O(log k))。如果它更大,请转到下一项。

当然,堆不会产生 k 个项目的排序列表,但这可以在 O(k log k) 中完成,这很容易。

同样,你可以用同样的方法找到最大的 k 个项目,在这种情况下你会使用最小堆。

【讨论】:

  • +1。这就是我会做的。它也很容易实现,只需要 O(k) 空间。
  • @Geek 堆被初始化为空,然后您想遍历数组中的前 k 个项目以填充它。然后按照我的描述继续,你的代码将把堆的大小保持在常数 k。堆是一种标准的树数据结构,通常每个节点有两个子节点(参见:en.wikipedia.org/wiki/Heap_(data_structure)
  • Blum 等人的选择算法在最坏的情况下占用O(n) 时间和O(1) 空间。即使你想报告排序的项目,你也可以在O(n + k log(k))time/O(1)space 中进行。
  • @btilly:根据经验混合渐近界限和“实际”考虑因素的论点是不可接受的。
  • 在这种情况下,如果 k 和 n 在大小上相当,理论上正确的选择算法会更快。如果 k 远小于 n,理论上错误的堆算法会更快,如果您的数据存储在磁盘上,则速度会加倍。因此,从业者应该知道这两种解决方案,并知道在优化很重要时使用哪个解决方案。
【解决方案2】:

您需要使用“选择算法”找到第 k 个最小的元素,即 O(n),然后再次迭代数组并返回每个更小/等于它的元素。
选择算法:http://en.wikipedia.org/wiki/Selection_algorithm
如果您有重复,则必须注意:您需要确保返回的元素不超过 k 个(例如,如果您有 1,2,...,k,k,k ,...)

编辑:
完整的算法,并根据要求返回一个列表:设数组为A

 1. find the k'th element in A using 'selection algorithm', let it be 'z'
 2. initialize an empty list 'L'
 3. initialize counter<-0
 4. for each element in A: 
 4.1. if element < z: 
   4.1.1. counter<-counter + 1 ; L.add(element)
 5. for each element in A:
 5.1. if element == z AND count < k:
   5.1.1. counter<-counter + 1 ; L.add(element)
 6. return L

请注意,如果您的列表可能有重复项,则需要进行第 3 次迭代。如果不能 - 没必要,只需将 4.1 中的条件更改为 另请注意:L.add 将元素插入到链表中,因此是 O(1)。

【讨论】:

  • 第五步的小优化: 5. while ( counter
  • @srikfreak:我决定不做这个优化,因为它显然是硬件问题,所以我想通过避免添加条件来保持它简单易懂。在将这个算法实现到一个真实的软件的情况下——你当然是对的,这个优化是必须要做的。
【解决方案3】:

假设您要显示 K 个最小的数字,您可以使用 Hoare 的 Select 算法来找到第 kth 个最小的数字。这会将数组划分为较小的数字、第 kth 数字和较大的数字。

【讨论】:

  • +1,虽然要注意 Hoare 的 快速选择算法不是 O(n),但它有最坏的情况。固定版本被称为“median-of-medians”方法,并不是由 Hoare 提出的。
【解决方案4】:

可以在O(n) 时间内找到 n 个元素中最小的 k 个(我的意思是真正的O(n) 时间,而不是O(n + some function of k))。请参阅the Wikipedia article "Selection algorithm",尤其是关于“无序部分排序”和“中值选择作为枢轴策略”的小节,以及构成此O(n) 的基本部分的the article "Median of medians"

【讨论】:

    【解决方案5】:

    这可以在预期的线性时间 (O(n)) 内完成。首先找到数组的kth 最小元素(使用枢轴分区方法查找kth 顺序统计),然后简单地遍历循环以检查哪些元素小于kth 最小元素。请注意,这仅适用于不同的元素。

    这是c中的代码:

        /*find the k smallest elements of an array in O(n) time. Using the Kth order 
    statistic-random pivoting algorithm to find the kth smallest element and then looping 
    through the array to find the elements smaller than kth smallest element.Assuming 
    distinct elements*/
    
    
        #include <stdio.h>
        #include <math.h>
        #include <time.h>
        #define SIZE 10
        #define swap(X,Y) {int temp=X; X=Y; Y=temp;}
    
    
        int partition(int array[], int start, int end)
        {
            if(start==end)
                return start;
            if(start>end)
                return -1;
            int pos=end+1,j;
            for(j=start+1;j<=end;j++)
            {       
                if(array[j]<=array[start] && pos!=end+1)
                {
                    swap(array[j],array[pos]);
                    pos++;
                }
                else if(pos==end+1 && array[j]>array[start])
                    pos=j;
            }
            pos--;
            swap(array[start], array[pos]);
            return pos;
        }
    
        int order_statistic(int array[], int start, int end, int k)
        {
            if(start>end || (end-start+1)<k)
                return -1;                   //return -1 
            int pivot=rand()%(end-start+1)+start, position, p;
            swap(array[pivot], array[start]);
            position=partition(array, start, end);
            p=position;
            position=position-start+1;                  //size of left partition
            if(k==position)
                return array[p];
            else if(k<position)
                return order_statistic(array, start,p-1,k);
            else
                return order_statistic(array,p+1,end,k-position);
        }
    
    
        void main()
        {
            srand((unsigned int)time(NULL));
            int i, array[SIZE],k;
            printf("Printing the array...\n");
            for(i=0;i<SIZE;i++)
                array[i]=abs(rand()%100), printf("%d ",array[i]);
            printf("\n\nk=");
            scanf("%d",&k);
            int k_small=order_statistic(array,0,SIZE-1,k);
            printf("\n\n");
            if(k_small==-1)
            {
                printf("Not possible\n");
                return ;
            }
            printf("\nk smallest elements...\n");
            for(i=0;i<SIZE;i++)
            {
                if(array[i]<=k_small)
                    printf("%d ",array[i]);
            }
        }
    

    【讨论】:

      【解决方案6】:

      该问题的最佳解决方案如下。使用快速排序找到枢轴并丢弃第k个元素不存在的部分,并递归查找下一个枢轴。 (这是第 k 个 Max finder,您需要更改 if else 条件以使其成为第 k 个 Min Finder)。这是 JavaScript 代码-

        // Complexity is O(n log(n))
        var source = [9, 2, 7, 11, 1, 3, 14, 22];
      
        var kthMax = function(minInd, MaxInd, kth) {
            // pivotInd stores the pivot position 
            // for current iteration
            var temp, pivotInd = minInd;
            if (minInd >= MaxInd) {
              return source[pivotInd];
            }
      
            for (var i = minInd; i < MaxInd; i++) {
              //If an element is greater than chosen pivot (i.e. last element)
              //Swap it with pivotPointer element. then increase ponter
              if (source[i] > source[MaxInd]) {
                temp = source[i];
                source[i] = source[pivotInd];
                source[pivotInd] = temp;
                pivotInd++;
              }
            }
            // we have found position for pivot elem. 
            // swap it to that position place .
            temp = source[pivotInd];
            source[pivotInd] = source[MaxInd];
            source[MaxInd] = temp;
      
            // Only try to sort the part in which kth index lies.
            if (kth > pivotInd) {
              return kthMax(pivotInd + 1, MaxInd, kth);
            } else if (kth < pivotInd) {
              return kthMax(minInd, pivotInd - 1, kth);
            } else {
              return source[pivotInd];
            }
      
          }
          // last argument is kth-1 , so if give 2 it will give you,
          // 3rd max which is 11
      
        console.log(kthMax(0, source.length - 1, 2));
      

      【讨论】:

      • 更糟糕的快速排序是 O(n*n)
      • 是的,但是,最好的情况是 O(n log n) ,这是解决这个问题的最好方法。如果您选择合并或堆,则需要 O(n) 额外内存,因此对于平均 n log n 排序解决方案来说,快速排序是完全可以接受的。
      【解决方案7】:

      另一种技术 - 使用 QuickSelect 算法,结果将是返回结果左侧的所有元素。平均时间复杂度为 O(n),在最坏的情况下为 O(n^2)。空间复杂度为 O(1)。

      【讨论】:

        【解决方案8】:

        我不完全知道您在寻找什么,但非常简单的 O(n * k) 时间和 O(k) 空间。这是最大的 K,所以需要翻转它。

        对于 k (结果)的 min 的蛮力可以替代一个堆

        private int[] FindKBiggestNumbersM(int[] testArray, int k)
        {
            int[] result = new int[k];
            int indexMin = 0;
            result[indexMin] = testArray[0];
            int min = result[indexMin];
        
            for (int i = 1; i < testArray.Length; i++)
            {
                if(i < k)
                {
                    result[i] = testArray[i];
                    if (result[i] < min)
                    {
                        min = result[i];
                        indexMin = i;
                    }
                }
                else if (testArray[i] > min)
                {
                    result[indexMin] = testArray[i];
                    min = result[indexMin];
                    for (int r = 0; r < k; r++)
                    {
                        if (result[r] < min)
                        {
                            min = result[r];
                            indexMin = r;
                        }
                    }
                }
            }
            return result;
        }
        

        【讨论】:

          【解决方案9】:

          我相信这可以使用 O(n) 空间在 O(n) 时间内完成。如前所述,您可以使用 Hoares 算法或快速选择的变体。

          基本上你在数组上运行快速排序,但只在分区的一侧运行,以确保有 K 或 K-1 个大于枢轴的元素(你可以包括 lr 排除枢轴)。如果不需要对列表进行排序,那么,您可以从枢轴打印数组的剩余部分。由于可以就地进行快速排序,因此这需要 O(n) 空间,并且由于您将每次检查的数组部分(平均)减半,因此需要 O(2n) == O(n) 时间

          【讨论】:

            【解决方案10】:

            只需使用 Merge Sort 对数组进行排序,然后打印第一个 k 数,最坏情况下需要 n*log2(n)。

            【讨论】:

            • @amit gr:你说得对,我只是在你评论的时候更正了这一点,谢谢你的评论。
            • 我试过了,但盒子上的练习建议用 O(n) 编写一个算法......
            • 我认为这不可能。
            【解决方案11】:

            如何使用堆来存储值。当您遍历数组中的每个值时,此成本为 n。

            然后通过Heap得到最小的k值。

            运行时间为 O(n) + O(k) = O(n)

            当然,内存空间现在是O(n + n)

            【讨论】:

            • 请注意,从堆中删除是 O(n),所以这个解决方案将导致 O(n+klog(n)) 可能比大 k 的 O(n) 更大
            • 对于这个问题,按照定义,k是固定的,所以O(k log(n))是o(n)。
            【解决方案12】:

            如前所述,有两种方法可以完成这样的任务:

            1) 您可以使用quicksortheapsort 或任何您想要的O (n log n) 排序算法对整个n 元素数组进行排序,然后选择数组中的m 最小值。此方法适用于O(n log n)

            2) 您可以使用selection algorithm 来查找m 数组中的最小元素。找到第 kth 个最小值需要 O(n) 时间,因为您将迭代此算法 m 次,所以总时间将是 m x O(n) = O(n)

            【讨论】:

              【解决方案13】:

              这是递归的基本条件的一个细微变化,在选择算法中,返回指向包含所有前 k 个随机顺序最小元素的动态数组的指针,它是 O(n)。

              void swap(int *a, int *b){
              int temp = *a;
              *a = *b;
              *b = temp;
              }
              int partition(int *A, int left, int right){
              int pivot = A[right], i = left, x;
              
              for (x = left; x < right; x++){
                  if (A[x] < pivot){
                      swap(&A[i], &A[x]);
                      i++;
                  }
              }
              
              swap(&A[i], &A[right]);
              return i;
              }
              
              
               int* quickselect(int *A, int left, int right, int k){
              
              //p is position of pivot in the partitioned array
              int p = partition(A, left, right);
              
              //k equals pivot got lucky
              if (p == k-1){
                  int*temp = malloc((k)*sizeof(int));
                  for(int i=left;i<=k-1;++i){
                      temp[i]=A[i];
                  }
                  return temp;
              }
              //k less than pivot
              else if (k - 1 < p){
                  return quickselect(A, left, p - 1, k);
              }
              //k greater than pivot
              else{
                  return quickselect(A, p + 1, right, k);
              }
              

              }

              【讨论】:

                猜你喜欢
                • 2016-03-20
                • 1970-01-01
                • 1970-01-01
                • 2015-06-07
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-04-29
                相关资源
                最近更新 更多