Consignes

Le TP est à rendre (par email) pour le mercredi 16 décembre à 8h00.

Pour ce TP comme pour les suivants, la pièce la plus importante sera un fichier tp2-nom-prenom.c contenant votre programme. Le seul fait que votre programme fonctionne ne suffit pas pour avoir une bonne note. Les points suivants seront également pris en compte :

  1. la lisibilité de votre programme (choix pertinent pour les noms de variables, indentation etc.),
  2. la présence de commentaires aux endroits appropriés,
  3. ...

Les réponses aux questions posées dans le TP (complexité de vos fonctions par exemple) seront insérées comme commentaires dans votre fichier principal.

Attention : les points suivants ne rapportent rien, mais ne pas les respecter pourra retrancher jusqu'à 10 points sur la note finale :

Liens utiles


1. Préliminaires

Avant de commencer

  1. Téléchargez l'archive du TP.

  2. La compilation se fait avec le fichier Makefile fourni comme pour le TP1. Il suffit de modifier la variable NOM pour prendre en compte votre propre fichier.

  3. Je ne fournis pas de fonction de test pour ce TP. Vous devrez donc vous débrouiller pour tester vos fonctions au fur et à mesure. (Par contre, ce n'est pas la peine de m'envoyer votre fonction principale.)

1.1. La bibliothèque

L'archive du TP contient le fichier graphes.c. Il contient les définitions des fonctions déclarées dans graphes.h.



2. Le tri topologique

Nous avons vu pendant le TD2 l'algorithme du « tri topologique ». Cet algorithme permet de linéariser un graphe orienté sans cycle et fonctionne de la manière suivante : on effectue un parcours en profondeur et on trie les sommets par ordre de « fin[] » décroissant.

Écrivez la fonction triTopologique de prototype

int *triTopologique(GrapheListe G) ;

Les consignes sont les suivantes :

Donnez la complexité de votre fonction, en justifiant.

Votre fonction devra être la plus simple possible et n'utilisera pas les listes chaînées comme structure intermédiaire.

(N'oubliez pas de documenter ce que vous faites...)

3. Composantes fortement connexes

Calculer les composante connexes dans un graphe non-orienté se fait facilement avec un parcours.

Décrivez un algorithme simple (sans forcement l'écrire) pour calculer les composantes connexes d'un graphe non-orienté. Les consignes sur le résultat de votre fonction sont les mêmes que pour la question suivante...

Une composante fortement connexe dans un graphe orienté est un ensemble maximal de sommets qui vérifie la propriété suivante : « il existe un chemin orienté de n'importe quel sommet vers n'importe quel autre ».

Une manière de calculer les composantes fortement connexe du graphe G est de procéder comme suit :

  1. faire un parcours en profondeur du graphe G
  2. calculer G', le transposé de G
  3. faire un parcours (en largeur ou en profondeur) de G', __en prenant les sommets par ordre de fin[] décroissant. (L'ordre de fin[] est donné par le parcours de G.)
  4. chaque arbre de la foret couvrante donné par le second parcours correspond exactement à une composante fortement connexe.

Programmer la fonction de prototype :

int composantesFortementConnexes(GrapheListe G, int *CC) ;

Sa valeur de retour sera le nombre de composantes fortement connexes du graphe G, et l'argument CC sera un tableau que l'on remplira avec les numéro de composantes fortement connexes : si le sommet s est dans la composante connexe c, alors après l'appel à la fonction on aura CC[s] == c.

Comme pour la question 1, essayer d'avoir un code le plus simple possible et documentez bien ce que vous faites.

4. Algorithme de Prim

Implémentez l'algorithme de Prim pour calculer un arbre couvrant de poids minimal dans un graphe non-orienté :

int prim(GrapheListe G, int *foret) ;

Votre fonction devra renvoyer le poids minimal trouvé et stocker l'arbre en question dans le tableau foret[].

Génération de labyrinthe (Bonus)

Les fonctions grilleRectPleine() et grilleHexPleine() créent des grilles rectangulaire / hexagonal pleines avec des poids aléatoires. Si on recherche un arbre couvrant de poids minimal, on obtient donc un arbre aléatoire sur la grille, c'est à dire un labyrinthe.

Générez des labyrinthes en écrivant les fonctions labyrintheRect() et grilleHexPleine().

Comparez avec les labyrinthes crées par des parcours en largeur ou des parcours en profondeur, où l'on prend les arêtes par ordre croissant de poids.