web-dev-qa-db-fra.com

C - Comment implémenter la structure de données Set?

Existe-t-il un moyen délicat d'implémenter une structure de données définie (une collection de valeurs uniques) en C? Tous les éléments d'un ensemble seront du même type et il y a une énorme mémoire RAM.

Comme je le sais, pour les entiers, cela peut être fait très rapidement'N'easy en utilisant des tableaux indexés sur les valeurs. Mais j'aimerais avoir un type de données Set très général. Et ce serait bien si un ensemble pouvait s'inclure.

43
psihodelia

Il existe plusieurs façons de mettre en œuvre la fonctionnalité de définition (et de mappage), par exemple:

  • approche arborescente (traversée ordonnée)
  • approche basée sur le hachage (traversée non ordonnée)

Puisque vous avez mentionné des tableaux indexés sur les valeurs , essayons l'approche basée sur le hachage qui se construit naturellement au-dessus de la valeur -indexed array technique .

Méfiez-vous des avantages et inconvénients des approches basées sur le hachage vs. basées sur les arbres.

Vous pouvez concevoir un ensemble de hachage (un cas particulier de tables de hachage ) de pointeurs vers hachable POD s, avec chaînage , représenté en interne comme un tableau de taille fixe de compartiments de hashables , où:

  • tous hashables dans un compartiment ont la même valeur de hachage
  • un bucket peut être implémenté comme tableau dynamique ou liste liée de hashables
  • a hashable la valeur de hachage est utilisée pour indexer dans le tableau de compartiments (tableau indexé sur la valeur de hachage)
  • un ou plusieurs des - hashables contenus dans le hash-set pourraient être (un pointeur vers) un autre hash-set, ou même vers le hash-set lui-même (ie l'auto-inclusion est possible )

Avec de grandes quantités de mémoire à votre disposition, vous pouvez dimensionner votre tableau de compartiments généreusement et, en combinaison avec une bonne méthode de hachage, réduire considérablement la probabilité de collision , en obtenant des performances pratiquement constantes.

Vous devrez implémenter:

  • fonction de hachage pour le type haché
  • une fonction d'égalité pour le type utilisé pour tester si deux hashables sont égaux ou non
  • la fonctionnalité de jeu de hachage contains/insert/remove.

Vous pouvez également utiliser adressage ouvert comme alternative à la maintenance et à la gestion des compartiments.

43
vladr

Les ensembles sont généralement implémentés comme une variété de arbre binaire . Arbres noirs rouges ont de bonnes performances dans le pire des cas.

Ceux-ci peuvent également être utilisés pour créer un map pour permettre les recherches de clé/valeur.

Cette approche nécessite une sorte d'ordre sur les éléments de l'ensemble et les valeurs clés dans une carte.

Je ne sais pas comment vous géreriez un ensemble qui pourrait éventuellement se contenir à l'aide d'arbres binaires si vous limitez l'appartenance à des types bien définis en C ... la comparaison entre de telles constructions pourrait être problématique. Vous pouvez cependant le faire assez facilement en C++.

5
andand

Si le nombre maximal d'éléments dans l'ensemble (la cardinalité du type de données sous-jacent) est suffisamment petit, vous voudrez peut-être envisager d'utiliser un ancien tableau de bits (ou tout ce que vous les appelez dans votre langue préférée).

Ensuite, vous avez une simple vérification d'appartenance à l'ensemble: le bit n est 1 si l'élément n est dans l'ensemble. Vous pouvez même compter les membres "ordinaires" à partir de 1 et ne rendre le bit 0 égal à 1 que si l'ensemble se contient.

Cette approche nécessitera probablement une sorte d'autre structure de données (ou fonction) pour traduire du type de données membre à la position dans le tableau de bits (et inversement), mais elle effectue des opérations d'ensemble de base (union, intersection, test d'appartenance, différence, insertion, retrait, compression) très très facile. Et il ne convient que pour des ensembles relativement petits, vous ne voudriez pas l'utiliser pour des ensembles d'entiers 32 bits, je ne suppose pas.

3

La façon d'obtenir la généricité en C est par void *, Donc vous allez quand même utiliser des pointeurs, et les pointeurs vers différents objets sont uniques. Cela signifie que vous avez besoin d'une carte de hachage ou d'un arbre binaire contenant des pointeurs, et cela fonctionnera pour tous les objets de données.

L'inconvénient est que vous ne pouvez pas entrer les valeurs r indépendamment. Vous ne pouvez pas avoir un ensemble contenant la valeur 5; vous devez attribuer 5 à une variable, ce qui signifie qu'elle ne correspondra pas à un 5. Vous pouvez la saisir comme (void *) 5, et pour des raisons pratiques, cela fonctionnera probablement avec de petits entiers, mais si vos entiers peuvent entrer dans des tailles assez grandes pour concurrencer les pointeurs, cela a une très faible probabilité d'échec.

Cela ne fonctionne pas non plus avec les valeurs de chaîne. Étant donné char a[] = "Hello, World!"; char b[] = "Hello, World!";, Un ensemble de pointeurs trouverait a et b différents. Vous voudrez probablement hacher les valeurs, mais si vous êtes préoccupé par les collisions de hachage, vous devez enregistrer la chaîne dans l'ensemble et faire une strncmp() pour comparer la chaîne stockée avec la chaîne de sondage.

(Il y a des problèmes similaires avec les nombres à virgule flottante, mais essayer de représenter des nombres à virgule flottante dans des ensembles est une mauvaise idée en premier lieu.)

Par conséquent, vous souhaitez probablement une valeur balisée, une balise pour tout type d'objet, une pour la valeur entière et une pour la valeur de chaîne, et peut-être plus pour différentes sortes de valeurs. C'est compliqué, mais faisable.

2
David Thornley