Consignes

Le TP est à rendre (par email) pour le dimanche 12 décembre à 23h59

Pour des raisons évidentes de météo, le TP ne pourra pas se dérouler normalement. Pour des raisons d'emploi du temps chargé, il semble difficile de déplacer le TP. Nous allons donc essayer de faire le TP à distance (pour ceux qui peuvent).

Le sujet étant en ligne, vous devriez pouvoir y accéder.

Pour nous contacter, vous pouvez utiliser

Pour utiliser Jabber, le plus simple est d'utiliser le logiciel Pidgin (paquet Ubuntu) et de se créer un compte Jabber / XMPP :

Si vous disposez d'une adresse @gmail.com, vous pouvez l'utiliser directement...

Je ne peux malheureusement pas vérifier si Pidgin est installé en Mauriennes, et pour des raisons évidentes de météo, la DSI fonctionne avec un personnel très réduit...

Les autres clients possibles pour utiliser Jabber / XMPP sont :

Si aucun de ces logiciels n'est installé dans les salles du bâtiment Mauriennes et que vous n'avez pas de portable accessible, utilisez alors l'email pour vos questions...

Pour ce TP comme pour les suivants, la pièce la plus importante sera un fichier tp1-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.

Si vous le souhaitez, vous pouvez aussi envoyer un rapport de TP. Le format de ce rapport sera au choix, mais par ordre de préférence :

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

Estimation du temps

Liens utiles

1. Préliminaires

Avant de commencer

Récupérer les fichiers

Téléchargez l'archive du TP1. Après un

$ tar xvf info505-tp1.tar

vous devriez avoir un répertoire info505-tp1 contenant plusieurs fichiers :

Compiler

Vous ne devez pas modifier les fichiers graphes.h ou tp1.h. Le fichier sur lequel vous travaillerez est tp1-nom-prenom.c. Un exemple de tels fichier est inclus dans l'archive : tp1-hyvernat-pierre.c. Vous pouvez l'utiliser comme point de départ.

Pour la compilation, le plus simple est d'utiliser le fichier Makefile fourni. Il suffit de l'ouvrir dans un éditeur de texte et de modifier la ligne

NOM = tp1-hyvernat-pierre
pour mettre votre nom et prénom à la place. (Attention, il faut que votre fichier s'appelle tp1-nom-prenom.c.) Ensuite, il suffit d'utiliser la commande

$ make

pour recompiler tout votre TP.

Tester

Votre fichier tp1-nom-prenom.c ne contient pas de fonction main() : celle ci se trouve dans le fichier test.c. Pour tester vos fonctions au fur et à mesure, vous pouvez :

Le fichier Makefile produit automatiquement un exécutable test qui contient le code de la fonction main().

Envoyer

Avant d'envoyer votre fichier tp1-nom-prenom.c, n'oubliez pas d'ajouter les options de compilation dans le fichier Makefile (voir les consignes) et de testez sur la fonction de test originale. (C'est ce que je ferais...)

1.1. La bibliothèque

Lisez le début du fichier graphes.h pour voir un peu quelles fonctionnalités vous sont fournies.

1.1.1. Les graphes

La bibliothèque propose deux types de données pour les graphes :

Dans les deux cas, si G est un graphe, vous avez accès au nombre de sommets avec G->nb_sommets. Pour accéder au tableau de listes d'adjacences, il faut faire G->Adj et pour accéder à la matrice, il faut faire G->Matr.

Les listes d'adjacences sont des listes chaînées :

Pour accéder à un élément de la matrice, on utilise G->Matr[i][j].

On stocke également un poids (nombre entier) pour chaque arc. Dans le cas d'une matrice d'adjacence, le poids est stocké directement dans la matrice ; dans le cas d'une liste d'adjacence, on stocke le poids du premier arc dans L->poids.

Vous avez du voir la notation G.Adj pour accéder au champs Adj dans G. La notation G->Adj fait la même chose quand G est un pointeur. C'est donc un synonyme de (*G).Adj. Vous devriez voir tout ça dans votre cours de C...

Le fichier graphes.h vous donne également les prototypes de quelques fonctions pour manipuler ces types :

Reportez-vous aux commentaires dans ce fichier pour savoir comment utiliser ces fonctions.

Attention : un graphe contient des données mutables (modifiables). Vous ne devez pas modifier un graphe lorsque vous le donnez en argument à une fonction.

1.1.2. Les tiles

Le fichier contient également un type de données Tile, qui permet de faire des piles ou des files d'arcs :

Pour le moment, vous n'avez besoin que de files de sommets (nombres entiers). Les tiles sont des listes chaînées d'arcs, avec une origine, un but et un poids. Vous n'avez donc qu'à utiliser un des champs des tiles et vous pouvez ignorer les autres. D'autre part, la fonction defile() utilise un pointeur. Vous utiliserez donc les files de la manière suivante :

  1. pour déclarer une file et l'initialiser à la file vide :
    Tile F = tileVide() ;
    
  2. pour insérer un sommet t dans la file F :
    entile(t, 0, 0, F) ;
    
    Remarquez que je mets 0 en arguments 2 et 3 car je n'en ai pas besoin. (Je pourrais tout aussi bien mettre 42...)
  3. pour défiler le premier élément de la file F dans la variable t (de type int) :
    defile(F, &t, NULL, NULL) ;
    
    Le « & » devant le t vient du fait que l'on veut récupérer la valeur dans la variable t : on doit donc passer l'adresse de t. Pour les arguments 3 et 4, on peut passer le pointeur NULL car on ne demande pas leurs valeurs.

1.1.3. Remarques sur les pointeurs

Vous n'avez pas encore vu les pointeurs, ni le lien entre pointeurs et tableaux dans le langage C. Pour ce TP, vous pouvez considérer que le type int* veut dire tableau d'entiers de taille inconnue. Par exemple, le prototype

int* distances (GrapheListe G, int a);
veut dire que la fonction renvoie un tableau d'entiers.

Pour allouer un tableau dynamique (dont la taille n'est pas connue à la compilation) non local à une fonction (sur lequel on veut faire un return), il faut utiliser la syntaxe suivante :

  int *sommets = malloc(n * sizeof(int));
qui permet d'allouer un tableau de n entiers dans le tas. Ce tableau a donc une durée de vie supérieure à la fonction dans lequel il est défini.

Une déclaration int sommets[n] déclarerait le tableau dans la pile, et disparaîtrait donc hors de la fonction... (Ceci n'est valide qu'à partir de la norme C99.)

1.2. Affichage sommaire d'un graphe

Écrivez une fonction de prototype

void afficheGrapheListe (GrapheListe) ;
qui affiche les arcs d'un graphe à l'écran.

Par exemple :

$ ./test
Le graphe a 8 sommets :
  0  --->  3
  0  --->  2
  0  --->  1
  4  --->  5
  5  --->  5
Le graphe a 5 arcs.

1.3. Conversion entre les deux types de données

Écrivez les deux fonctions de conversion

GrapheMatr listeVersMatrice(GrapheListe) ;
GrapheListe matriceVersListe(GrapheMatr) ;
pour passer d'un type à l'autre.

N'oubliez pas de tester vos fonctions...

1.4. Inversion des arcs d'un graphe

Écrivez une fonction

GrapheListe transpose(GrapheListe) ;
qui à partir d'un graphe, calcul son transposé : tous les arcs sont en sens inverse.

Qu'elle est la complexité de votre fonction, en fonction de n (nombre de sommets) et m (nombre d'arc) ?

2. Parcours en largeur

2.1. Parcours

Programmez l'algorithme du parcours en largeur vu en cours.

Pour changer un peu, au lieu de renvoyer un tableau pere[] contenant la foret du parcours, renvoyez un graphe Pere. (Une foret est un type particulier de graphe...)

Le prototype de votre fonction est donc

GrapheListe parcoursLargeur (GrapheListe G) ;

2.2. Calcul de distances

Programmez une fonction

int* distances(GrapheListe G, int a) ;
qui renvoie le tableau des distance du sommet a vers tous les autres sommets (ou -1 s'il n'y a pas de chemin approprié.)

Déduisez-en une fonction

int distance(GrapheListe G, int a, int b) ;
qui renvoie la distance entre les sommets a et b dans le graphe G.

Qu'elle est la complexité de votre fonction, en fonction de n (nombre de sommets) et m (nombre d'arc) ?

2.3. Calcul du diamètre

Le diametre d'un graphe, c'est le maximum de toutes les distances entre les sommets du graphes...

Écrivez une fonction

int diametre(GrapheListe G, int *a, int *b) ;
qui calcule le diamètre d'un graphe, et met deux sommets le plus éloignés possible dans a et b.

Qu'elle est la complexité de votre fonction, en fonction de n (nombre de sommets) et m (nombre d'arc) ?

Cette fonction prend en argument deux pointeurs vers des entiers. Elle n'utilise pas les valeurs entières correspondantes mais les modifie pour donner les numéros des sommets.

Votre fonction se terminera donc probablement par quelque chose comme :

int diametre(GrapheListe G, int *a, int *b) {
  ...
  ...
  *a = s1 ;
  *b = s2 ;
  return(d) ;
}
Et vous l'appellerez de la manière suivante :
  ...
  int d, s, t ;
  d = diametre(G, &s, &t) ;
  printf("Le graphe a un diamètre de %i ;
          %i et %i sont deux sommets éloignés de cette distance.\n",
             d, s, t) ;
  ...
(C'est le même principe que dans un scanf().)

3. Résolution de labyrinthes

Un labyrinthe peut être vu comme un graphe non-orienté : chaque sommet est une pièce et les arêtes sont des portes.

Si on impose qu'il y ait exactement un chemin entre chaque pièce, le graphe du labyrinthe est en fait un arbre...

Écrivez une fonction

int* resoudLabyrinthe(GrapheListe G, int entree, int sortie) ;
qui parcourt le graphe G à partir du sommet entree et cherche un chemin vers le sommet sortie.

Votre fonction renverra le morceau du tableau pere[] créé par le parcours.

Écrivez une petite fonction afficheChemin(int *pere, int entree, int sortie) qui affiche les sommets pour aller de entree à sortie en utilisant l'information contenue dans le tableau pere. Par exemple :

 - départ du sommet 12,
 - aller au sommet 5,
 - aller au sommet 6,
 - aller au sommet 22,
 - aller au sommet 0,
 - arrivée au somme 42 !

Que pensez-vous de cette méthode pour résoudre un labyrinthe « à la main » ?

Pour tester votre fonction, vous pouvez aussi utiliser les fonctions un peu plus visuelles suivantes :

qui permettent de créer et d'afficher un labyrinthe sur une grille rectangulaire ou hexagonale :

    +--+--+--+--+--+--+--+--+--+--+                __    __    __    __    __
    |  |  |              |     |  |               /  \__/  \__/  \__/  \__/  \__
    +  +  +  +--+  +--+--+--+  +  +               \  /  \        /   __/   __   \
    |        |        |           |               /   __   \__/  \__    __/  \__/
    +  +--+  +--+  +  +--+  +--+--+               \__/        \__/  \      __   \
    |  |     |  |  |           |  |               /  \__/  \__   \     \__/  \__/
    +  +--+--+  +  +--+--+  +--+  +               \__    __   \  /  \__/   __   \
    |        |     |     |  |     |               /   __/  \__/   __/  \  /   __/
    +  +  +--+--+--+--+  +  +  +  +               \__/   __    __/  \  /  \__   \
    |  |  |     |        |     |  |               /        \__   \__   \__/  \  /
    +  +--+  +--+  +--+--+--+--+  +               \__/  \__/  \__/   __/  \__   \
    |     |  |  |  |        |     |               /   __      /   __/  \  /     /
    +--+--+  +  +  +--+  +--+  +--+               \__/  \__/   __/  \  /  \__/  \
    |                    |        |               /   __   \        /   __/  \  /
    +  +  +--+--+--+--+--+--+--+  +               \__/  \__   \__/   __/   __   \
    |  |     |           |        |               /  \__   \__/         __/  \  /
    +  +--+--+--+  +--+--+  +--+--+               \__    __   \__/  \__   \  /  \
    |              |              |               /   __/   __/  \__/  \__   \  /
    +--+  +--+  +  +--+--+  +--+  +               \__/   __/   __    __    __/  \
    |        |  |              |  |               /     /   __   \  /  \  /  \  /
    +--+--+--+--+--+--+--+--+--+--+               \__/   __/   __/   __/   __/  \
                                                     \__/  \__/  \__/  \__/  \__/

Écrivez une fonction

char *transformePere(GrapheListe G, int *pere, int a, int b) ;
qui produit un tableau S[] utilisable par les fonctions afficheGrilleRect() et afficheGrilleHex().

Par exemple, si votre fonction test contient

  depart = 0;
  arrivee = 90;
  G = labyrintheHex(10,10);
  pere = resoudLabyrinthe(G, depart, arrivee);
  S = transformePere(G, pere, arrivee, depart);
  afficheGrilleRect(G,10,10,S);
le résultat donnera

$ ./test
 __    __    __    __    __    __    __    __    __    __    
/ @\__/  \__/  \__/  \__/  \__/ .\__/ .\__/  \__/  \__/  \__ 
\  / . __/ .\  /     /     / $\    . __  . __/  \__    __   \
/ .   / .\    .\  /  \__/  \__  .\  /     /  \__   \__/  \  /
\  / . __  .\  /  \__    __   \__/  \__/ .\      __      /  \
/  \__/  \__/ . __   \     \__   \__/     /  \     \__/   __/
\__/  \__   \__  .\__/  \__/  \__/  \__/ .   /  \__/  \__/  \
/   __/        \__  .\  /   __    __  . __/  \__/  \  /     /
\     \__/  \  / . __/  \__/  \__/ .   /  \__   \__    __/  \
/  \__   \__/ .   /  \__/   __  .\  /  \  /  \__      /  \  /
\     \__  . __/  \  /  \  /  \    .\__/   __/  \  /  \__   \
/  \__/     / .\__/ . __/  \  / .\  /   __/  \  /  \__/     /
\__/     / .\    .   / .\__/  \  /  \__   \__   \__/   __/  \
/  \__/  \    .\__/ .   /     / .\__/  \__   \__   \__/  \__/
\__      /  \__/   __/ .\__/ . __    __/  \     \  /  \__   \
/  \__/  \  /  \__/       .\     \__   \  /  \__    __   \  /
\  /  \__/  \  /  \__/  \    .\  /  \__      /  \__/   __/  \
/  \__   \     \  /   __/  \__/  \__    __/  \__    __   \  /
\     \     \__/  \__/   __/  \__/   __/     /  \  /   __   \
/  \     \      __/     /   __   \  /  \__/     /  \  /   __/
\__/  \__/  \__/   __/   __   \__/   __   \__/  \__/  \__   \
   \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/  \__/

4. Un parcours générique (Bonus)

(bonus)

Les questions 4, 5 et 7 utilisent toutes un parcours en largeur. Programmez un parcours en largeur générique qui permette de définir les fonctions parcoursLargeur(), distances(), distance() et resoudLabyrinthe().

Redéfinissez ces fonctions pour utiliser votre nouveau parcours.