Vous recherchez une implémentation d'ensemble avec une faible empreinte mémoire


9

Je recherche l'implémentation du type de données défini. Autrement dit, nous devons

  • maintenir un sous-ensemble dynamique S (de taille n ) à partir de l'univers de taille u avecuU={0,1,2,3,,u1}u
  • opérations insert(x)(ajouter un élément xà S ) et find(x)(vérifie si l'élément xest membre de S ).

Je me fiche des autres opérations. Pour l'orientation, dans les applications avec lesquelles je travaille, nous avons u1010 .

Je connais des implémentations qui fournissent les deux opérations dans le temps O(1) , donc je m'inquiète principalement de la taille de la structure des données. Je m'attends à des milliards d'entrées mais je veux éviter autant que possible d'échanger.

Je suis prêt à sacrifier l'exécution si nécessaire. Le temps d'exécution amorti de O(logn) est ce que je peux admettre; les temps d'exécution attendus ou les temps d'exécution dans ω(logn) ne sont pas admissibles.

Une idée que j'ai est que si S peut être représenté comme une union de plages [xmin, xmax], alors nous pourrons économiser sur la taille du stockage avec le prix d'une certaine baisse de performance. En outre, certains autres modèles de données sont possibles, comme [0, 2, 4, 6].

Pourriez-vous s'il vous plaît me diriger vers des structures de données qui peuvent faire quelque chose comme ça?



Comment le nombre d'éléments entre-t-il dans l'image? C'est à dire, que se passe-t-il si un élément est inséré et qu'il y a déjà ? nnn
vonbrand

@vonbrand - nest la taille de l'ensemble S. Elle peut augmenter à chaque fois insert, ou elle peut rester la même si l'élément xest déjà dans l'ensemble.
HEKTO

3
Pouvez-vous accepter une faible probabilité de faux positifs? Si c'est le cas, un filtre de floraison pourrait être idéal: en.wikipedia.org/wiki/Bloom_filter
Joe

1
@AlekseyYakovlev, le taux de faux positifs d'un filtre de floraison n'a rien à voir avec la taille de l'univers (uniquement avec le nombre de fonctions de hachage , la taille de la structure de données et le nombre d'éléments ), mais si est vraiment près de (disons pour une petite constante ), vous aurez du mal à faire mieux qu'un simple vecteur de bits je pense, avec seulement bits totaux d'espace. kmnnuu=ncccn
Joe

Réponses:


8

La réponse de Joe est extrêmement bonne et vous donne tous les mots clés importants.

Vous devez savoir que la recherche succincte sur la structure des données en est encore à ses débuts et que de nombreux résultats sont largement théoriques. La plupart des structures de données proposées sont assez complexes à mettre en œuvre, mais la majeure partie de la complexité est due au fait que vous devez maintenir une complexité asymptotique à la fois sur la taille de l'univers et le nombre d'éléments stockés. Si l'un ou l'autre est relativement constant, alors une grande partie de la complexité disparaît.

Si la collection est semi-statique (c'est-à-dire que les insertions sont rares ou au moins à faible volume), alors il vaut certainement la peine d'envisager une structure de données statique facile à implémenter (le sdarray de Sadakane est un bon choix) en conjonction avec une mise à jour cache. Fondamentalement, vous enregistrez des mises à jour dans une structure de données traditionnelle (par exemple, B-tree, trie, table de hachage) et mettez à jour périodiquement en bloc la structure de données "principale". Il s'agit d'une technique très populaire dans la recherche d'informations, car les index inversés présentent de nombreux avantages pour la recherche mais sont difficiles à mettre à jour sur place. Si tel est le cas, faites-le moi savoir dans un commentaire et je modifierai cette réponse pour vous donner quelques conseils.

Si les insertions sont plus fréquentes, je suggère un hachage succinct. L'idée de base est assez simple à expliquer ici, donc je vais le faire.

Donc, le résultat théorique de l'information de base est que si vous stockez éléments d'un univers de éléments, et qu'il n'y a pas d'autres informations (par exemple aucune corrélation entre les éléments), alors vous avez besoin de bits pour le stocker. (Tous les logarithmes sont en base 2, sauf indication contraire.) Vous avez besoin de ce nombre de bits. Il n'y a pas moyen de contourner cela.nlog ( uuJournal(un)+O(1)

Maintenant une terminologie:

  • Si vous avez une structure de données qui peut stocker les données et prendre en charge vos opérations dans bits d'espace, nous appelons cela une structure de données implicite .Journal(un)+O(1)
  • Si vous avez une structure de données qui peut stocker les données et prendre en charge vos opérations dans bits d'espace, nous appelons cela une structure de données compacte . Notez qu'en pratique, cela signifie que les frais généraux relatifs (par rapport au minimum théorique) se situent dans une constante. Il peut s'agir de 5% de frais généraux, de 10% de frais généraux ou de 10 fois les frais généraux.Journal(un)+O(Journal(un))=(1+O(1))Journal(un)
  • Si vous avez une structure de données qui peut stocker les données et prendre en charge vos opérations dans bits d'espace, nous appelons cela une structure de données succincte .Journal(un)+o(Journal(un))=(1+o(1))Journal(un)

La différence entre succinct et compact est la différence entre petit-oh et grand-oh. Ignorant la valeur absolue pendant un moment ...

  • c n 0 n > n 0 g ( n ) < c f ( n )g(n)=O(F(n)) signifie qu'il existe une constante et un nombre tels que pour tout , .cn0n>n0g(n)<cF(n)
  • c n 0 n > n 0 g ( n ) < c f ( n )g(n)=o(F(n)) signifie que pour toutes les constantes il existe un nombre tel que pour tout , .cn0n>n0g(n)<cf(n)

Informellement, big-oh et little-oh sont tous deux "dans un facteur constant", mais avec big-oh, la constante est choisie pour vous (par le concepteur de l'algorithme, le fabricant du CPU, les lois de la physique ou autre), mais avec peu -oh vous choisissez vous-même la constante et elle peut être aussi petite que vous le souhaitez . En d'autres termes, avec des structures de données succinctes, la surcharge relative devient arbitrairement petite à mesure que la taille du problème augmente.

Bien sûr, la taille du problème peut devoir devenir énorme pour réaliser les frais généraux relatifs que vous souhaitez, mais vous ne pouvez pas tout avoir.

OK, avec cela sous nos ceintures, mettons quelques chiffres sur le problème. Supposons que les clés soient des entiers à bits (donc la taille de l'univers est ), et nous voulons stocker de ces entiers. Supposons que nous pouvons par magie organiser une table de hachage idéalisée avec une occupation complète et sans gaspillage, de sorte que nous avons besoin exactement de emplacements de hachage.2 n 2 m 2 mn2n2m2m

Une opération de recherche hacherait la clé à bits, masquerait bits pour trouver les emplacements de hachage, puis vérifierait si la valeur du tableau correspondait à la clé. Jusqu'ici tout va bien.nm

Une telle table de hachage utilise bits. Pouvons-nous faire mieux que cela?n2m

Supposons que la fonction de hachage soit inversible. Ensuite, nous n'avons pas à stocker la clé entière dans chaque emplacement de hachage. L'emplacement de l'emplacement de hachage vous donne bits de la valeur de hachage, donc si vous ne stockez que les bits restants, vous pouvez reconstruire la clé à partir de ces deux informations (l'emplacement de l'emplacement de hachage et la valeur qui y est stockée). Vous n'auriez donc besoin que de bits de stockage.hmnm(nm)2m

Si est petit par rapport à , l'approximation de Stirling et un peu d'arithmétique (la preuve est un exercice!) Révèle que:2m2n

(nm)2m=log(2n2m)+o(log(2n2m))

Cette structure de données est donc succincte.

Cependant, il y a deux captures.

Le premier problème est la construction de «bonnes» fonctions de hachage inversables. Heureusement, c'est beaucoup plus facile qu'il n'y paraît; les cryptographes font des fonctions inversibles tout le temps, seulement ils les appellent "cyphers". Vous pouvez, par exemple, baser une fonction de hachage sur un réseau Feistel, ce qui est un moyen simple de construire des fonctions de hachage inversibles à partir de fonctions de hachage non inversibles.

Le deuxième inconvénient est que les vraies tables de hachage ne sont pas idéales, grâce au paradoxe anniversaire. Vous voudriez donc utiliser un type de table de hachage plus sophistiqué qui vous rapproche de l'occupation complète sans déversement. Le hachage du coucou est parfait pour cela, car il vous permet de vous rapprocher arbitrairement de l'idéal en théorie et assez proche en pratique.

Le hachage de coucou nécessite plusieurs fonctions de hachage et nécessite que les valeurs des emplacements de hachage soient étiquetées avec la fonction de hachage utilisée. Ainsi, si vous utilisez quatre fonctions de hachage, par exemple, vous devez stocker deux bits supplémentaires dans chaque emplacement de hachage. Ceci est toujours succinct à mesure que grandit, donc ce n'est pas un problème dans la pratique, et bat toujours le stockage de clés entières.m

Oh, vous voudrez peut-être aussi regarder les arbres de van Emde Boas.

PLUS DE RÉFLEXIONS

Si est quelque part autour de , alors est approximativement , donc (encore une fois) en supposant qu'il n'y a plus de corrélation entre les valeurs, vous ne pouvez pas en faire mieux qu'un vecteur de bits. Vous remarquerez que la solution de hachage ci-dessus dégénère effectivement dans ce cas (vous finissez par stocker un bit par emplacement de hachage), mais il est moins cher d'utiliser la clé comme adresse plutôt que d'utiliser une fonction de hachage.nu2Journal(un)u

Si est très proche de , toute la littérature succincte sur les structures de données vous conseille d'inverser le sens du dictionnaire. Stockez les valeurs qui ne se produisent pas dans l'ensemble. Cependant, vous devez désormais prendre en charge l'opération de suppression et, pour conserver un comportement succinct, vous devez également pouvoir réduire la structure des données à mesure que davantage d'éléments sont "ajoutés". Agrandir une table de hachage est une opération bien comprise, mais pas la contracter.nu


Bonjour, comme pour le deuxième paragraphe de votre réponse - je m'attends à ce que chaque appel à insertsoit accompagné d'un appel à findavec le même argument. Donc, si le findretour true, alors nous sautons simplement le insert. Ainsi, la fréquence des findappels est plus que la fréquence des insertappels, également lorsqu'elle ndevient proche u, les insertappels deviennent alors très rares.
HEKTO

Mais vous vous attendez à ce que vous rapprochiez de finalement? un
Pseudonyme du

Dans le monde réel, n augmente jusqu'à ce qu'il atteigne u, mais nous ne pouvons pas prédire si cela se produira ou non. La structure de données devrait bien fonctionner pour tout le monden <= u
HEKTO

Droite. Ensuite, il est juste de dire que nous ne connaissons pas une seule structure de données qui soit succincte (dans le sens ci-dessus) et qui réalise cela sur toute la gamme de . Je pense que vous allez vouloir une structure de données clairsemée lorsque , puis passer à une structure dense (par exemple un vecteur de bits) lorsque est autour de , puis une structure de données clairsemée avec un inversé détecter quand est proche de . nun<unu2nu
Pseudonyme du

5

Il semble que vous souhaitiez une structure de données succincte pour le problème d'appartenance dynamique .

Rappelons qu'une structure de données succincte est une structure pour laquelle l'espace requis est "proche" de la limite inférieure de la théorie de l'information, mais contrairement à une structure de données compressée, elle permet toujours des requêtes efficaces.

Le problème d'adhésion est exactement ce que vous décrivez dans votre question:

maintenir un sous-ensemble (de tailleSn ) de l'univers de taille avec les opérations:U={0,1,2,3,,u-1}u

  • find(x)(vérifie si l'élément xest membre de ).S
  • insert(x)(ajouter un élément xà )S
  • delete(x)(supprimer un élément xde )S

Si seule l' findopération est prise en charge, il s'agit du problème d'appartenance statique . Si l'un insertou l' autre deleteest pris en charge, mais pas les deux, il est appelé semi-dynamique et si les trois opérations sont prises en charge, il est alors appelé le problème d'appartenance dynamique .

Sur le plan technique, je pense que vous avez uniquement demandé une structure de données pour le problème d'adhésion semi-dynamique, mais je ne connais aucune structure de données qui profite de cette contrainte et répond également à vos autres exigences. Cependant, j'ai la référence suivante:

Dans le théorème 5.1 de l'article Appartenance au temps constant et à l'espace presque minimum , Brodnik et Munro donnent le résultat suivant:

Il existe une structure de données nécessitant O(B) bits qui prend en charge les recherches en temps constant et les insertions et suppressions en temps amorti attendu constant.

B=Journal(un) est le nombre minimum théorique de bits requis.

L'idée de base est qu'ils divisent récursivement l'univers en gammes de tailles soigneusement choisies, donc cela semble même que les techniques pourraient être dans le sens que vous envisagez.

Cependant, si vous cherchez quelque chose que vous pouvez réellement mettre en œuvre, je ne sais pas si ce sera votre meilleur choix. Je n'ai fait qu'effleurer le papier, et essayer d'expliquer les détails dépasse largement le cadre de cette réponse. Ils paramètrent leur solution, en utilisant différentes stratégies en fonction des tailles relatives de et . Et la version dynamique de la structure de données n'est esquissée que dans le document.un


1
L'abrégé sur papier Brodnik & Munro ne dit rien sur les inserts. Mais leur résultat est ce à quoi nous pouvons nous attendre, non? Si n = u/2, alors l'espace nécessaire est maximal.
HEKTO

@AlekseyYakovlev Ils ne mentionnent pas vraiment le cas dynamique dans l'abstrait, mais le théorème qui traite du cas dynamique est cité dans ma réponse (de la section 5).
Joe
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.