Mode : Graphes pondérés
Mode : Automates rationnels
Sommets

Répartition :



Délimitez un rectangle sur le canevas pour générer les sommets.

Arêtes

Admissibles :



Sélectionner les sommets :

Sélectionner les arêtes :

Désélectionner les sommets :

Désélectionner les arêtes :

Sélectionner :

Désélectionner :

Zoom :

Délimitez un rectangle sur le canevas pour zoomer/dézoomer sur cette zone.


Création de graphe

Sur la moitié en haut ou à gauche de la page se trouve le canevas sur lequel vous pouvez dessiner un graphe.

Utilisez le clic gauche de la souris pour modifier le graphe et sélectionner ses éléments :

  • Créer (1)
    • Cliquez dans le vide pour ajouter un sommet.
    • Cliquez dans le vide ou sur un sommet et glissez pour ajouter une arête.
    • Cliquez sur une arête et glissez vers un sommet source et un sommet destination pour changer les extrémités de l’arête.
    • Cliquez avec Maj enfoncée pour créer une arête depuis le sommet sélectionné vers le sommet pointé.
  • Déplacer (2)
    • Cliquez sur un sommet et glissez pour le déplacer.
    • Cliquez sur une arête et glissez pour la courber (courbe de Bézier quadratique).
    • Cliquez dans le vide et glissez pour déplacer tout le graphe.
    • Cliquez sur un élément sélectionné et glissez pour déplacer tous les sommets sélectionnés.
    • Gardez Maj enfoncée pour sélectionner plusieurs éléments.
  • Sélectionner (3)
    • Cliquez pour désélectionner tous les éléments et sélectionner l’éventuel élément pointé.
    • Cliquez et glissez pour sélectionner un ensemble d’éléments entièrement englobés.
    • Gardez Maj enfoncée pour ne pas désélectionner les éléments déjà sélectionnés.
    • Cliquez sur un élément avec Maj enfoncée pour le désélectionner.
  • Zoomer (4)
    • Cliquez pour zoomer ou dézoomer à 110 %, selon le choix fait dans l’onglet.
    • Délimitez un rectangle pour que le canevas ne contienne que son contenu (zoom avant), ou au contraire, que le contenu du canevas s’y concentre (zoom arrière).
    • Notez que les actions de zoom peuvent être annulées et restaurées.
  • Modifier les valeurs (double-clic)
    • Double-cliquez sur un sommet pour modifier son numéro (les numéros des autres sommets seront décalés de 1).
    • Double-cliquez sur une arête pour modifier son poids.
    • Double-cliquez sur une arête avec Maj enfoncée pour supprimer sa courbure (aucun autre élément ne doit être sélectionné).

Vous pouvez utiliser les touches numériques du clavier pour changer facilement de mode.

Certaines touches du clavier ont un effet sur la sélection :

  • Suppr (ou Retour arrière) : Effacer les éléments sélectionnés.
  • Début et Fin : Placer les arêtes sélectionnés en début/fin de liste (pour les algorithmes).
  • Échap : Vider la sélection, annuler l’action en cours avec la souris.

Sur écran tactile, faites un appui long sur un élément pour modifier sa valeur. En mode Sélectionner, l’appui long permet de supprimer les éléments sélectionnés.

Jusqu’à 50 actions peuvent être annulées et restaurées.

Raccourcis clavier

  • Ctrl+A : Tout sélectionner.
  • Ctrl+Z : Annuler la dernière modification faite au graphe.
  • Ctrl+Y ou Ctrl+Maj+Z : Restaurer la modification annulée.
  • Ctrl+O : Ouvrir un graphe au format SVG.
  • Ctrl+S : Enregistrer le graphe au format SVG.
  • Maj+F1-F4 : Ouvrir/fermer l’onglet.
  • Début, Fin, Gauche et Droite : Naviguer parmi les états de l’algorithme en cours de visualisation.

Les raccourcis clavier ne fonctionnent pas si le curseur se trouve dans un élément de formulaire (cliquez alors sur une zone vide ou appuyez sur Échap pour les réactiver).

Formats

Pour charger un graphe enregistré, vous pouvez utiliser le glisser-déposer : glissez votre fichier ou son contenu (code SVG complet) sur la page. Il en est de même pour les algorithmes et leurs résultats.

Il y a une petite prise en charge des graphes au format SVG générés par Graphviz. Le code SVG doit contenir le mot « graphviz » (généralement placé dans un commentaire). Les seules données utilisées sont : le titre des sommets (qui doit être numérique), le titre des arêtes, le type de la première arête (orientée ou non) et les coordonnées du centre de chaque sommet. En général, le graphe chargé débordera en haut, il faut utiliser l’outil « Déplacer » pour le rendre visible.

Une liste d’arêtes au format simple de GenGraph peut être chargée dans l’onglet Graphe > Représentations. Le nombre de sommets n’est pas pris en compte : il faut les générer au préalable. Les arêtes auront toutes une longueur de 1 et leur orientation n’est pas prise en compte ; cette dernière doit être configurée dans l’onglet Graphe > Paramètres. Seule la lecture de chemins est prise en charge.

Les graphes enregistrés peuvent être visionnés en dehors de l’application. Cependant, si celle-ci n’est pas exécutée via un serveur web, les règles de style ne sont pas incorporées aux documents enregistrés, et le rendu n’est alors pas bon.

À propos

Visionneuse web d’algorithmes de graphes, version 1.1 (janvier 2024).

Application par Sylvain Chiron, sous licence GNU AGPL v3. Conçue à partir du cours d’algorithmique des graphes de František Kardoš à l’Université de Bordeaux.

Dépôt : https://gitlab.com/Frigory33/graph-algos-viewer-web

Dépôt du projet : https://gitlab.com/Frigory33/graph-algos-viewer-web

Représentations numériques du graphe :

  • Possibilité de vérifier la cohérence et le formatage d’une représentation (lorsqu’elle est saisie manuellement) ?
  • Accepter les poids de zéro, utiliser autre chose qu’un nombre dans les matrices quand il n’y a pas d’arête ?

Résultats des algorithmes :

  • Meilleurs choix de couleurs, meilleurs contrastes dans les résultats des algorithmes ?
  • Affichage du nombre d’opérations effectuées à chaque étape des algorithmes ?
  • Utilisation de couleurs dans les tableaux ? Prise en charge d’options de style dans la fonction formatArraysForVertices ?
  • Fonctions utilitaires pour afficher facilement les représentations du graphe (listes d’adjacence, etc.) avec des valeurs/couleurs à chaque état d’exécution des algorithmes ?
  • Fonction utilitaire pour afficher facilement une matrice (comme avec Floyd-Warshall) ?
  • Exercices à trous ?
  • Organisation plus sophistiquée (arborescente) des étapes ?

Fonctionnalités des algorithmes ou pour les algorithmes :

  • Stocker directement les numéros des liens au lieu d’utiliser la fonction parentsToEdges dans le code des algorithmes, pour prendre en charge les graphes non simples ?
  • Utiliser Infinity plutôt que null pour initialiser les distances ?
  • Permettre aux algorithmes de modifier le graphe ? Proposer des actions pour modifier le graphe en fonction des résultats des algorithmes ?
  • Permettre d’exécuter les codes fournis sur le serveur (sauf pour JavaScript) ?

Fichiers locaux

Format : SVG.

Données du navigateur

Fichiers du serveur

Code SVG

Grille

Pour aligner facilement les sommets, vous pouvez configurer une grille. Ses données ne sont pas enregistrées dans le graphe.

Taille d’une cellule (largeur × hauteur, en pixels) : ×

Trier les arêtes

Dans les listes d’arêtes, les listes d’adjacence et la matrice d’incidence reçues par les algorithmes, les arêtes ont le même ordonnancement que dans l’arbre XML/SVG du graphe. Vous pouvez facilement altérer cet ordre avec les touches Début et Fin du clavier, ou en utilisant l’une des fonctions de tri proposées ci-dessous.

Choisissez un ordre de tri des arêtes :





Liste d’arêtes : — Format simple de GenGraph :

Listes de successeurs : — Listes de prédécesseurs :

Matrice d’adjacence :

Matrice d’incidence :

Source

Complexité
Type d’opérationNombre d’invocationsÉquivalent à

À propos de l’algorithme

Le parcours en largeur et le parcours en profondeur sont les deux techniques élémentaires pour parcourir un graphe tout en construisant des chemins. Elles permettent toutes les deux d’explorer un graphe, avec un ordre de parcours différent et en allant d’un sommet à l’autre en passant par une arête (tant que cela est possible), ce qui conduit l’exploration à construire une forêt couvrante du graphe. Toutefois, l’implémentation de parcours en largeur fournie ici n’explore que les sommets accessibles depuis les sommets initiaux.

L’implémentation de ces algorithmes est très similaire ; la seule différence repose sur la structure de données utilisée pour déterminer dans quel ordre explorer les sommets : le parcours en largeur utilise une file, et le parcours en profondeur utilise une pile. Le parcours en profondeur est le plus souvent implémenté avec une fonction récursive plutôt qu’avec une pile explicite. Ainsi, le parcours en largeur explore les sommets de proche en proche, tandis que le parcours en profondeur avance tant qu’il peut et ne revient en arrière que quand il n’y a plus de chemin disponible.

Le parcours en largeur explore donc les sommets dans l’ordre de leur distance par rapport aux sommets de départ. Dans le cas où le graphe est un arbre, le parcours en largeur l’explorera ligne par ligne. Le parcours en profondeur, lui, peut associer à chaque sommet des dates de début et de fin de visite, ce qui lui permet notamment de trier les arcs en quatre catégories : arcs de liaison, arcs de retour, arcs d’avant et arcs transverses. Le parcours en profondeur permet aussi d’obtenir un tri topologique du graphe : si le graphe est orienté et sans circuit, une fois les sommets triés par ordre de fin de visite, tous les arcs iront dans le même sens, de sorte qu’aucun sommet n’a un descendant situé avant lui, ce qui est intéressant lorsque le graphe est un graphe de dépendances.

Complexité : O(n + m).

Les algorithmes de Kosaraju et de Tarjan permettent de délimiter les composantes fortement connexes du graphe, c’est-à-dire les plus grandes régions du graphe dans lesquelles il est possible d’accéder à chaque sommet depuis tout autre sommet de la région.

L’algorithme de Kosaraju fait simplement deux parcours en profondeur du graphe, le deuxième étant fait avec les sommets triés par ordre de fin de visite et en parcourant les arcs dans le sens inverse. L’algorithme de Tarjan utilise une autre technique, qui consiste à calculer, pour chaque sommet, la date de début de visite du sommet accessible le plus proche du début de la pile du parcours au profondeur, qui se rapproche à peu près de la date de découverte de sa composante fortement connexe.

Complexité : O(n + m).

Les algorithmes de Kruskal et de Prim permettent de calculer l’arbre couvrant de poids minimum de chaque composante connexe du graphe. L’algorithme de Kruskal parcourt les arêtes dans l’ordre croissant de leur poids et décide ou non s’il faut les prendre. L’algorithme de Prim, semblablement à l’algorithme de Dijkstra, part d’un sommet et avance d’arête en arête en utilisant une file à priorité. Avec une petite adaptation, l’algorithme de Kruskal permettrait de trouver tous les arbres couvrants de poids minimum (il peut y en avoir plusieurs si certaines arêtes ont le même poids) des composantes connexes du graphe. L’algorithme de Kruskal utilise une structure de données de type unir-trouver (union-find) pour déterminer si deux sommets sont déjà connectés par les arêtes précédemment sélectionnées.

Application : déterminer la manière la plus économique de relier tous les éléments d’un réseau.

Complexité : O(n + m log n).

L’algorithme de Dijkstra est semblable à l’algorithme de parcours en largeur, à ceci près qu’il prend en compte les poids des arcs et utilise une file à priorité pour déterminer de façon gloutonne le prochain sommet à visiter. Il détermine ainsi, depuis un sommet, comment aller le plus vite vers chacun des autres sommets ; ou le contraire : comment aller le plus vite vers un sommet depuis chacun des autres sommets (si on inverse le sens des arcs). L’algorithme de Dijkstra peut être trompé par l’existence de poids négatif, et ainsi n’est prévu que pour les graphes dont tous les poids sont positifs ou nuls.

Si l’objectif est d’aller d’un sommet à un autre le plus rapidement possible et que les poids des arcs se rapprochent fortement de la distance réelle entre les sommets, l’algorithme de Dijkstra peut être inutilement lent. L’algorithme A* est une généralisation de l’algorithme de Dijkstra qui prend en compte les données géométriques afin d’aller plus directement vers la destination.

Complexité : O(m + n log n).

L’algorithme de Bellman permet d’explorer un graphe orienté sans circuit (tel qu’un graphe de dépendances), depuis les sommets sans prédécesseur jusqu’aux sommets sans successeur. Il calcule pour cela, pour chaque sommet, le nombre de ses prédécesseurs non encore visités. Lorsque ce nombre tombe à zéro, le sommet est ajouté dans la file pour être exploré prochainement. L’algorithme de Bellman permet de déterminer la distance entre chaque sommet et le plus proche sommet sans prédécesseur, ainsi que le rang de chaque sommet, qui est tel que tous les sommets de même rang auraient pu être visités dans n’importe quel ordre (le rang des sommets sans prédécesseur est de zéro, et le rang des autres sommets est un de plus que le plus élevé des rangs de leurs prédécesseurs).

Complexité : O(n + m).

L’algorithme de Bellman-Ford permet de calculer les plus courts chemins depuis un sommet vers tous les autres et ce, contrairement à l’algorithme de Dijkstra, même s’il y a des poids négatifs. La technique est de vérifier plusieurs fois si emprunter une arête permet d’aller plus vite vers le sommet au bout de l’arc. L’algorithme permet de détecter les circuits absorbants du graphe, c’est-à-dire les circuits dont les sommets sont tous à distance -∞.

Complexité : O(n m).

L’algorithme de Floyd-Warshall permet de calculer tous les plus courts chemins entre tous les couples de sommets du graphe, avec une technique algorithmique simple : pour chaque sommet, et pour tout couple de sommets, déterminer si emprunter le premier sommet permet d’aller plus vite d’un sommet du couple à l’autre, et si c’est le cas, profiter du raccourci. L’algorithme permet de détecter les circuits absorbants.

Complexité : O(n³ + m).

L’algorithme d’Edmonds-Karp permet d’obtenir un flot maximum pour un réseau de flot. Un réseau de flot est un graphe dans lequel les arcs sont bornés par des capacités, qui déterminent combien d’éléments peuvent traverser l’arc en même temps. La technique consiste à déterminer un à un des chemins améliorants à l’aide de parcours en largeur successifs, qui indiquent parfois qu’il faut moins utiliser une arête. L’algorithme d’Edmonds-Karp est une précision de l’algorithme de Ford-Fulkerson, qui est identique à ceci près qu’il ne précise pas comment trouver les chemins améliorants.

Application : déterminer comment utiliser un réseau le plus efficacement possible.

Complexité : O(n + m²).

Entrée : — Sortie : — Fichier :

Exécution :

Télécharger :

Documentation

Pour appliquer un algorithme sur un graphe de l’application, téléchargez la bibliothèque et le squelette correspondant à votre langage, puis complétez le squelette. Il s’agit d’écrire une fonction recevant un graphe en paramètre.

Quel que soit votre langage, comme cela est montré dans les squelettes, l’exécution de votre fonction doit reposer sur un appel runAlgorithm(votreFonction).

Des exemples plus complets, implémentant l’algorithme de Dijkstra, sont disponibles pour mieux vous montrer comment utiliser la bibliothèque de chaque langage.

Les sections ci-dessous décrivent précisément comment utiliser les bibliothèques.

Entrée

Pour que votre programme reconnaisse le graphe, vous devez lui fournir sa description en entrée. Commencez par sélectionner les éléments que votre algorithme doit privilégier le cas échéant. Ensuite, cliquez sur l’un des boutons ci-dessus (« Minimale » ou « Avec positions ») pour obtenir des données d’entrée, et récupérez le texte produit (cliquez dessus puis utilisez les raccourcis clavier Ctrl+A et Ctrl+C pour copier le texte) ou cliquez sur le bouton pour l’enregistrer dans un fichier. Fournissez le texte d’entrée à votre programme, soit en redirigeant son entrée vers le fichier enregistré, soit en collant directement le texte dans un terminal exécutant votre programme.

Le graphe fourni en paramètre à votre fonction est un objet ou une structure ayant les champs suivants :

  • nbVertices : le nombre de sommets du graphe ;
  • nbEdges : le nombre d’arêtes du graphe ;
  • isUndirected : vrai si le graphe est non orienté, faux sinon ;
  • selectedVertices : la liste des numéros des sommets sélectionnés ;
  • selectedEdges : la liste des numéros des arêtes sélectionnées ;
  • edges : la liste (de taille nbEdges) des arêtes du graphe, chaque élément ayant les champs suivants :
    • src : le numéro du sommet source,
    • dest : le numéro du sommet destination,
    • weight : le poids de l’arête,
    • num : le numéro de l’arête (identique à sa position dans la liste) ;
  • succLists : la liste (de taille nbVertices) des listes de successeurs, indexées par le numéro du sommet source, chaque élément ayant les champs suivants :
    • vertex : le numéro du sommet à l’autre extrémité,
    • weight : le poids de l’arête,
    • num : le numéro de l’arête ;
  • predLists : la liste (de taille nbVertices) des listes de prédécesseurs, au même format que succLists, et contenant les mêmes éléments si le graphe est non orienté ;
  • adjMatrix : la matrice d’adjacence du graphe (adjMatrix[vertex1][vertex2] est le poids de l’arête allant de vertex1 à vertex2, ou 0 s’il n’y en a pas).

Certains champs sont facultatifs. Pour les obtenir, vous devez remplacer le dernier nombre de la toute première ligne de l’entrée par la somme (ou l’union des bits) des nombres qui sont indiqués entre crochets à côté du nom des champs que vous voulez. Les champs facultatifs sont :

  • verticesPos [1] (vous devez prendre l’entrée « Avec positions ») : la liste (de taille nbVertices) des positions des sommets, représentées par deux champs x et y ;
  • incMatrix [2] : la matrice d’incidence du graphe (incMatrix[vertex][edge] est le poids de edge si c’est une arête sortante de vertex, l’opposé de son poids si c’est une arête entrante de vertex, 0 sinon).

Sortie

Votre fonction doit retourner un tableau de tableaux de dictionnaires (les dictionnaires étant des tableaux dont les indices sont ici des chaines de caractères), qui seront convertis en JSON par la bibliothèque et envoyés sur la sortie. Une fois que vous avez cette sortie, collez-la dans la zone de texte ci-dessus (en l’ayant préalablement vidée de son contenu : utilisez le raccourci clavier Ctrl+A pour sélectionner facilement tout le texte afin de le supprimer) et cliquez sur le bouton « Traiter » pour visionner les données retournées par l’algorithme. Vous pouvez aussi utiliser le glisser-déposer : glissez les données JSON ou le fichier les contenant sur la page.

Chaque tableau contenu dans le tableau principal correspond à un état de l’algorithme, auquel on peut accéder avec les boutons fléchés dans la visualisation des résultats de l’algorithme. La taille du tableau principal correspond ainsi au nombre d’états visualisables. Pour construire chaque état, il est recommandé d’avoir une ou plusieurs fonctions spécialisées (j’ai appelé les miennes addDataToShow) qui ajoutent un état au tableau (que j’appelle dataToShow) qui sera à la fin renvoyé par votre algorithme, cette ou ces fonctions étant appelées régulièrement par votre algorithme avec les paramètres adéquats.

Les données de chaque état

Chaque dictionnaire de chaque état doit posséder au moins les clés type et value, et peut en posséder d’autres, qu’elles soient obligatoires ou optionnelles, en fonction de la valeur de type. La valeur de type détermine aussi comment la valeur de value est interprétée.

5 types sont pris en charge : text, table, colorization, vertexValues et edgeValues.

  • text : Le champ value sera par défaut interprété comme du simple texte à insérer dans un paragraphe (balise HTML <p>). Si un champ asHtml est fourni et vaut true, alors la chaine de caractères contenue dans value est insérée directement (sans balise <p>) et est interprétée comme du HTML.
  • table : Le champ value doit contenir un tableau de tableaux de chaines de caractères, qui seront placées dans un tableau HTML. La première colonne est interprétée comme un titre et sera mise en gras. La propriété facultative nbHeadRows permet de définir combien de lignes (forcément les premières du tableau) seront également mises en gras (0 par défaut) : cela permet notamment d’afficher plus proprement les matrices. La propriété style peut contenir des propriétés CSS qui seront fournies au tableau (seulement l’élément principal <table> ; cela peut permettre d’avoir des tableaux côte à côte plutôt que toujours les uns en dessous des autres). Par défaut, les valeurs à mettre dans les cellules sont simplement interprétées comme du texte, mais si un champ asHtml est fourni et vaut true, alors la chaine de caractères est interprétée comme du HTML.
  • colorization : Le champ value doit contenir une couleur (format expliqué ci-dessous). Il y a un autre champ obligatoire : target, qui doit être un tableau contenant les données des éléments à colorer. Pour colorer un sommet, il faut simplement donner son numéro. Pour colorer une arête, il faut donner ses données, ou alors un dictionnaire contenant une clé num dont la valeur est le numéro de l’arête, sinon deux clés src et dest (qui ne permettent pas de gérer les graphes non simples). Si vous fournissez directement les données d’une arête (ou d’un successeur ou prédécesseur), elles seront converties en dictionnaire.
  • vertexValues : Le champ value doit être un tableau de taille nbVertices, contenant le texte à afficher aux côtés de chaque sommet. Le texte sera interprété en SVG (ce qui implique qu’il vaut mieux échapper les < et &). Il y a deux paramètres optionnels : position, un tableau de deux nombres indiquant la position relative du texte par rapport au sommet (par défaut [0, -1], c’est-à-dire au-dessus du sommet) ; et color, la couleur du texte (noir par défaut).
  • edgeValues : Le champ value doit être un tableau de taille nbEdges, contenant le texte à afficher à côté de chaque arête. Le texte sera interprété en SVG et affiché à la place du poids de l’arête — tous les poids seront masqués si l’état utilise le type edgeValues. Si vous voulez néanmoins voir les poids des arêtes, il faut les inclure dans le texte de chaque arête. La clé optionnelle color permet de choisir la couleur du texte.

L’ordre des données de chaque état importe pour les données de type text et table, qui seront insérées les unes en dessous des autres. Concernant les données de type colorization, il faut éviter que plusieurs d’entre elles essaient de colorer le même élément, car alors la couleur de l’élément n’est pas déterminée (elle dépend du moment où la couleur est ajoutée dans le document SVG).

Si des nombres plutôt que des chaines de caractères sont fournis comme valeurs pour les données de type table, vertexValues et edgeValues, ils seront formatés avec la fonction floatToStr présentée plus bas.

Les couleurs doivent être des chaines de caractères contenant soit un nom de couleur CSS (recommandé), soit le code hexadécimal de la couleur (au format #RGB, #RGBA, #RRGGBB ou #RRGGBBAA). Les autres formats (tels qu’un appel de la fonction rgb()) ne sont pas pris en charge.

Les fonctions utilitaires

Quelques fonctions utilitaires sont fournies pour vous aider à construire votre sortie (leur utilisation n’est pas du tout obligatoire). Remarquez que certaines s’appuient sur les données du graphe chargé pour votre fonction : le graphe est en effet enregistré dans une variable globale.

  • getVertexLabel(vertexNum) : renvoie l’étiquette correspondant au numéro de sommet fourni.
  • floatToStr(nb) : renvoie nb converti en chaine de caractères, en ayant supprimé l’éventuel « .0 » en fin de nombre entier et en ayant remplacé l’éventuel point par une virgule francophone. Si nb vaut l’infini positif ou négatif, ou -∞ est renvoyé.
  • formatArraysForVertices(rowNames, values) : génère un tableau de tableaux voué à être utilisé comme valeur de type table. La première ligne contient le titre « Sommet » et l’étiquette de chaque sommet. Ensuite, chaque ligne a comme titre rowNames[i] puis comme contenu values[i] (chaque values[i] doit donc être un tableau). Cela permet d’afficher facilement des données associées aux sommets. Exemple d’utilisation : { type: "table", value: formatArraysForVertices(["Distance", "Père"], [distances, parents]) }.
  • formatBinaryTree(title, treeAsArray) : renvoie un tableau de tableaux organisant le contenu du tableau treeAsArray en arbre binaire. Utile pour afficher les données des files à priorité.
  • parentsToEdges(parents) : renvoie un tableau de dictionnaires contenant chacun deux clés src et dest. À chaque destination i est associée la source parents[i]. Cette fonction permet de colorer facilement les arêtes reliant un sommet à son père (mais n’est pas idéale pour les graphes non simples).
  • getEdgesFromBools(edgesToTake) : renvoie un tableau de dictionnaires contenant chacun une seule clé num contenant la valeur i lorsque edgesToTake[i] vaut vrai. Cette fonction permet de colorer facilement des arêtes marquées par des booléens.
  • getVerticesFromBools(verticesToTake) : renvoie un tableau contenant les entiers i pour lesquels verticesToTake[i] vaut vrai. Cette fonction permet de colorer facilement des sommets marqués par des booléens.

Le nombre d’opérations

Pour compter les opérations effectuées par votre algorithme, vous devez appeler une fonction :

  • countOperation(name, quantity = 1) : compte quantity opérations supplémentaires (1 par défaut) de type name.

Les comptages d’opérations effectués avec countOperation seront inclus dans la sortie JSON de la bibliothèque. Ainsi, vous devez faire un appel countOperation("Description de l’opération") à chaque endroit où l’opération est effectuée dans votre algorithme, pour obtenir une ligne correspondante dans le tableau de complexité.

Outils

Les files à priorité

Une classe ou structure PriorityQueue est fournie, pour vous offrir les fonctionnalités d’une file à priorité avec suivi des indices. Voici ses méthodes et ses champs :

  • PriorityQueue(priorityFct, saveIndexes = false, opNames = null) : priorityFct doit être une fonction recevant en paramètres deux éléments de la file et renvoyant un booléen indiquant si le premier élément est prioritaire par rapport au second, saveIndexes est un booléen indiquant si la sauvegarde des indices est activée, et opNames est un tableau de deux chaines de caractères nommant les opérations de rangement respectivement pour enfilement et pour défilement (si le paramètre vaut null, countOperation ne sera pas appelée ; valeur conseillée : ["Rangement pour enfilement", "Rangement pour défilement"]) ;
  • length() : renvoie la taille de la file (0 lorsqu’elle est vide) ;
  • top() : renvoie l’élément au sommet de la file ;
  • push(val) : ajoute un élément à la file ;
  • pop() : enlève de la file l’élément se trouvant au sommet et le renvoie ;
  • data : le tableau sous-jacent utilisé par la structure (peut être fourni en paramètre à la fonction formatBinaryTree).

Lorsque le suivi des indices est activé, les méthodes et champs suivants peuvent également être utilisés :

  • upgrade(pos, val) : remplace la valeur de l’élément à la position pos par val, et fait éventuellement monter l’élément dans la file ;
  • downgrade(pos, val) : remplace la valeur de l’élément à la position pos par val, et fait éventuellement descendre l’élément dans la file ;
  • dataIndexes : un tableau indexé par les valeurs des éléments, contenant leur position dans la file (dans le tableau data).

Remarquez que pour que le suivi des indices fonctionne, les éléments de la file doivent être des nombres entiers positifs et tous relativement petits. En effet, la taille du tableau dataIndexes est déterminée par la plus grande valeur qui a été mise dans la file. L’idée est que les éléments de la file soient des numéros de sommets ou bien des numéros d’arêtes, qui font appel à des données mutables indexées par ces numéros pour connaitre leur niveau de priorité.

Informations spécifiques à chaque langage

Les interfaces de programmation fournies par les bibliothèques dans les différents langages sont très semblables pour l’entrée, mais ont parfois beaucoup de spécificités pour la sortie. Ces spécificités et les autres sont détaillées dans les sections ci-dessous.

C

La bibliothèque pour le langage C s’appuie sur la petite bibliothèque HandyMacros, que vous devez avoir installée. Le tout est puissant et très efficace, mais est aussi évidemment plus difficile à utiliser que les bibliothèques des langages plus haut niveau. Pour savoir précisément comment utiliser un élément, consultez le fichier api.h (une partie du code sert à permettre à cette bibliothèque d’être utilisée en C++, certaines syntaxes du C n’étant pas acceptées par C++). Votre fonction doit retourner une valeur de type Slice(Any).

Concernant l’entrée, il n’y a pas grand-chose de spécial, si ce n’est que les champs selectedVertices, selectedEdges, succLists et predLists utilisent le type de tableaux dynamiques DynArr d’HandyMacros. Pour la sortie, il faut utiliser intensivement HandyMacros, et l’arbre JSON est fait à l’aide du type Any fourni.

Structures de données spécialisées :

  • Un type générique Any est fourni. Il permet de stocker une valeur de n’importe quel type parmi une dizaine (consultez le fichier api.h). Une macro Any(valeur) est également fournie : elle fonctionne comme un constructeur, prenant une valeur d’un type accepté et retourne une valeur de type Any stockant la valeur fournie. Cette macro utilise le mot-clé _Generic de C11, ainsi qu’une série de minis fonctions annotées static inline. Petit exemple d’utilisation :
    Any vars[] = { Any(1), Any(2.) };
    for (int i = 0; i < array_len(vars); ++i) {
       if (vars[i].type == INT) {
          printf("%d\n", vars[i].i);
       } else if (vars[i].type == FLOAT) {
          printf("%lg\n", vars[i].f);
       }
    }
    Attention avec les booléens : si vous voulez réellement stocker true ou false, vous devrez peut-être convertir explicitement votre paramètre en bool. Si vous construisez un pointeur complexe qui requiert des parenthèses (par exemple un pointeur vers une fonction ou vers un tableau), vous devrez peut-être utiliser AnyT, qui est un synonyme du type Any qui ne risque pas d’être interprété comme la macro de construction.
  • Pour savoir comment utiliser HandyMacros, vous pouvez consulter sa documentation ou le fichier d’exemple fourni ci-dessus.
  • La fonction destroyAny(val) désalloue récursivement tous les tableaux et dictionnaires contenus dans l’arbre de la valeur val. Cette fonction est appelée sur le tableau renvoyé par votre algorithme, c’est pourquoi il ne doit pas contenir deux fois le même AnyArray ou Dict, au risque de faire planter le programme. Si vous voulez mettre deux fois la même valeur dans votre arborescence Any, vous pouvez donner un pointeur vers un Any, par exemple : anyArrayPush(&tab, Any(&val)).

Outils :

  • Quelques fonctions pour gérer les chaines de caractères efficacement sont fournies.
    • Lorsque vous allouez une chaine sur le tas, vous pouvez appeler addString(str) pour que la chaine soit automatiquement désallouée après l’écriture des données JSON. Cette fonction est appelée par les fonctions renvoyant des chaines de caractères, y compris les deux fonctions suivantes.
    • La fonction formatStr(format, ...) renvoie une chaine de caractères correspondant au format fourni, qui est le même que pour printf.
    • La fonction concatStrings(strs) renvoie la concaténation des chaines de caractères pointées par chaque case du tableau strs. Le tableau doit avoir la valeur NULL dans sa dernière case.
  • Les fonctions traitant les files à priorité sont préfixées par PQ_ et prennent en premier paramètre l’adresse de votre file à priorité, sauf la fonction PQ_create. Il n’y a pas de fonctions PQ_top et PQ_length ; lisez directement data.v[0] et data.len. Il y a une fonction PQ_destroy qui se charge de désallouer (non récursivement) les tableaux data et dataIndexes.

Commande de compilation conseillée (une fois que vous avez renommé le fichier template.c en algo.c) :

cc -o algo api.c algo.c -std=c17 -Wall -lhandymacros

Rajoutez éventuellement -O2 ou -O3 pour avoir plus d’optimisations.

C++

La bibliothèque C++ est plus complexe à utiliser que celle de langages plus haut niveau tels que Python et JavaScript, mais est bien plus efficace et demeure confortable si vous avez l’habitude du C++. Vous pouvez consulter le fichier api.hpp (en particulier les lignes non indentées) pour voir tous les éléments qu’elle vous offre.

Votre fonction doit retourner une valeur de type DataToShow, qui est un synonyme de vector<vector<Dict>>, Dict étant un synonyme de map<string, any>. La construction de la sortie repose en effet sur l’utilisation du type any de C++17, et ainsi la compilation ne fonctionnera que si votre compilateur prend en charge C++17. En pratique, seulement 8 types de valeurs seront convertis en JSON :

  • string ;
  • char const * (c’est le type des chaines de caractères constantes comme "celle-ci") ;
  • int ;
  • double ;
  • bool ;
  • Dict (synonyme de map<string, any>) ;
  • AnyVector (synonyme de vector<any>) ;
  • Table (synonyme de vector<AnyVector>) ;
  • pour tout autre type, la valeur null sera écrite.

Les valeurs des structures de type map<string, any> et vector<any> seront converties en JSON récursivement.

Le type PriorityQueue n’accepte que des entiers dans sa file, et leurs indices sont toujours enregistrés dans le tableau dataIndexes (aussi le constructeur n’accepte pas de paramètre saveIndexes). Si vous voulez mettre autre chose que des entiers dans votre file à priorité, vous pouvez utiliser le patron de classe priority_queue de la bibliothèque standard.

La fonction formatBinaryTree n’est pas un patron de fonction (pour éviter d’avoir à inclure son code dans api.hpp afin de vous permettre de lire ce fichier plus facilement) ; au lieu de ça, elle prend en paramètre un vector<any>.

Commande de compilation conseillée (une fois que vous avez renommé le fichier template.cpp en algo.cpp) :

c++ -o algo api.cpp algo.cpp -std=c++17 -Wall

Rajoutez éventuellement -O2 ou -O3 pour avoir plus d’optimisations.

OCaml

La bibliothèque OCaml n’est probablement pas très bien réussie : elle est essentiellement codée en impératif. Les contributions pour l’améliorer sont les bienvenues.

Les noms de types sont écrits tout en minuscules avec tirets bas. Pour la sortie JSON, il y a un type json. Votre fonction doit retourner une liste de listes de listes de couples d’une chaine de caractères et d’une valeur de type json (de telles listes de couples sont appelées « listes associatives » en jargon OCaml). Consultez le début du fichier api.ml pour voir quels constructeurs propose le type json. Les listes et les tableaux seront convertis en JSON de manière identique : que votre valeur soit un JsonArray ou une JsonList, le même code JSON sera généré.

Les données du graphe sont pour certaines fournies sous forme de tableaux (pour fournir l’accès direct par indice), pour d’autres sous forme de listes (lorsque les indices n’ont pas de sens) : consultez le début du fichier api.ml. Les fonctions utilitaires attendent toutes des tableaux, et renvoient soit des tableaux de JsonArray (lorsque leur sortie est un tableau 2D), soit des listes.

La fonction countOperation name n’accepte pas le second paramètre. Si vous voulez compter plusieurs opérations d’un coup, appelez la fonction countOperations name quantity.

Les files à priorité simulent des tableaux dynamiques en interne, c’est-à-dire que la taille du tableau est simplement doublée à chaque fois que nécessaire, et le nombre d’éléments effectif est stocké à part. Lorsque vous appelez la méthode data, vous obtenez une version tronquée du tableau (toutes les valeurs sont copiées). Les paramètres du constructeur de la classe priority_queue diffèrent par rapport au canon exposé plus haut :

  • le deuxième paramètre (saveIndexes) est facultatif, mais si vous voulez activer la sauvegarde des indices, il ne suffit pas de fournir true : il faut fournir ~assignValue:assignValueWithIndexes (assignValue est le nom du paramètre facultatif, assignValuesWithIndexes est la fonction que la file à priorité va utiliser en tant qu’assignValue) ;
  • le troisième paramètre (opNames) ne peut pas être null : pour ne pas compter les opérations, fournissez simplement un tableau vide ;
  • un quatrième paramètre est demandé : il s’agit de la valeur à donner aux cases de tableau inutilisées (qui peut être n’importe quoi mais doit être du même type que les autres valeurs).

Commande de compilation conseillée (une fois que vous avez renommé le fichier template.ml en algo.ml) :

ocamlopt -o algo api.ml algo.ml

Java

La bibliothèque Java ressemble fort à celle du C++. Pour des raisons de simplicité, il est recommandé que votre classe hérite de la classe Api fournie (qui ne contient que des membres statiques). Les champs de la classe Graph et de ses sous-classes sont simplement tous publics (et ces classes n’offrent aucune méthode).

Le type Object est utilisé partout pour la généricité et la conversion en JSON, sauf dans PriorityQueue qui, comme en C++, n’accepte que des Integer et enregistre toujours les indices (pour d’autres types, utilisez la classe java.util.PriorityQueue). Votre fonction doit renvoyer un objet de type List<List<Map<String, Object>>>. Parfois les méthodes attendent des tableaux, d’autres fois des List. Utilisez List.of ou Arrays.asList pour construire facilement une liste à partir d’un tableau, et la méthode .toArray(new Object[0]) dans le sens inverse.

Chaque élément Object est converti en JSON suivant cette procédure :

  • s’il vaut null, alors la valeur null est écrite ;
  • s’il dérive de Number ou de Boolean, alors la valeur est écrite directement ;
  • si c’est une Map, les clés sont écrites comme des String et les valeurs sont converties récursivement, le tout mis dans un objet JSON ;
  • si c’est une List, les valeurs sont converties récursivement et mises dans un tableau JSON ;
  • si c’est une arête ou une extrémité d’arête (Graph.EdgeProps ou Graph.EdgeEnd), un objet JSON contenant la propriété num de l’arête est écrit ;
  • sinon, l’objet est converti en chaine de caractères et écrit comme chaine de caractères.

Pour compiler et exécuter (si vous avez renommé Template.java en Algo.java et adapté le nom de la classe) :

javac Algo.java
java Algo

Python

Aucune remarque sur la bibliothèque Python. Elle est aussi facile à utiliser que la bibliothèque JavaScript.

JavaScript

Si vous utilisez JavaScript, il est inutile de récupérer l’entrée ci-dessus, et il n’y a aucune sortie à coller : collez simplement votre code dans la zone de texte ci-dessus (ou glissez ce code ou le fichier sur la page) et cliquez sur le bouton « JavaScript ». S’il n’y a pas d’erreur, le résultat sera automatiquement affiché dans l’onglet Algorithmes. Vous pouvez obtenir le même résultat en exécutant votre code via la console du navigateur (accessible en appuyant sur F12).

Pour obtenir les champs facultatifs, vous devez passer à la fonction runAlgorithm le nombre correspondant à ce que vous voulez en deuxième paramètre. Par exemple, pour avoir les positions des sommets et la matrice d’incidence :

runAlgorithm(myAlgo, 3);
Graphe
Sommet
,
.,
Votre fichier n’a pas pu être chargé comme graphe SVG.
Votre graphe précédent n’a pas pu être restauré. Veuillez recharger la page.
Voulez-vous vraiment supprimer le graphe « {$0} » ?
Supprimer les éléments sélectionnés ?
Nouveau poids de l’arête :
Nouvelles lettres acceptées par la transition, séparées par des virgules :
Valeur invalide. Pour écrire un nombre à virgule, écrivez soit une virgule soit un point. La valeur 0 n’est pas acceptée car elle n’est pas compatible avec les matrices.
Nouvelle étiquette du sommet (entre {$0} et {$1}) :
Saisie invalide.
Au moins une valeur saisie est invalide.
Donnez le facteur de multiplication
Donnez le nombre de chiffres après la virgule à conserver.
Tri effectué !
Entrez le numéro de l’état auquel vous rendre.
La zone de texte ne contient pas des données JSON valides.
Votre algorithme n’a pas appelé correctement la fonction runAlgorithm ou n’a pas renvoyé de données à afficher.
Une erreur s’est produite, ouvrez la console (F12) pour voir les détails.
Élément n° {$0} de l’état incorrect :
Élément n° {$0} de l’état incorrect (dans target, chaque {} correspond à un élément valide) :
Action annulée : « {$0} »
Aucune action à annuler
Action restaurée : « {$0} »
Aucune action à restaurer
Chargement de document
Suppression des éléments sélectionnés
Création de sommet
Création de lien
Génération de sommets
Génération de liens
Changement des extrémités d’un lien
Déplacement de sommet
Courbement de lien
Déplacement d’éléments
Décourbement de lien
Modification du poids d’un lien
Modification des lettres d’une transition
Changement de l’étiquette d’un sommet
Zoom avant
Zoom arrière
Placement de liens en premier
Placement de liens en dernier
Changement de la convention d’étiquetage des sommets (type)
Changement de la convention d’étiquetage des sommets (première étiquette)
Masquage des poids
Affichage des poids
Passage à un graphe non orienté
Passage à un graphe orienté
Utilisation des vraies distances comme poids
Multiplication des poids par {$0}
Arrondissement des poids à {$0} décimale(s)
Inversion du sens des arcs
Fusion des arêtes parallèles
Fusion des transitions parallèles
Tri des liens
Application de liste d’arêtes
Application de description au format simple de GenGraph
Application de listes d’adjacence
Application de matrice d’adjacence
Marquage d’états comme initiaux
Marquage d’états comme finaux