Supprimer les doublons efficacement et avec une surcharge de mémoire faible

9

Je veux filtrer efficacement une liste d'entiers pour les doublons d'une manière que seul l'ensemble résultant doit être stocké.

Cela peut être vu d'une manière:

  • nous avons une gamme d'entiers S={1,,N} avecN grand (disons240 )
  • nous avons une fonction f:SS avec, supposément, de nombreuses collisions (les images sont uniformément réparties dans S )
  • il faut alors stocker f[S] , c'est-à-dire {f(x)|xS}

J'ai une estimation (probabiliste) assez précise de ce qui |f[S]|est, et peut donc allouer des structures de données à l'avance (disons |f[S]|230 ).

J'ai eu quelques idées, mais je ne sais pas quelle serait la meilleure approche:

  • un jeu de bits est hors de question car l'ensemble d'entrée ne tient pas en mémoire.
  • une table de hachage, mais (1) cela nécessite une surcharge de mémoire, disons 150% de et (2) la table doit être explorée lors de sa construction, ce qui nécessite du temps supplémentaire en raison de la surcharge de la mémoire.|f[S]|
  • un tri "à la volée", de préférence avec une complexité (tri sans comparaison). À ce sujet, je ne sais pas quelle est la principale différence entre le tri par compartiment et le tri flash .O(N)
  • un tableau simple avec un arbre de recherche binaire, mais cela nécessite temps.O(Nlog|f[S]|)
  • peut-être que l'utilisation de filtres Bloom ou d'une structure de données similaire pourrait être utile pour détendre (avec des faux positifs) le problème.

Certaines questions sur stackoverflow semblent s'attaquer à ce genre de choses ( /programming/12240997/sorting-array-in-on-run-time , /programming/3951547/java -array-find-duplicates ), mais aucun ne semble correspondre à mes exigences.

doc
la source
2
Avez-vous besoin d'énumérer f [S] (quel qu'il soit), ou de pouvoir dire rapidement s'il y a du x?
Gilles 'SO- arrête d'être méchant'
@ Gilles: Je pense que, comme aucune structure évidente ne peut être trouvée dans f [S], les deux solutions sont équivalentes.
doc
Vos chiffres ne correspondent pas. L'image attendue d'une fonction aléatoire sur un domaine de taille est à peu près ( une - une / e ) N . Un autre problème est que passer par 2 56 va prendre trop de temps, sauf si vous avez un supercalculateur ou un grand cluster à votre disposition. N(11/e)N256
Yuval Filmus
1
Le temps pour l'arbre de recherche binaire serait , qui peut être proche ou non de O ( N log N ) en pratique mais qui est toujours plus précis. O(Nlog|f[S]|)O(NlogN)
jmad
1
Avec , un algorithme de temps linéaire ne sera-t-il pas trop prohibitif? (D'après mes calculs, même si vous considérez un élément de S en 1 nano-seconde, cela vous prendrait 2 bonnes années!). N256S
Aryabhata

Réponses:

1

Pourquoi pas un bac et une chaîne?

L'idée est de stocker des entiers positifs représentables par bits dans un tableau A de 2 k entrées représentant des plages de valeurs: l'entrée A [ y ] , y 0 , représente la plage [ 2 m y , 2 m ( y + 1 ) - 1 ] . Pour tout 1 x < 2 n, nous pouvons écrire x = 2 m yn=k+mA2kA[y]y0[2my,2m(y+1)1]1x<2n y a k bits et z a m bits. Essayez de stocker z (pas x !) À l'emplacement y :x=2my+zykzmzxy

  • Lorsque déjà, ne faites rien: x est un doublon.A[y]=zx

  • Lorsque n'est pas initialisé, stockez z à A [ y ] .A[y]zA[y]

  • Sinon, stockez un index dans un tableau séparé utilisé pour enchaîner les (qui sont entrés en collision en y ) dans des listes chaînées. Vous devrez effectuer une recherche linéaire dans la liste dirigée par A [ y ] et, selon ce que la recherche révèle, insérer potentiellement z dans la liste.zyA[y]z

À la fin, est facile à récupérer en parcourant les entrées initialisées de A et - en concaténant simplement deux chaînes de bits - en réassemblant chaque z trouvé à l'emplacement y (soit directement soit dans une chaîne référencée ici) dans l'original valeur x = 2 m y + z .f(S)Azyx=2my+z

Lorsque la distribution est proche de l'uniforme et que dépasse N , il n'y aura pas beaucoup de chaînage (cela peut être évalué de la manière habituelle) et les chaînes auront tendance à être courtes. Lorsque la distribution n'est pas uniforme, l'algorithme fonctionne toujours, mais peut atteindre un timing quadratique. Si c'est une possibilité, utilisez quelque chose de plus efficace que les chaînes (et payez un peu de frais généraux pour le stockage).2kN

Le stockage nécessaire est au maximum de bits pour A et de 2 2 k bits pour les chaînes (en supposant que m k ). C'est exactement l'espace nécessaire pour stocker 2 k valeurs de n bits chacune. Si vous êtes confiant dans l'uniformité, vous pouvez sous-allouer le stockage pour les chaînes. Si la non-uniformité est une possibilité, vous voudrez peut-être augmenter k et préconiser pleinement le stockage en chaîne.2nA22kmk2knk

Une autre façon de penser à cette solution est qu'il s'agit d' une table de hachage avec une fonction de hachage particulièrement agréable (prenez les bits les plus significatifs) et, à cause de cela, nous n'avons besoin que de stocker les m = n - k bits les moins significatifs dans la table.km=nk

Il existe des moyens de superposer le stockage pour les chaînes avec le stockage pour mais cela ne semble pas valoir la peine, car cela n'économiserait pas beaucoup (en supposant que m est beaucoup plus petit que k ) de l'espace et rendrait le code plus difficile à développer, déboguer et maintenir.Amk

Whuber
la source
1
Je pense que l'avant-dernier paragraphe est le principal ici, et devrait probablement être en haut (comme idée). Je ne connais pas le terme "bin and chain" (bien que cela ait du sens après avoir lu le post). Cette idée peut être étendue aux essais .
Raphael
Donc, c'est sur des entrées mal réparties. Je ne vois pas comment cela est efficace. Θ(n2)
einpoklum
@einpoklum Cette réponse décrit explicitement les conditions dans lesquelles la solution est efficace.
whuber