Si chaque ensemble conserve un enregistrement des autres ensembles existants et que vous avez un total de s>0 ensembles, vous pouvez facilement transformer n'importe quelle structure de données pour une collection ( par exemple, les arbres de recherche binaire, etc. ) en une où vous pouvez avoir la récupération de un élément de l'intersection de deux ensembles dans le temps O(logs) .
Chaque ensemble doit avoir un identifiant unique provenant d'un ensemble totalement ordonné. Si vous nommez explicitement vos ensembles S1,S2,… alors l'identifiant pourrait simplement être l'index.
Vous devez implémenter un "registre" des ensembles; une structure de données qui conserve une collection de tous les ensembles que vous avez définis. Le registre doit être implémenté sous la forme d'une structure de données d'arbre de recherche, pour permettre une récupération facile ( par exemple si vous souhaitez supprimer l'ensemble) et une traversée en temps linéaire des ensembles.
Chaque ensemble conserve également un "index" de chacun des autres ensembles - pas une copie d'entre eux, mais plutôt une structure de données qui est indexée par les étiquettes des autres ensembles. Cet index sera utilisé pour maintenir, pour chaque ensemble S k , un arbre de recherche binaire de tous les éléments de S j ∩ S k . (Les deux ensembles S j et S k partagent une copie de cet arbre de recherche.)SjSkSj∩SkSjSk
Initialisation
Initialisation d'un ensemble est constitué de O ( 1 ) opérations pour initialiser l'arbre de ses éléments, O ( s ) les opérations que l' initialisation ( la copie du registre) l'indice pour l'ensemble T et O ( s log s ) opérations que vous parcourez le registre pour ajouter T dans les indices de chacun des autres ensembles S j . Dans l'index de T , nous créons des arbres de recherche représentant T ∩ S j = ∅T=∅O(1)O(s)TO(slogs)TSjTT∩Sj=∅pour les autres ensembles ; nous copions le même pointeur pour l'indice de S j .SjSj
Ajout d'un élément à un ensemble T
L'ajout de à l'ensemble T prend le temps O ( log n T ) comme d'habitude, où n T = | T | . Nous testons également l'appartenance de x dans chacun des autres ensembles S 1 , S 2 , … , ce qui prend du temps O ( log n S 1 + log n S 2 + ⋯ ) ⊆ O ( s log nx∈VTO(lognT)nT=|T|xS1,S2,… où n = | V | est la taille de l'univers (ou du plus grand ensemble S j ) et s est le nombre d'ensembles dans le registre. Pour chaque série S j de telle sorte que x ∈ S j , également insérer x dans l'index de l'ensemble S j ∩ T . Pour chacun de ces ensembles S j , il faut O ( log s + log n T ) pour rechercher S j
O(lognS1+lognS2+⋯)⊆O(slogn),
n=|V|SjsSjx∈SjxSj∩TSjO(logs+lognT)Sjdans l'indice de
et insérer
x dans
S j ∩ T ; sur tous les ensembles
S 1 , S 2 , … cela prend du temps
O ( s log s + s log n T ) . Si nous supposons que le nombre d'ensembles
S j est bien inférieur à la taille de l'univers
V (c'est-à-dire si nous supposons
s ≪ n ), le temps total pour l'insertion d'élément est alors
O ( s log n )TxSj∩TS1,S2,…O(slogs+slognT)SjVs≪nO(slogn).
Si vous ne permettez pas de doublons dans les jeux, nous pouvons gagner du temps dans le cas où déjà en renonçant à l'épreuve d'adhésion et les insertions pour les autres ensembles T . "Insertion" dans le cas où x est déjà présent ne prend alors que le temps O ( log n T ) .x∈STxO(lognT)
Test d'intersection
L'indice de chaque ensemble est maintenu avec précision afin de permettre une évaluation rapide de l' intersection ou non de deux ensembles et S k . Pour un ensemble S j , simplement en vérifiant son index pour l'ensemble S k , nous pouvons non seulement déterminer dans le temps O ( log s ) si S j intersecte S k , mais nous pouvons également récupérer un arbre binaire contenant l'ensemble entier S j ∩ S k .SjSkSjSkO(logs)SjSkSj∩Sk
Suppression d'élément
Pour supprimer un élément d'un ensemble T , nous le supprimons non seulement de l'arbre de recherche de T lui-même, mais de chacune des intersections S j ∩ T pour les ensembles S j dans son index. Cela prend le temps O ( s log n T ) , où n T = | T | .xTTSj∩TSjO(slognT)nT=|T|
Définir la suppression
En raison de la surcharge de recherche dans le registre, si vous disposez de plusieurs ensembles, il peut être souhaitable de supprimer les ensembles une fois qu'ils ne sont plus nécessaires. En parcourant l'ensemble du registre, nous pouvons supprimer de l'index de tous les autres ensembles S j dans le temps O ( s n T ) , dominé par le coût de la suppression de l'arbre de recherche représentant S j ∩ T pour chacun des autres ensembles S j , où n T = | T | .SSjO(snT)Sj∩TSjnT=|T|
Remarques
Si vous vous attendez à n'implémenter qu'un nombre constant d'ensembles, les temps d'exécution ci-dessus se réduisent à:
initialisation: O(1)
insertion d'élément: O(logn)
test d'intersection (et récupération de l'intersection): O(1)
suppression d'élément: O(lognT)
supprimer la définition: O(nS)
où est la taille du plus grand ensemble du registre et n T = | T | pour l'ensemble T sur lequel vous opérez.nnT=|T|T
Si vous vous attendez à avoir des ensembles , où V est votre univers, vous pouvez avoir besoin d'une structure de données différente si vous voulez que ces opérations fonctionnent en temps sub-linéaire. Cependant, si vous avez des paires d'ensembles dont vous savez que vous ne testerez jamais l'intersection, vous pourrez peut-être réduire la taille de l'index pour les ensembles (en n'incluant aucun ensemble dont vous allez tester l'intersection) ou utiliser plusieurs registres ( un pour chaque collection d'ensembles dont vous pourriez tester l'intersection). En fait, un registre n'est utile que si vous voulez un contrôle centralisé pour vous assurer que chaque paire d'ensembles a un enregistrement les uns des autres dans l'index: il peut être pratique dans certains cas, lors de l'initialisation d'un ensemble S , d'enregistrer simplementO(|V|)VSad hoc chaque nouvel ensemble dans les indices des autres ensembles dont l'intersection avec S vous intéresse.TS