Je recherche l'implémentation du type de données défini. Autrement dit, nous devons
- maintenir un sous-ensemble dynamique (de taille ) à partir de l'univers de taille u avecu
- opérations
insert(x)
(ajouter un élémentx
à ) etfind(x)
(vérifie si l'élémentx
est membre de ).
Je me fiche des autres opérations. Pour l'orientation, dans les applications avec lesquelles je travaille, nous avons .
Je connais des implémentations qui fournissent les deux opérations dans le temps , 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 est ce que je peux admettre; les temps d'exécution attendus ou les temps d'exécution dans ne sont pas admissibles.
Une idée que j'ai est que si 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?
n
est la taille de l'ensemble S. Elle peut augmenter à chaque foisinsert
, ou elle peut rester la même si l'élémentx
est déjà dans l'ensemble.Réponses:
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.n log ( uu Journal( un) +O(1)
Maintenant une terminologie:
La différence entre succinct et compact est la différence entre petit-oh et grand-oh. Ignorant la valeur absolue pendant un moment ...
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 mn 2n 2m 2m
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.n m
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.h m n−m (n−m)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:2m 2n
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.n u2 Journal( 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.n u
la source
insert
soit accompagné d'un appel àfind
avec le même argument. Donc, si lefind
retourtrue
, alors nous sautons simplement leinsert
. Ainsi, la fréquence desfind
appels est plus que la fréquence desinsert
appels, également lorsqu'ellen
devient procheu
, lesinsert
appels deviennent alors très rares.n <= u
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:
find(x)
(vérifie si l'élémentx
est membre de ).insert(x)
(ajouter un élémentx
à )delete(x)
(supprimer un élémentx
de )Si seule l'
find
opération est prise en charge, il s'agit du problème d'appartenance statique . Si l'uninsert
ou l' autredelete
est 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:
oùB = ⌈ log( 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.u n
la source
n = u/2
, alors l'espace nécessaire est maximal.