3 votes

Comment implémenter une structure de données union-find (ensemble disjoint) dans Coq ?

Je suis assez nouveau dans Coq, mais pour mon projet, je dois utiliser une structure de données union-find dans Coq. Existe-t-il des implémentations de la structure de données union-find (ensemble disjoint) dans Coq ?

Sinon, quelqu'un peut-il fournir une implémentation ou quelques idées ? Il n'est pas nécessaire qu'elle soit très efficace. (pas besoin de faire de la compression de chemin ou toutes les optimisations fantaisistes) J'ai juste besoin d'une structure de données qui peut contenir un type de données arbitraire (ou nat si c'est trop difficile) et performer : syndicat y trouver .

Merci d'avance

0voto

Maëlan Points 500

Si vous n'avez besoin que d'un modèle mathématique, sans vous préoccuper des performances réelles, j'opterais pour le plus simple : une carte fonctionnelle (fonction partielle finie) dans laquelle chaque élément est éventuellement lié à un autre élément avec lequel il a été fusionné.

  • Si un élément n'est lié à rien, alors son représentant canonique est lui-même.
  • Si un élément est lié à un autre élément, alors son représentant canonique est le représentant canonique de cet autre élément.

Note : dans le reste de cette réponse, comme c'est la norme avec union-find, je supposerai que les éléments sont simplement des nombres naturels. Si vous voulez un autre type d'éléments, il suffit d'avoir une autre carte qui lie tous les éléments à des nombres uniques.

Ensuite, vous devez définir une fonction find : UnionFind nat nat qui renvoie le représentant canonique d'un élément donné, en suivant les liens aussi longtemps que possible. Remarquez que cette fonction utiliserait la récursion, dont l'argument de terminaison n'est pas trivial. Pour y parvenir, je pense que le moyen le plus simple est de maintenir l'invariant selon lequel un nombre ne peut être lié qu'à un nombre inférieur (c'est-à-dire si i liens vers j entonces i > j ). Ensuite, la récursion se termine car, en suivant les liens, l'élément courant est un entier naturel décroissant.

Définition de la fonction union : UnionFind nat nat UnionFind est plus facile : union m i j renvoie simplement une carte mise à jour avec max i' j' la liaison avec min i' j' , donde i' = find m i y j' = find m j .

[Note sur les performances : le maintien de l'invariant signifie que vous ne pouvez pas choisir de manière adéquate laquelle des deux partitions doit être fusionnée dans l'autre, en fonction de leur rang ; cependant, vous pouvez toujours mettre en œuvre la compression de chemin si vous le souhaitez].

Quant à la structure de données à utiliser pour la carte, il en existe plusieurs. Le site bibliothèque standard (regardez sous le titre FSets ) possède plusieurs implémentations (FMapList, FMapPositive, etc.) satisfaisant l'interface FMapInterface . La librairie stdpp a gmap .

Encore une fois, si les performances ne sont pas un problème, choisissez simplement le codage le plus simple ou, plus important encore, celui qui rend vos preuves les plus simples. Je pense simplement à une liste de nombres naturels. Les positions de la liste sont les éléments dans l'ordre inverse . Les valeurs de la liste sont offsets c'est-à-dire le nombre de positions à sauter en avant pour atteindre la cible du lien.

  • Pour un élément i la liaison avec j ( i > j ), le décalage est i j .
  • Pour un représentant canonique, le décalage est de zéro.

Avec mes meilleurs talents de pseudo-art ASCII, voici une carte où les liens sont { 62, 42, 30, 21 } et les représentants canoniques sont { 5, 1, 0 } :

  6   5   4   3   2   1   0   element

               /
[ 4 ; 0 ; 2 ; 3 ; 1 ; 0 ; 0 ] map
   \       \____ \_
    \___________/

La motivation est que l'invariant discuté ci-dessus est alors mis en application structurellement . On peut donc espérer que find pourrait en fait être défini par induction structurelle (sur la structure de la liste), et avoir une terminaison gratuite.


Un article connexe est publié : Sylvain Conchon et Jean-Christophe Filliâtre. A Persistent Union-Find Data Structure. Dans Atelier ACM SIGPLAN sur les ML .

Il décrit l'implémentation d'une structure de données union-find efficace en ML, qui est persistante du point de vue de l'utilisateur, mais qui utilise la mutation en interne. Ce qui peut être plus intéressant pour vous, c'est qu'ils prouvent que c'est correct dans Coq, ce qui implique qu'ils ont un modèle Coq pour union-find. Cependant, ce modèle reflète le magasin de mémoire pour le programme impératif dont ils cherchent à prouver la correction. Je ne suis pas sûr qu'il soit applicable à votre problème.

0voto

Philip Zucker Points 1

Maëlan a une bonne réponse, mais pour une structure de données d'ensembles disjoints encore plus simple et plus inefficace, vous pouvez simplement utiliser des fonctions pour nat pour les représenter. Cela permet d'éviter tout problème de terminaison. En substance, les préimages de toute fonction totale forment des ensembles disjoints sur le domaine. Une autre façon de voir cela est de représenter tout ensemble disjoint G comme l'application curry find_root G : nat -> nat depuis find_root est l'interface essentielle que fournissent les ensembles disjoints. Ceci est également analogue à l'utilisation de fonctions pour représenter les cartes dans Coq comme dans Software Foundations. https://softwarefoundations.cis.upenn.edu/lf-current/Maps.html

Require Import Arith.
Search eq_nat_decide.
(* disjoint set *)
Definition ds := nat -> nat.
Definition init_ds : ds := fun x => x.
Definition find_root (g : ds) x := g x.
Definition in_same_set (g : ds) x y := 
    eq_nat_decide (g x) (g y).
Definition union (g : ds) x y : ds := 
    fun z =>
    if in_same_set g x z
    then find_root g y
    else find_root g z.

Vous pouvez également le rendre générique sur le type détenu dans l'ensemble disjoint comme suit

Definition ds (a : Type) := a -> nat.
Definition find_root {a} (g : ds a) x := g x.
Definition in_same_set {a} (g : ds a) x y := 
    eq_nat_decide (g x) (g y).
Definition union {a} (g : ds a) x y : ds a := 
    fun z =>
    if in_same_set g x z
    then find_root g y
    else find_root g z.

Pour initialiser l'ensemble disjoint pour un a vous avez besoin d'une instance Enum pour votre type. a fondamentalement.

Definition init_bool_ds : ds bool := fun x => if x then 0 else 1.

Vous pourriez vouloir échanger eq_nat_decide para eqb ou un autre élément à peu près équivalent, en fonction de votre style d'épreuve et de vos besoins.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X