Écrivez un programme pour trouver les 100 plus grands nombres sur un tableau de 1 milliard de nombres

300

J'ai récemment assisté à une interview où on m'a demandé «d'écrire un programme pour trouver les 100 plus grands nombres sur un tableau de 1 milliard de nombres».

Je n'ai pu donner qu'une solution de force brute qui consistait à trier le tableau en complexité temporelle O (nlogn) et à prendre les 100 derniers nombres.

Arrays.sort(array);

L'intervieweur cherchait une meilleure complexité temporelle, j'ai essayé quelques autres solutions mais je n'ai pas réussi à lui répondre. Existe-t-il une meilleure solution de complexité temporelle?

userx
la source
70
Le problème est peut-être que ce n'était pas une question de tri , mais une question de recherche .
geomagas
11
En tant que note technique, le tri n'est peut-être pas la meilleure façon de résoudre le problème, mais je ne pense pas que ce soit la force brute - je peux penser à des façons bien pires de le faire.
Bernhard Barker
88
Je viens de penser à une méthode de force brute encore plus stupide ... Trouvez toutes les combinaisons possibles de 100 éléments dans le tableau d'un milliard d'éléments et voyez laquelle de ces combinaisons a la plus grande somme.
Shashank
10
Notez que tous les algorithmes déterministes (et corrects) le sont O(1)dans ce cas, car il n'y a pas d'augmentation de dimension. L'intervieweur aurait dû demander "Comment trouver m les plus grands éléments d'un tableau de n avec n >> m?".
Bakuriu

Réponses:

328

Vous pouvez conserver une file d'attente prioritaire des 100 plus grands nombres, parcourir le milliard de nombres, chaque fois que vous rencontrez un nombre supérieur au plus petit nombre dans la file d'attente (le tête de la file d'attente), supprimer le tête de la file d'attente et ajouter le nouveau numéro à la file d'attente.

EDIT: comme l'a noté Dev, avec une file d'attente prioritaire implémentée avec un tas, la complexité de l'insertion dans la file d'attente estO(logN)

Dans le pire des cas, vous obtenez ce qui est mieux quebillionlog2(100)billionlog2(billion)

En général, si vous avez besoin des plus grands nombres K d'un ensemble de N nombres, la complexité est O(NlogK)plutôt que O(NlogN), cela peut être très important lorsque K est très petit par rapport à N.

EDIT2:

Le temps attendu de cet algorithme est assez intéressant, car à chaque itération une insertion peut ou non se produire. La probabilité que le ième nombre soit inséré dans la file d'attente est la probabilité qu'une variable aléatoire soit plus grande qu'au moins i-Kdes variables aléatoires de la même distribution (les k premiers nombres sont automatiquement ajoutés à la file d'attente). Nous pouvons utiliser des statistiques de commande (voir lien ) pour calculer cette probabilité. Par exemple, supposons que les nombres ont été sélectionnés aléatoirement de manière uniforme {0, 1}, la valeur attendue du (iK) ème nombre (parmi i nombres) est (i-k)/i, et le risque qu'une variable aléatoire soit plus grande que cette valeur est 1-[(i-k)/i] = k/i.

Ainsi, le nombre d'insertions attendu est:

entrez la description de l'image ici

Et le temps de fonctionnement prévu peut être exprimé comme suit:

entrez la description de l'image ici

(le ktemps de générer la file d'attente avec les premiers kéléments, puis les n-kcomparaisons et le nombre prévu d'insertions comme décrit ci-dessus, chacun prend un log(k)/2temps moyen )

Notez que lorsque Nest très grand par rapport à K, cette expression est beaucoup plus proche de nplutôt que de NlogK. C'est quelque peu intuitif, car dans le cas de la question, même après 10000 itérations (ce qui est très petit par rapport à un milliard), la chance qu'un nombre soit inséré dans la file d'attente est très faible.

Ron Teller
la source
6
Il s'agit en fait uniquement de O (100) pour chaque insert.
MrSmith42
8
@RonTeller Vous ne pouvez pas effectuer de recherche binaire dans une liste chaînée de manière efficace, c'est pourquoi une file d'attente prioritaire est généralement implémentée avec un tas. Votre temps d'insertion comme décrit est O (n) et non O (logn). Vous aviez raison la première fois (file d'attente ordonnée ou file d'attente prioritaire) jusqu'à ce que Skizz vous fasse vous-même une seconde supposition.
Dev
17
@ThomasJungblut billion est également une constante, donc si c'est le cas, c'est O (1): P
Ron Teller
9
@RonTeller: normalement, ce genre de questions concerne le fait de trouver les 10 premières pages de milliards de résultats de recherche Google, ou les 50 mots les plus fréquents pour un nuage de mots, ou les 10 chansons les plus populaires sur MTV, etc. Donc, je crois, dans des circonstances normales il est sûr de considérer k constant et petit par rapport à n. Cependant, il faut toujours garder à l'esprit ces "circonstances normales".
ffriend
5
Puisque vous avez des éléments 1G, échantillonnez 1000 éléments au hasard et choisissez les 100 plus grands. Cela devrait éviter les cas dégénérés (triés, triés en sens inverse, principalement triés), ce qui réduit considérablement le nombre d'insertions.
ChuckCottrill
136

Si cela est demandé dans une interview, je pense que l'intervieweur veut probablement voir votre processus de résolution de problèmes, pas seulement votre connaissance des algorithmes.

La description est assez générale, alors vous pouvez peut-être lui demander la plage ou la signification de ces chiffres pour clarifier le problème. Cela peut impressionner un intervieweur. Si, par exemple, ces chiffres représentent l'âge des personnes à l'intérieur d'un pays (par exemple la Chine), alors c'est un problème beaucoup plus facile. En supposant raisonnablement que personne en vie n'a plus de 200 ans, vous pouvez utiliser un tableau int de taille 200 (peut-être 201) pour compter le nombre de personnes du même âge en une seule itération. Ici, l'indice signifie l'âge. Après cela, c'est un morceau de gâteau pour trouver 100 plus grand nombre. D'ailleurs, cet algo est appelé tri de comptage .

Quoi qu'il en soit, rendre la question plus précise et plus claire est bon pour vous lors d'une interview.

jin
la source
26
Très bons points. Personne d'autre n'a demandé ou indiqué quoi que ce soit sur la répartition de ces chiffres - cela pourrait faire toute la différence dans la façon d'aborder le problème.
NealB
13
Je voudrais que cette réponse soit suffisante pour la prolonger. Lisez les nombres une fois pour obtenir les valeurs min / max afin de pouvoir assumer la distribution. Ensuite, prenez l'une des deux options. Si la plage est suffisamment petite, créez un tableau dans lequel vous pouvez simplement cocher les nombres au fur et à mesure qu'ils se produisent. Si la plage est trop grande, utilisez l'algorithme de tas trié discuté ci-dessus .... Juste une pensée.
Richard_G
2
Je suis d'accord, poser une question à l'intervieweur fait en effet beaucoup de différence. En fait, une question telle que vous êtes limité par la puissance de calcul ou non peut également vous aider à paralléliser la solution en utilisant plusieurs nœuds de calcul.
Sumit Nigam
1
@R_G Pas besoin de parcourir toute la liste. Assez pour échantillonner une petite fraction (par exemple, un million) de membres aléatoires de la liste pour obtenir des statistiques utiles.
Itamar
Pour ceux qui n'auraient pas pensé à cette solution, je recommanderais de lire sur le tri de comptage en.wikipedia.org/wiki/Counting_sort . C'est en fait une question d'interview assez courante: pouvez-vous trier un tableau mieux que O (nlogn). Cette question n'est qu'une extension.
Maxime Chéramy
69

Vous pouvez parcourir les nombres qui prennent O (n)

Chaque fois que vous trouvez une valeur supérieure au minimum actuel, ajoutez la nouvelle valeur à une file d'attente circulaire de taille 100.

Le min de cette file d'attente circulaire est votre nouvelle valeur de comparaison. Continuez à ajouter à cette file d'attente. S'il est plein, extrayez le minimum de la file d'attente.

Regenschein
la source
3
Ça ne marche pas. Par exemple, trouver le top 2 de {1, 100, 2, 99} donnera {100,1} comme top 2.
Skizz
7
Vous ne pouvez pas vous déplacer pour maintenir la file d'attente triée. (si vous ne voulez pas à chaque fois rechercher le plus petit élément dans la file d'attente des trous)
MrSmith42
3
@ MrSmith42 Le tri partiel, comme dans un tas, est suffisant. Voir la réponse de Ron Teller.
Christopher Creutzig
1
Oui, j'ai supposé en silence qu'une extraction-min-file d'attente est implémentée comme un tas.
Regenschein
Au lieu de la file d'attente circulaire, utilisez un tas minimal de taille 100, ce qui aura un minimum de cent en haut. Cela ne prendra que O (log n) pour l'insertion par rapport à o (n) en cas de file d'attente
techExplorer
33

Je me suis rendu compte que cela est étiqueté avec «algorithme», mais jetterai d'autres options, car il devrait probablement également être étiqueté «interview».

Quelle est la source du milliard de chiffres? S'il s'agit d'une base de données, «sélectionner la valeur dans l'ordre des tables en fonction de la valeur de desc limit 100» ferait très bien l'affaire - il pourrait y avoir des différences de dialecte.

S'agit-il d'un cas unique ou de quelque chose qui se répétera? Si répété, à quelle fréquence? S'il s'agit d'une donnée unique et que les données sont dans un fichier, alors 'cat srcfile | trier (options selon les besoins) | head -100 'vous permettra de faire rapidement un travail productif pour lequel vous êtes payé pendant que l'ordinateur gère cette corvée insignifiante.

Si elle se répète, vous conseillerez de choisir une approche décente pour obtenir la réponse initiale et de stocker / mettre en cache les résultats afin que vous puissiez continuellement signaler les 100 premiers.

Enfin, il y a cette considération. Êtes-vous à la recherche d'un emploi d'entrée de gamme et d'un entretien avec un manager geek ou un futur collègue? Si oui, alors vous pouvez lancer toutes sortes d'approches décrivant les avantages et les inconvénients techniques relatifs. Si vous recherchez un emploi plus managérial, abordez-le comme un gestionnaire, soucieux des coûts de développement et de maintenance de la solution, et dites "merci beaucoup" et partez si tel est le cas, l'intervieweur souhaite se concentrer sur les anecdotes CS . Lui et vous n'auriez probablement pas beaucoup de potentiel d'avancement là-bas.

Bonne chance pour la prochaine interview.

Fred Mitchell
la source
2
Réponse exceptionnelle. Tout le monde s'est concentré sur le côté technique de la question, tandis que cette réponse aborde la partie sociale de l'entreprise.
vbocan
2
Je n'aurais jamais imaginé que vous pourriez dire merci et laisser une interview et ne pas attendre qu'elle se termine. Merci d'avoir ouvert mon esprit.
UrsulRosu
1
Pourquoi ne pouvons-nous pas créer un tas de milliards d'éléments et extraire les 100 plus grands éléments. De cette façon, le coût = O (milliards) + 100 * O (log (milliards)) ??
Mohit Shah
17

Ma réaction immédiate serait d'utiliser un tas, mais il existe un moyen d'utiliser QuickSelect sans garder toutes les valeurs d'entrée à portée de main à tout moment.

Créez un tableau de taille 200 et remplissez-le avec les 200 premières valeurs d'entrée. Exécutez QuickSelect et jetez les 100 bas, vous laissant 100 places libres. Lisez les 100 valeurs d'entrée suivantes et réexécutez QuickSelect. Continuez jusqu'à ce que vous ayez exécuté l'intégralité de l'entrée par lots de 100.

À la fin, vous avez les 100 premières valeurs. Pour N valeurs, vous avez exécuté QuickSelect environ N / 100 fois. Chaque Quickselect coûte environ 200 fois une constante, donc le coût total est 2N fois une constante. Cela semble linéaire dans la taille de l'entrée pour moi, quelle que soit la taille du paramètre que je suis câblé pour être de 100 dans cette explication.

mcdowella
la source
10
Vous pouvez ajouter une optimisation petite mais peut-être importante: après avoir exécuté QuickSelect pour partitionner le tableau de taille 200, le minimum des 100 premiers éléments est connu. Ensuite, lors de l'itération sur l'ensemble des données, ne remplissez les 100 valeurs inférieures que si la valeur actuelle est supérieure au minimum actuel. Une implémentation simple de cet algorithme en C ++ est comparable à celle de libstdc ++ partial_sortexécutée directement sur un ensemble de données de 200 millions 32 bits int(créé via un MT19937, uniformément distribué).
dyp
1
Bonne idée - n'affecte pas l'analyse du pire des cas, mais semble en valoir la peine.
mcdowella
@mcdowella Ça vaut le coup d'essayer et je le ferai, merci!
userx
8
C'est exactement ce que fait Goyava Ordering.greatestOf(Iterable, int) . C'est un temps absolument linéaire et en un seul passage, et c'est un algorithme super mignon. FWIW, nous avons également des repères réels: ses facteurs constants sont un cheveu plus lents que la file d'attente prioritaire traditionnelle dans le cas moyen, mais cette implémentation est beaucoup plus résistante aux entrées du "pire des cas" (par exemple, les entrées strictement ascendantes).
Louis Wasserman
15

Vous pouvez utiliser l' algorithme de sélection rapide pour trouver le nombre à l'index (par ordre) [milliards-101], puis parcourir les nombres et trouver les nombres supérieurs à ce nombre.

array={...the billion numbers...} 
result[100];

pivot=QuickSelect(array,billion-101);//O(N)

for(i=0;i<billion;i++)//O(N)
   if(array[i]>=pivot)
      result.add(array[i]);

Cet algorithme Temps est: 2 XO (N) = O (N) (performance moyenne du cas)

La deuxième option comme le suggère Thomas Jungblut est:

Utilisez la construction de tas , le tas MAX prendra O (N), puis les 100 premiers nombres max seront en haut du tas, tout ce dont vous avez besoin est de les extraire du tas (100 XO (Log (N)).

Cet algorithme Time est: O (N) + 100 XO (Log (N)) = O (N)

One Man Crew
la source
8
Vous parcourez la liste entière trois fois. 1 bio. les entiers font environ 4 Go, que feriez-vous si vous ne pouviez pas les mettre en mémoire? quickselect est le pire choix possible dans ce cas. Répéter une fois et conserver un tas des 100 premiers éléments est à mon humble avis la solution la plus performante en O (n) (notez que vous pouvez couper le O (log n) des insertions de tas car n dans le tas est 100 = constant = très petit ).
Thomas Jungblut
3
Même si c'est toujours le cas O(N), faire deux QuickSelects et un autre balayage linéaire est bien plus lourd que nécessaire.
Kevin
Il s'agit du code PSEUDO, toutes les solutions ici prendront plus de temps (O (NLOG (N) ou 100 * O (N))
One Man Crew
1
100*O(N)(si c'est une syntaxe valide) = O(100*N)= O(N)(certes, 100 peut être variable, si c'est le cas, ce n'est pas strictement vrai). Oh, et Quickselect a la pire performance de O (N ^ 2) (aïe). Et s'il ne tient pas dans la mémoire, vous rechargerez les données du disque deux fois, ce qui est bien pire qu'une fois (c'est le goulot d'étranglement).
Bernhard Barker
Il y a le problème que c'est le temps d'exécution prévu, et non le pire des cas, mais en utilisant une stratégie de sélection de pivot décente (par exemple, choisir 21 éléments au hasard, et choisir la médiane de ces 21 comme pivot), alors le nombre de comparaisons peut être garanti avec une probabilité élevée d'être au plus (2 + c) n pour une constante arbitrairement petite c.
One Man Crew
10

Bien que l'autre solution quickselect ait été déclassée, le fait demeure que quickselect trouvera la solution plus rapidement que l'utilisation d'une file d'attente de taille 100. Quickselect a un temps d'exécution prévu de 2n + o (n), en termes de comparaisons. Une mise en œuvre très simple serait

array = input array of length n
r = Quickselect(array,n-100)
result = array of length 100
for(i = 1 to n)
  if(array[i]>r)
     add array[i] to result

Cela prendra en moyenne 3n + o (n) comparaisons. De plus, il peut être rendu plus efficace en utilisant le fait que la sélection rapide laissera les 100 plus grands éléments du tableau dans les 100 emplacements les plus à droite. Donc en fait, le temps de fonctionnement peut être amélioré à 2n + o (n).

Il y a le problème que c'est le temps d'exécution prévu, et non le pire des cas, mais en utilisant une stratégie de sélection de pivot décente (par exemple, choisir 21 éléments au hasard, et choisir la médiane de ces 21 comme pivot), alors le nombre de comparaisons peut être garanti avec une probabilité élevée d'être au plus (2 + c) n pour une constante arbitrairement petite c.

En fait, en utilisant une stratégie d'échantillonnage optimisée (par exemple, échantillonner des éléments sqrt (n) au hasard, et choisir le 99e centile), le temps d'exécution peut être ramené à (1 + c) n + o (n) pour arbitrairement petit c (en supposant que K, le nombre d'éléments à sélectionner est o (n)).

D'un autre côté, l'utilisation d'une file d'attente de taille 100 nécessitera des comparaisons O (log (100) n), et la base de log 2 de 100 est approximativement égale à 6,6.

Si nous pensons à ce problème dans le sens plus abstrait de choisir les plus grands éléments K dans un tableau de taille N, où K = o (N) mais K et N vont à l'infini, alors le temps d'exécution de la version de sélection rapide sera O (N) et la version de file d'attente sera O (N log K), donc dans ce sens, la sélection rapide est également asymptotiquement supérieure.

Dans les commentaires, il a été mentionné que la solution de file d'attente s'exécutera dans le temps prévu N + K log N sur une entrée aléatoire. Bien sûr, l'hypothèse d'entrée aléatoire n'est jamais valide à moins que la question ne l'énonce explicitement. La solution de file d'attente pourrait être faite pour traverser le tableau dans un ordre aléatoire, mais cela entraînera le coût supplémentaire de N appels vers un générateur de nombres aléatoires ainsi que soit en permutant l'ensemble du tableau d'entrée, soit en allouant un nouveau tableau de longueur N contenant le indices aléatoires.

Si le problème ne vous permet pas de vous déplacer dans les éléments du tableau d'origine et que le coût d'allocation de mémoire est élevé, la duplication du tableau n'est pas une option, c'est une autre affaire. Mais strictement en termes de durée de fonctionnement, c'est la meilleure solution.

mrip
la source
4
Votre dernier paragraphe est le point clé: avec un milliard de chiffres, il n'est pas possible de conserver toutes les données en mémoire ou d'échanger des éléments. (C'est du moins ainsi que j'interpréterais le problème, étant donné qu'il s'agissait d'une question d'entrevue.)
Ted Hopp
14
Dans toute question algorithmique, si la lecture des données pose problème, elle doit être mentionnée dans la question. La question indique "étant donné un tableau" non "étant donné un tableau sur le disque qui ne tient pas en mémoire et ne peut pas être manipulé selon le modèle de von neuman qui est la norme dans l'analyse des algorithmes". Ces jours-ci, vous pouvez obtenir un ordinateur portable avec 8 Go de RAM. Je ne sais pas d'où vient l'idée de conserver un milliard de numéros en mémoire. J'ai actuellement plusieurs milliards de numéros en mémoire sur mon poste de travail.
mrip
FYI Le pire cas d'exécution de quickselect est O (n ^ 2) (voir en.wikipedia.org/wiki/Quickselect ), et il modifie également l'ordre des éléments dans le tableau d'entrée. Il est possible d'avoir une solution O (n) dans le pire des cas, avec une très grande constante ( en.wikipedia.org/wiki/Median_of_medians ).
pts
Il est peu probable que le pire des cas de sélection rapide se produise de manière exponentielle, ce qui signifie que, pour des raisons pratiques, cela n'est pas pertinent. Il est facile de modifier la sélection rapide de sorte qu'avec une probabilité élevée, le nombre de comparaisons soit (2 + c) n + o (n) pour un c arbitrairement petit.
mrip
"le fait demeure que quickselect trouvera la solution plus rapidement que l'utilisation d'une file d'attente de taille 100" - Non. La solution de tas prend environ N + Klog (N) comparaisons contre la moyenne 2N pour la sélection rapide et 2,95 pour la médiane des médianes. C'est nettement plus rapide pour le K. donné
Neil G
5

prendre les 100 premiers chiffres du milliard et les trier. maintenant, parcourez simplement le milliard, si le nombre source est supérieur au plus petit des 100, insérez-les dans l'ordre de tri. Ce que vous obtenez est quelque chose de beaucoup plus proche de O (n) que de la taille de l'ensemble.

Samuel Thurston
la source
3
oups n'a pas vu la réponse plus détaillée que la mienne.
Samuel Thurston
Prenez les 500 premiers nombres environ et arrêtez-vous pour trier (et jetez les 400 bas) lorsque la liste se remplit. (Et il va sans dire que vous n'ajoutez alors à la liste que si le nouveau numéro est> le plus bas des 100 sélectionnés.)
Hot Licks
4

Deux options:

(1) Tas (PriorityQueue)

Conservez un min-tas d'une taille de 100. Parcourez le tableau. Une fois que l'élément est plus petit que le premier élément du tas, remplacez-le.

InSERT ELEMENT INTO HEAP: O(log100)
compare the first element: O(1)
There are n elements in the array, so the total would be O(nlog100), which is O(n)

(2) Modèle de réduction de carte.

Ceci est très similaire à l'exemple du nombre de mots dans hadoop. Travail de carte: comptez la fréquence ou le temps d'apparition de chaque élément. Réduire: Obtenez l'élément K supérieur.

Habituellement, je donnais au recruteur deux réponses. Donnez-leur ce qu'ils veulent. Bien sûr, le codage de réduction de la carte serait laborieux car vous devez connaître tous les paramètres exacts. Pas de mal à le pratiquer. Bonne chance.

Chris Su
la source
+1 pour MapReduce, je ne peux pas croire que vous étiez le seul à mentionner Hadoop pour un milliard de numéros. Et si l'intervieweur demandait 1k milliard de numéros? Vous méritez plus de votes positifs à mon avis.
Silviu Burcea
@Silviu Burcea Merci beaucoup. J'apprécie également MapReduce. :)
Chris Su
Bien que la taille de 100 soit constante dans cet exemple, vous devriez vraiment généraliser cela à une variable distincte. k. Comme 100 est aussi constant que 1 milliard, alors pourquoi donnez-vous à la taille du grand ensemble de nombres une variable de taille de n, et non pour le plus petit ensemble de nombres? Vraiment, votre complexité devrait être O (nlogk) qui n'est pas O (n).
Tom Heard
1
Mais mon point est que si vous répondez simplement à la question, 1 milliard est également fixé dans la question, alors pourquoi généraliser 1 milliard à n et non 100 à k. Suivant votre logique, la complexité devrait en fait être O (1) car à la fois 1 milliard et 100 sont fixes dans cette question.
Tom Heard
1
@ TomHeard Très bien. O (nlogk) Il n'y a qu'un seul facteur qui affectera les résultats. Cela signifie que si n augmente de plus en plus, le "niveau de résultat" augmentera linéairement. Ou nous pouvons dire, même avec des milliards de milliards, je peux toujours obtenir 100 plus grands nombres. Cependant, vous ne pouvez pas dire: avec l'augmentation de n, le k augmente de sorte que le k affectera le résultat. C'est pourquoi j'utilise O (nlogk) mais pas O (nlogn)
Chris Su
4

Une solution très simple serait de parcourir le tableau 100 fois. Ce qui est O(n).

Chaque fois que vous retirez le plus grand nombre (et modifiez sa valeur à la valeur minimale, de sorte que vous ne le voyez pas dans l'itération suivante, ou gardez une trace des index des réponses précédentes (en gardant une trace des index que le tableau d'origine peut avoir) multiple du même nombre)). Après 100 itérations, vous avez les 100 plus grands nombres.

James Oravec
la source
1
Deux inconvénients - (1) Vous détruisez l'entrée dans le processus - ceci est de préférence évité. (2) Vous parcourez le tableau plusieurs fois - si le tableau est stocké sur le disque et ne peut pas tenir dans la mémoire, cela pourrait facilement être presque 100 fois plus lent que la réponse acceptée. (Oui, ils sont tous les deux O (n), mais quand même)
Bernhard Barker
Bon appel @Dukeling, j'ai ajouté un libellé supplémentaire sur la façon d'éviter de modifier l'entrée d'origine en gardant une trace des indices de réponse précédents. Ce qui serait quand même assez facile à coder.
James Oravec
Un brillant exemple d'une solution O (n) beaucoup plus lente que O (n log n). log2 (1 milliard) n'est que de 30 ...
gnasher729
@ gnasher729 Quelle est la taille de la constante cachée dans O (n log n)?
miracle173
1

Inspiré par la réponse de @ron teller, voici un programme C barebones pour faire ce que vous voulez.

#include <stdlib.h>
#include <stdio.h>

#define TOTAL_NUMBERS 1000000000
#define N_TOP_NUMBERS 100

int 
compare_function(const void *first, const void *second)
{
    int a = *((int *) first);
    int b = *((int *) second);
    if (a > b){
        return 1;
    }
    if (a < b){
        return -1;
    }
    return 0;
}

int 
main(int argc, char ** argv)
{
    if(argc != 2){
        printf("please supply a path to a binary file containing 1000000000"
               "integers of this machine's wordlength and endianness\n");
        exit(1);
    }
    FILE * f = fopen(argv[1], "r");
    if(!f){
        exit(1);
    }
    int top100[N_TOP_NUMBERS] = {0};
    int sorts = 0;
    for (int i = 0; i < TOTAL_NUMBERS; i++){
        int number;
        int ok;
        ok = fread(&number, sizeof(int), 1, f);
        if(!ok){
            printf("not enough numbers!\n");
            break;
        }
        if(number > top100[0]){
            sorts++;
            top100[0] = number;
            qsort(top100, N_TOP_NUMBERS, sizeof(int), compare_function);
        }

    }
    printf("%d sorts made\n"
    "the top 100 integers in %s are:\n",
    sorts, argv[1] );
    for (int i = 0; i < N_TOP_NUMBERS; i++){
        printf("%d\n", top100[i]);
    }
    fclose(f);
    exit(0);
}

Sur ma machine (Core i3 avec un SSD rapide), cela prend 25 secondes et 1724 tris. J'ai généré un fichier binaire avec dd if=/dev/urandom/ count=1000000000 bs=1pour cette course.

Évidemment, il y a des problèmes de performances avec la lecture de seulement 4 octets à la fois - à partir du disque, mais c'est par exemple. Du côté positif, très peu de mémoire est nécessaire.


la source
1

La solution la plus simple consiste à analyser le grand tableau de milliards de chiffres et à conserver les 100 plus grandes valeurs trouvées jusqu'à présent dans un petit tampon de tableau sans aucun tri et à mémoriser la plus petite valeur de ce tampon. J'ai d'abord pensé que cette méthode avait été proposée par fordprefect mais dans un commentaire, il a dit qu'il supposait que la structure de données à 100 nombres était implémentée comme un tas. Chaque fois qu'un nouveau nombre est trouvé qui est plus grand, le minimum dans le tampon est écrasé par la nouvelle valeur trouvée et le tampon est à nouveau recherché pour le minimum actuel. Si les nombres en milliards de tableaux de nombres sont distribués de façon aléatoire la plupart du temps, la valeur du grand tableau est comparée au minimum du petit tableau et jetée. Seulement pour une très petite fraction du nombre, la valeur doit être insérée dans le petit tableau. Ainsi, la différence de manipulation de la structure de données contenant les petits nombres peut être négligée. Pour un petit nombre d'éléments, il est difficile de déterminer si l'utilisation d'une file d'attente prioritaire est en fait plus rapide que mon approche naïve.

Je veux estimer le nombre d'insertions dans le petit tampon de tableau à 100 éléments lorsque le tableau à 10 ^ 9 éléments est analysé. Le programme scanne les 1000 premiers éléments de ce grand tableau et doit insérer au plus 1000 éléments dans le tampon. Le tampon contient 100 éléments sur les 1000 éléments analysés, soit 0,1 de l'élément analysé. Nous supposons donc que la probabilité qu'une valeur du grand tableau soit supérieure au minimum actuel du tampon est d'environ 0,1. Un tel élément doit être inséré dans le tampon. Maintenant, le programme analyse les 10 ^ 4 éléments suivants du grand tableau. Parce que le minimum du tampon augmentera à chaque fois qu'un nouvel élément est inséré. Nous avons estimé que le rapport des éléments supérieurs à notre minimum actuel est d'environ 0,1 et il y a donc 0,1 * 10 ^ 4 = 1000 éléments à insérer. En fait, le nombre attendu d'éléments insérés dans le tampon sera plus petit. Après l'analyse de cette fraction de 10 ^ 4 éléments des nombres dans le tampon, il y aura environ 0,01 des éléments analysés jusqu'à présent. Ainsi, lors de la numérisation des 10 ^ 5 prochains nombres, nous supposons que pas plus de 0,01 * 10 ^ 5 = 1000 seront insérés dans le tampon. Poursuivant cette argumentation, nous avons inséré environ 7000 valeurs après avoir analysé 1000 + 10 ^ 4 + 10 ^ 5 + ... + 10 ^ 9 ~ 10 ^ 9 éléments du grand tableau. Ainsi, lors de la numérisation d'un tableau avec 10 ^ 9 éléments de taille aléatoire, nous n'attendons pas plus de 10 ^ 4 (= 7000 arrondis) insertions dans le tampon. Après chaque insertion dans le tampon, le nouveau minimum doit être trouvé. Si le tampon est un simple tableau, nous avons besoin d'une comparaison de 100 pour trouver le nouveau minimum. Si le tampon est une autre structure de données (comme un tas), nous avons besoin d'au moins 1 comparaison pour trouver le minimum. Pour comparer les éléments du grand tableau, nous avons besoin de 10 ^ 9 comparaisons. Donc, dans l'ensemble, nous avons besoin d'environ 10 ^ 9 + 100 * 10 ^ 4 = 1.001 * 10 ^ 9 comparaisons lors de l'utilisation d'un tableau comme tampon et d'au moins 1.000 * 10 ^ 9 comparaisons lors de l'utilisation d'un autre type de structure de données (comme un tas) . Ainsi, l'utilisation d'un segment n'apporte qu'un gain de 0,1% si les performances sont déterminées par le nombre de comparaison. Mais quelle est la différence de temps d'exécution entre l'insertion d'un élément dans un tas de 100 éléments et le remplacement d'un élément dans un tableau de 100 éléments et la recherche de son nouveau minimum? 000 * 10 ^ 9 comparaisons lors de l'utilisation d'un autre type de structure de données (comme un tas). Ainsi, l'utilisation d'un segment n'apporte qu'un gain de 0,1% si les performances sont déterminées par le nombre de comparaison. Mais quelle est la différence de temps d'exécution entre l'insertion d'un élément dans un tas de 100 éléments et le remplacement d'un élément dans un tableau de 100 éléments et la recherche de son nouveau minimum? 000 * 10 ^ 9 comparaisons lors de l'utilisation d'un autre type de structure de données (comme un tas). Ainsi, l'utilisation d'un segment n'apporte qu'un gain de 0,1% si les performances sont déterminées par le nombre de comparaison. Mais quelle est la différence de temps d'exécution entre l'insertion d'un élément dans un tas de 100 éléments et le remplacement d'un élément dans un tableau de 100 éléments et la recherche de son nouveau minimum?

  • Au niveau théorique: combien de comparaisons sont nécessaires pour insérer dans un tas. Je sais que c'est O (log (n)) mais quelle est la valeur du facteur constant? je

  • Au niveau de la machine: quel est l'impact de la mise en cache et de la prédiction de branche sur le temps d'exécution d'une insertion de segment de mémoire et d'une recherche linéaire dans un tableau.

  • Au niveau de l'implémentation: Quels coûts supplémentaires sont cachés dans une structure de données de tas fournie par une bibliothèque ou un compilateur?

Je pense que ce sont quelques-unes des questions auxquelles il faut répondre avant de pouvoir estimer la vraie différence entre les performances d'un tas de 100 éléments ou d'un tableau de 100 éléments. Il serait donc logique de faire une expérience et de mesurer les performances réelles.

miracle173
la source
1
C'est ce que fait un tas.
Neil G
@Neil G: Qu'est-ce que "ça"?
miracle173
1
Le haut du tas est l'élément minimal du tas et les nouveaux éléments sont rejetés avec une seule comparaison.
Neil G
1
Je comprends ce que vous dites, mais même si vous optez pour un nombre absolu de comparaisons plutôt que pour un nombre asymptotique de comparaisons, le tableau est encore beaucoup plus lent car le temps pour "insérer un nouvel élément, supprimer l'ancien minimum et trouver un nouveau minimum" est 100 plutôt qu'environ 7.
Neil G
1
D'accord, mais votre estimation est très détournée. Vous pouvez calculer directement le nombre attendu d'insertions à k (digamma (n) - digamma (k)), ce qui est inférieur à klog (n). Dans tous les cas, le tas et la solution de tableau ne dépensent qu'une seule comparaison pour supprimer un élément. La seule différence est que le nombre de comparaisons pour un élément inséré est de 100 pour votre solution contre jusqu'à 14 pour le tas (bien que le cas moyen soit probablement beaucoup moins.)
Neil G
1
 Although in this question we should search for top 100 numbers, I will 
 generalize things and write x. Still, I will treat x as constant value.

Algorithme Les plus grands éléments x de n:

J'appellerai la valeur de retour LISTE . C'est un ensemble de x éléments (à mon avis qui devrait être une liste liée)

  • Les premiers éléments x sont extraits du pool "au fur et à mesure" et triés dans la LISTE (cela se fait en temps constant puisque x est traité comme constant - O (x log (x)) temps)
  • Pour chaque élément qui vient ensuite, nous vérifions s'il est plus grand que le plus petit élément de la LISTE et si c'est le cas, nous sortons le plus petit et insérons l'élément actuel dans la LISTE. Comme c'est une liste ordonnée, chaque élément doit trouver sa place dans le temps logarithmique (recherche binaire) et comme c'est une insertion de liste ordonnée n'est pas un problème. Chaque étape se fait également en temps constant (temps O (log (x))).

Alors, quel est le pire des cas?

x log (x) + (nx) (log (x) +1) = nlog (x) + n - x

C'est donc le temps O (n) pour le pire des cas. Le +1 est la vérification si le nombre est supérieur au plus petit dans la LISTE. Le temps prévu pour le cas moyen dépendra de la distribution mathématique de ces n éléments.

Améliorations possibles

Cet algorithme peut être légèrement amélioré pour le pire des cas mais à mon humble avis (je ne peux pas prouver cette affirmation) qui dégradera le comportement moyen. Le comportement asymptotique sera le même.

L'amélioration de cet algorithme sera que nous ne vérifierons pas si l'élément est plus grand que le plus petit. Pour chaque élément, nous essaierons de l'insérer et s'il est plus petit que le plus petit, nous l'ignorerons. Bien que cela semble absurde si nous ne considérons que le pire des cas, nous aurons

x log (x) + (nx) log (x) = nlog (x)

opérations.

Pour ce cas d'utilisation, je ne vois aucune autre amélioration. Pourtant, vous devez vous demander - et si je dois faire cela plus que log (n) fois et pour différents x-es? Évidemment, nous trierions ce tableau dans O (n log (n)) et prendrions notre élément x chaque fois que nous en aurions besoin.

Rouz
la source
1

On répondrait à cette question avec la complexité N log (100) (au lieu de N log N) avec une seule ligne de code C ++.

 std::vector<int> myvector = ...; // Define your 1 billion numbers. 
                                 // Assumed integer just for concreteness 
 std::partial_sort (myvector.begin(), myvector.begin()+100, myvector.end());

La réponse finale serait un vecteur où les 100 premiers éléments sont garantis être les 100 plus grands nombres de votre tableau tandis que les éléments restants ne sont pas ordonnés

C ++ STL (bibliothèque standard) est assez pratique pour ce genre de problèmes.

Remarque: je ne dis pas que c'est la solution optimale, mais cela aurait sauvé votre entretien.

Vivian Miranda
la source
1

La solution simple consisterait à utiliser une file d'attente prioritaire, à ajouter les 100 premiers numéros à la file d'attente et à garder une trace du plus petit nombre dans la file d'attente, puis à parcourir les autres milliards de numéros, et chaque fois que nous en trouvons un qui est plus grand que le plus grand nombre dans la file d'attente prioritaire, nous supprimons le plus petit numéro, ajoutons le nouveau numéro et gardons à nouveau la trace du plus petit numéro dans la file d'attente.

Si les nombres étaient dans un ordre aléatoire, cela fonctionnerait très bien car comme nous parcourons un milliard de nombres aléatoires, il serait très rare que le nombre suivant soit parmi les 100 plus grands jusqu'à présent. Mais les chiffres ne sont peut-être pas aléatoires. Si le tableau était déjà trié par ordre croissant, nous insérions toujours un élément dans la file d'attente prioritaire.

Nous choisissons donc disons 100 000 nombres aléatoires dans le tableau en premier. Pour éviter un accès aléatoire qui pourrait être lent, nous ajoutons par exemple 400 groupes aléatoires de 250 numéros consécutifs. Avec cette sélection aléatoire, nous pouvons être sûrs que très peu des nombres restants sont dans les cent premiers, donc le temps d'exécution sera très proche de celui d'une simple boucle comparant un milliard de nombres à une valeur maximale.

gnasher729
la source
1

Il est préférable de trouver les 100 premiers sur un milliard de nombres en utilisant un tas minimal de 100 éléments.

Amorcez d'abord le min-tas avec les 100 premiers nombres rencontrés. min-heap stockera le plus petit des 100 premiers nombres à la racine (en haut).

Maintenant, au fur et à mesure que vous avancez, les autres chiffres ne les comparent qu'à la racine (la plus petite des 100).

Si le nouveau nombre rencontré est supérieur à la racine de min-heap, remplacez la racine par ce nombre, sinon ignorez-la.

Dans le cadre de l'insertion du nouveau numéro dans le tas min, le plus petit nombre dans le tas viendra en haut (racine).

Une fois que nous aurons parcouru tous les nombres, nous aurons les 100 plus grands nombres dans le tas.

imsaar
la source
0

J'ai écrit une solution simple en Python au cas où quelqu'un serait intéressé. Il utilise le bisectmodule et une liste de retour temporaire qu'il conserve triés. Ceci est similaire à une implémentation de file d'attente prioritaire.

import bisect

def kLargest(A, k):
    '''returns list of k largest integers in A'''
    ret = []
    for i, a in enumerate(A):
        # For first k elements, simply construct sorted temp list
        # It is treated similarly to a priority queue
        if i < k:
            bisect.insort(ret, a) # properly inserts a into sorted list ret
        # Iterate over rest of array
        # Replace and update return array when more optimal element is found
        else:
            if a > ret[0]:
                del ret[0] # pop min element off queue
                bisect.insort(ret, a) # properly inserts a into sorted list ret
    return ret

Utilisation avec 100 000 000 d'éléments et entrée dans le pire des cas, qui est une liste triée:

>>> from so import kLargest
>>> kLargest(range(100000000), 100)
[99999900, 99999901, 99999902, 99999903, 99999904, 99999905, 99999906, 99999907,
 99999908, 99999909, 99999910, 99999911, 99999912, 99999913, 99999914, 99999915,
 99999916, 99999917, 99999918, 99999919, 99999920, 99999921, 99999922, 99999923,
 99999924, 99999925, 99999926, 99999927, 99999928, 99999929, 99999930, 99999931,
 99999932, 99999933, 99999934, 99999935, 99999936, 99999937, 99999938, 99999939,
 99999940, 99999941, 99999942, 99999943, 99999944, 99999945, 99999946, 99999947,
 99999948, 99999949, 99999950, 99999951, 99999952, 99999953, 99999954, 99999955,
 99999956, 99999957, 99999958, 99999959, 99999960, 99999961, 99999962, 99999963,
 99999964, 99999965, 99999966, 99999967, 99999968, 99999969, 99999970, 99999971,
 99999972, 99999973, 99999974, 99999975, 99999976, 99999977, 99999978, 99999979,
 99999980, 99999981, 99999982, 99999983, 99999984, 99999985, 99999986, 99999987,
 99999988, 99999989, 99999990, 99999991, 99999992, 99999993, 99999994, 99999995,
 99999996, 99999997, 99999998, 99999999]

Il a fallu environ 40 secondes pour calculer cela pour 100 000 000 d'éléments, j'ai donc peur de le faire pour 1 milliard. Pour être juste cependant, je lui fournissais l'entrée du pire des cas (ironiquement un tableau qui est déjà trié).

Shashank
la source
0

Je vois beaucoup de discussions O (N), donc je propose quelque chose de différent juste pour l'exercice de réflexion.

Existe-t-il des informations connues sur la nature de ces chiffres? Si c'est de nature aléatoire, n'allez pas plus loin et regardez les autres réponses. Vous n'obtiendrez pas de meilleurs résultats qu'eux.

Toutefois! Vérifiez si le mécanisme de remplissage de liste a rempli cette liste dans un ordre particulier. Sont-ils dans un modèle bien défini où vous pouvez savoir avec certitude que la plus grande ampleur des nombres se trouvera dans une certaine région de la liste ou sur un certain intervalle? Il peut y avoir un motif. Si tel est le cas, par exemple s'ils sont garantis dans une sorte de distribution normale avec la bosse caractéristique au milieu, ont toujours des tendances à la hausse répétitives parmi les sous-ensembles définis, ont un pic prolongé à un certain moment T au milieu des données défini comme peut-être une incidence de délits d'initiés ou de panne d'équipement, ou peut-être simplement avoir un "pic" chaque Nième nombre comme dans l'analyse des forces après une catastrophe, vous pouvez réduire le nombre d'enregistrements que vous devez vérifier de manière significative.

Il y a de quoi réfléchir quand même. Peut-être que cela vous aidera à donner aux futurs intervieweurs une réponse réfléchie. Je sais que je serais impressionné si quelqu'un me posait une telle question en réponse à un problème comme celui-ci - cela me dirait qu'il pense à l'optimisation. Il suffit de reconnaître qu'il n'est pas toujours possible d'optimiser.

djdanlib
la source
0
Time ~ O(100 * N)
Space ~ O(100 + N)
  1. Créer une liste vide de 100 emplacements vides

  2. Pour chaque numéro dans la liste d'entrée:

    • Si le nombre est plus petit que le premier, sautez

    • Sinon, remplacez-le par ce numéro

    • Ensuite, poussez le numéro à travers l'échange adjacent; jusqu'à ce qu'il soit plus petit que le suivant

  3. Retourner la liste


Remarque: si le log(input-list.size) + c < 100, alors le moyen optimal est de trier la liste d'entrée, puis de diviser les 100 premiers éléments.

Khaled.K
la source
0

La complexité est O (N)

Créez d'abord un tableau de 100 ints initialisez le premier élément de ce tableau comme premier élément des N valeurs, gardez une trace de l'index de l'élément courant avec une autre variable, appelez-le CurrentBig

Itérer si les valeurs N

if N[i] > M[CurrentBig] {

M[CurrentBig]=N[i]; ( overwrite the current value with the newly found larger number)

CurrentBig++;      ( go to the next position in the M array)

CurrentBig %= 100; ( modulo arithmetic saves you from using lists/hashes etc.)

M[CurrentBig]=N[i];    ( pick up the current value again to use it for the next Iteration of the N array)

} 

une fois terminé, imprimez le tableau M de CurrentBig 100 fois modulo 100 :-) Pour l'étudiant: assurez-vous que la dernière ligne du code ne l'emporte pas sur les données valides juste avant la sortie du code

Angelos Karageorgiou
la source
0

Un autre algorithme O (n) -

L'algorithme trouve les 100 plus grands par élimination

considérer tous les millions de nombres dans leur représentation binaire. Commencez par le bit le plus significatif. Trouver si le MSB est 1 peut être fait par une multiplication d'opération booléenne avec un nombre approprié. S'il y a plus de 100 1 dans ces millions, éliminez les autres nombres avec des zéros. Maintenant, des nombres restants, passez au bit le plus significatif suivant. compter le nombre de numéros restants après élimination et continuer tant que ce nombre est supérieur à 100.

L'opération booléenne majeure peut être effectuée en parallèle sur les GPU

Panduranga Rao Sadhu
la source
0

Je découvrirais qui a eu le temps de mettre un milliard de numéros dans un tableau et de le virer. Doit travailler pour le gouvernement. Au moins, si vous aviez une liste chaînée, vous pourriez insérer un nombre au milieu sans déplacer un demi-milliard pour faire de la place. Encore mieux, un Btree permet une recherche binaire. Chaque comparaison élimine la moitié de votre total. Un algorithme de hachage vous permettrait de remplir la structure de données comme un damier mais pas si bon pour des données éparses. Comme il est préférable de disposer d'un tableau de solutions de 100 entiers et de garder une trace du nombre le plus bas dans votre tableau de solutions afin de pouvoir le remplacer lorsque vous rencontrez un nombre plus élevé dans le tableau d'origine. Vous devez regarder chaque élément du tableau d'origine en supposant qu'il n'est pas trié au départ.

David Allan Houser Jr
la source
0

Vous pouvez le faire à O(n)temps. Parcourez simplement la liste et suivez les 100 plus grands nombres que vous avez vus à un moment donné et la valeur minimale de ce groupe. Lorsque vous trouvez un nouveau nombre plus grand le plus petit de vos dix, remplacez-le et mettez à jour votre nouvelle valeur minimale de 100 (cela peut prendre un temps constant de 100 pour le déterminer à chaque fois que vous le faites, mais cela n'affecte pas l'analyse globale ).

James Oravec
la source
1
Cette approche est presque identique à la fois aux réponses les plus et aux deuxièmes mieux notées à cette question.
Bernhard Barker
0

La gestion d'une liste séparée est un travail supplémentaire et vous devez déplacer les choses dans la liste entière chaque fois que vous trouvez un autre remplaçant. Il suffit de le trier et de prendre le top 100.

Chris Fox
la source
-1 quicksort est O (n log n) qui est exactement ce que l'OP a fait et demande à améliorer. Vous n'avez pas besoin de gérer une liste séparée, seulement une liste de 100 numéros. Votre suggestion a également pour effet indésirable de modifier la liste d'origine ou de la copier. C'est à peu près 4 Go de mémoire.
0
  1. Utilisez le nième élément pour obtenir le 100e élément O (n)
  2. Répétez la deuxième fois, mais une seule fois et sortez chaque élément supérieur à cet élément spécifique.

Veuillez noter esp. la deuxième étape pourrait être facile à calculer en parallèle! Et ce sera également efficace lorsque vous aurez besoin d'un million de plus gros éléments.

math
la source
0

C'est une question de Google ou d'autres géants de l'industrie. Le code suivant est peut-être la bonne réponse attendue par votre interlocuteur. Le coût de temps et le coût d'espace dépendent du nombre maximal dans le tableau d'entrée.Pour l'entrée de tableau int 32 bits, le coût d'espace maximal est de 4 * 125M octets, le coût de temps est de 5 * milliards.

public class TopNumber {
    public static void main(String[] args) {
        final int input[] = {2389,8922,3382,6982,5231,8934
                            ,4322,7922,6892,5224,4829,3829
                            ,6892,6872,4682,6723,8923,3492};
        //One int(4 bytes) hold 32 = 2^5 value,
        //About 4 * 125M Bytes
        //int sort[] = new int[1 << (32 - 5)];
        //Allocate small array for local test
        int sort[] = new int[1000];
        //Set all bit to 0
        for(int index = 0; index < sort.length; index++){
            sort[index] = 0;
        }
        for(int number : input){
            sort[number >>> 5] |= (1 << (number % 32));
        }
        int topNum = 0;
        outer:
        for(int index = sort.length - 1; index >= 0; index--){
            if(0 != sort[index]){
                for(int bit = 31; bit >= 0; bit--){
                    if(0 != (sort[index] & (1 << bit))){
                        System.out.println((index << 5) + bit);
                        topNum++;
                        if(topNum >= 3){
                            break outer;
                        }
                    }
                }
            }
        }
    }
}
Su Xiang
la source
0

j'ai fait mon propre code, je ne sais pas si c'est ce que "l'intervieweur" cherche

private static final int MAX=100;
 PriorityQueue<Integer> queue = new PriorityQueue<>(MAX);
        queue.add(array[0]);
        for (int i=1;i<array.length;i++)
        {

            if(queue.peek()<array[i])
            {
                if(queue.size() >=MAX)
                {
                    queue.poll();
                }
                queue.add(array[i]);

            }

        }
Javier
la source
0

Améliorations possibles.

Si le fichier contient 1 milliard, sa lecture peut être très longue ...

Pour améliorer ce travail, vous pouvez:

  • Divisez le fichier en n parties, créez n threads, faites en sorte que n threads recherchent chacun les 100 plus grands nombres dans leur partie du fichier (en utilisant la file d'attente prioritaire), et enfin obtenez les 100 plus grands nombres de tous les threads en sortie.
  • Utilisez un cluster pour effectuer une telle tâche, avec une solution comme hadoop. Ici, vous pouvez diviser encore plus le fichier et obtenir la sortie plus rapidement pour un fichier de 1 milliard (ou 10 ^ 12) nombres.
Maxime B.
la source
0

Prenez d'abord 1000 éléments et ajoutez-les dans un tas maximum. Maintenant, sortez les 100 premiers éléments max et stockez-les quelque part. Maintenant, choisissez les 900 éléments suivants dans le fichier et ajoutez-les dans le tas avec les 100 derniers éléments les plus élevés.

Continuez à répéter ce processus consistant à récupérer 100 éléments du tas et à ajouter 900 éléments à partir du fichier.

Le choix final de 100 éléments nous donnera le maximum de 100 éléments à partir d'un milliard de nombres.

Juvenik
la source
-1

Problème: Trouver m les plus grands éléments de n éléments où n >>> m

La solution la plus simple, qui devrait être évidente pour tout le monde, consiste simplement à effectuer m passes de l'algorithme de tri à bulles.

puis imprimez les n derniers éléments du tableau.

Cela ne nécessite aucune structure de données externe et utilise un algorithme que tout le monde connaît.

Le temps d'exécution estimé est O (m * n). Jusqu'à présent, la meilleure réponse est O (n log (m)), donc cette solution n'est pas beaucoup plus chère pour les petits m.

Je ne dis pas que cela ne pourrait pas être amélioré, mais c'est de loin la solution la plus simple.

Chris Cudmore
la source
1
Pas de structures de données externes? Qu'en est-il du tableau de milliards de nombres à trier? Un tableau de cette taille représente un énorme surcoût en temps de remplissage et en espace de stockage. Et si tous les "grands" nombres se trouvaient à la mauvaise extrémité du tableau? Vous auriez besoin de l'ordre de 100 milliards de swaps pour les "bulle" en position - un autre gros frais généraux ... Enfin, M N = 100 milliards vs M Log2 (N) = 6,64 milliards, ce qui représente près de deux ordres de grandeur de différence. Peut-être repenser celui-ci. Une analyse en une seule passe tout en conservant une structure de données des plus grands nombres va considérablement dépasser cette approche.
NealB