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(log( un) )=(1+O(1))log( 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(log( un) )=(1+o(1))log( 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 ) < c ⋅ f( 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)<c⋅f(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.hmn−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:2m2n
(n−m)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