web-dev-qa-db-fra.com

Algorithme pour trouver k plus petits nombres dans un tableau de n éléments

J'essaie d'écrire un algorithme qui peut imprimer les k plus petits nombres dans un tableau de taille n en O(n) temps, mais je ne peux pas réduire la complexité du temps à n. Comment Je fais ça?

22
Jessica

Je l'ai déjà fait dans une interview auparavant, et l'un des moyens les plus élégants/efficaces de le faire est

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

Fondamentalement, vous allez utiliser un tas max de taille limitée à k. Pour chaque élément du tableau, vérifiez s'il est plus petit que le maximum (uniquement O (1)). Si c'est le cas, mettez-le dans le tas (O (log k)) et retirez le max. Si c'est plus gros, passez à l'élément suivant.

Bien sûr, le tas ne donne pas une liste triée de k éléments, mais cela peut être fait dans O (k log k), ce qui est facile.

De même, vous pouvez faire de même pour trouver les plus gros éléments k, auquel cas vous utiliseriez un min-tas.

41
Chet

vous devrez trouver le kème plus petit élément en utilisant 'algorithme de sélection', qui est O (n), puis itérer à nouveau le tableau et retourner chaque élément qui est plus petit/égal à lui.
algorithme de sélection: http://en.wikipedia.org/wiki/Selection_algorithm
vous devrez faire attention si vous avez des répétitions: vous devrez vous assurer que vous ne retournez pas plus de k éléments (c'est possible si par exemple vous avez 1,2, ..., k, k , k, ...)

MODIFIER:
l'algorithme complet, et renvoyer une liste, comme demandé: laissez le tableau être 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

notez ici qu'une 3e itération est requise si votre liste peut contenir des doublons. si ce n'est pas possible - c'est inutile, changez simplement la condition de 4.1 en <=.
note également: L.add insère un élément dans une liste chaînée et est donc O (1).

25
amit

En supposant que vous essayez d'afficher les K plus petits nombres, vous pouvez utiliser l'algorithme Select de Hoare pour trouver le ke le plus petit nombre. Qui partitionne le tableau en plus petits nombres, le ke nombre, et les plus grands nombres.

5
Jerry Coffin

La meilleure solution possible au problème est la suivante: utilisez Tri rapide pour trouver les pivots et éliminer la partie où ce kème élément ne se trouve pas, et trouvez récursivement le pivot suivant. (C'est kth Max Finder, vous devez changer la condition if else pour en faire kth Min Finder) .Voici le JavaScript code-

  // 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));
1
sapy

Il est possible de trouver le k plus petit des n éléments dans O(n) temps (j'entends par là vrai O(n) temps, pas O(n + some function of k)). Reportez-vous à l'article Wikipédia "Algorithme de sélection" , en particulier aux sous-sections sur le "tri partiel non ordonné" et "la sélection médiane en tant que stratégie de pivot", ainsi qu'à l'article "Médiane des médianes" pour la pièce essentielle qui fait cette O(n).

1
David K

Cela peut être fait dans le temps linéaire attendu (O (n)). Recherchez d'abord le kth le plus petit élément du tableau (en utilisant la méthode de partition pivot pour trouver la statistique d'ordre kth), puis parcourez simplement la boucle pour vérifier quels éléments sont inférieurs au kth le plus petit élément. Notez que cela ne fonctionne correctement que pour un élément distinct.

Voici le code en 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]);
        }
    }
1
sudeepdino008

Je ne sais pas exactement ce que vous cherchez, mais un temps O (n * k) et O(k) espace) assez simple. C'est le plus grand K, il faudrait donc le floper.

Car la brute pour min de k (résultat) peut substituer un tas

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;
}
1
paparazzo

Cela peut être fait dans O(n) temps en utilisant O(n) espace je crois. Comme cela a été mentionné, vous pouvez utiliser l'algorithme de Hoares, ou une variation de sélection rapide.

Fondamentalement, vous exécutez Quicksort sur la baie, mais uniquement sur le côté de la partition nécessaire pour vous assurer qu'il y a K ou K-1 éléments plus grands que le pivot (vous pouvez soit inclure lr exclure le pivot). Si la liste n'a pas besoin d'être triée, vous pouvez simplement imprimer le reste du tableau à partir du pivot. Étant donné que le tri rapide peut être effectué sur place, cela prend O(n) espace, et puisque vous divisez par deux la partie du tableau (en moyenne) que vous vérifiez à chaque fois, c'est O(2n) == O(n) temps

1
Aaron Parry

Une autre technique: utilisez l'algorithme QuickSelect et le résultat serait tous les éléments à gauche du résultat renvoyé. La complexité temporelle moyenne serait O(n) et dans le pire des cas, elle serait O (n ^ 2). La complexité spatiale serait O (1).

1
Neel Shah

Triez simplement le tableau avec Merge Sort puis imprimez le premier nombre k, cela prendra n * log2 (n) dans le pire des cas.

0
Tamer Shlash

Comme mentionné, il existe deux façons d'accomplir une telle tâche:

1) Vous pouvez trier l'ensemble du tableau des éléments n avec quicksort , heapsort ou n'importe quel algorithme de tri O (n log n) que vous souhaitez, puis choisissez les m les plus petites valeurs de votre tableau. Cette méthode fonctionnera dans O(n log n).

2) Vous pouvez utiliser algorithme de sélection pour fink m les plus petits éléments de votre tableau. Il faudra O(n) temps pour trouver la kème plus petite valeur, puisque vous itérerez cet algorithme m fois, la durée globale sera m x O(n) = O(n).

0
Mirac Suzgun

Il s'agit d'une légère variation de la condition de base de la récursivité, dans l'algorithme de sélection, pour retourner le pointeur sur le tableau dynamique contenant tous les k premiers plus petits éléments dans un ordre aléatoire, c'est 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);
}

}

0
Sakib malik

Que diriez-vous d'utiliser un tas pour stocker les valeurs. Ce coût est n lorsque vous parcourez chaque valeur du tableau.

Parcourez ensuite le tas pour obtenir les plus petites valeurs k.

Le temps d'exécution est O(n) + O(k) = O (n)

Bien sûr, l'espace mémoire est maintenant O (n + n)

0
Matthew Chan