在n个项的数组中找到k个最小数的算法

时间:2011-03-21 16:28:28

标签: arrays algorithm selection

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

13 个答案:

答案 0 :(得分:39)

我之前在接受采访时已经这样做了,其中一种最优雅/最有效的方法是

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

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

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

同样,你可以做同样的事情来寻找最大的k项,在这种情况下你会使用最小的堆。

答案 1 :(得分:25)

你需要使用'选择算法'找到第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中的条件更改为&lt; =。
还要注意:L.add正在向链表中插入一个元素,因此是O(1)。

答案 2 :(得分:5)

假设您正在尝试显示K个最小数字,您可以使用Hoare选择算法来查找k th 最小数字。将数组划分为较小的数字,k th 数字和较大的数字。

答案 3 :(得分:1)

这可以在预期的线性时间(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]);
        }
    }

答案 4 :(得分:1)

可以在O(n)时间内找到k个最小的n个元素(我的意思是真O(n)个时间,而不是O(n + some function of k))。请参阅the Wikipedia article "Selection algorithm",尤其是&#34;无序部分排序&#34;的小节。和&#34;中位选择作为支点策略&#34;,以及the article "Median of medians"作为制作此O(n)的基本部分。

答案 5 :(得分:1)

该问题的最佳解决方案如下:使用快速排序来查找枢轴并丢弃此第k个元素不存在的部分,并递归地找到下一个枢轴。 (它是第k个最大查找器,你需要改变if else条件才能使它成为最小的查找器。)这是 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));

答案 6 :(得分:1)

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

对于最小的k(结果)的粗暴可以替换堆

sys.path.append('/opt/gsnworkflow/')  
sys.path.append('/opt/gsnworkflow/Modules/')  
sys.path.append('/opt/gsnworkflow/GSNEventLoop/')  

答案 7 :(得分:1)

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

基本上,您在阵列上运行Quicksort,但是仅在分区的一侧运行,以确保比枢轴大K或K-1个元素(可以包括lr排除枢轴)。如果列表不需要排序,则只需从数据透视表中打印阵列的其余部分即可。由于可以在适当的位置进行快速排序,因此需要O(n)的空间,并且由于要对阵列中要检查的部分(平均)进行一半的处理,因此每次需要O(2n)== O(n)的时间

答案 8 :(得分:0)

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

答案 9 :(得分:0)

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

然后通过堆来获得最小的k值。

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

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

答案 10 :(得分:0)

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

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

2)您可以使用selection algorithm来渲染阵列中最小的元素O(n log n)m时间找到 kth 最小值,因为您将迭代此算法 m 次,总时间将为O(n)

答案 11 :(得分:0)

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

答案 12 :(得分:0)

这与递归的基本条件略有不同,在选择算法中,要返回指向以随机顺序包含所有前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);
}

}