1. Graphs & Algorithmie : Recherches sur l’implémentation
des algorithmes liés à Dijkstra et au problème des K-plus
courts chemins
Arthur Van de Wiele
Louis Thibon
25 Janvier 2011
3. Chapitre 1
Structures de donnée et outils divers
1.1 Graphviz
Graphviz est une suite d’utilitaires sous système Unix qui permet de tracer le visuel d’un graphe
donné à partir d’un structure de donnée (langage) propre à Graphviz. Il est donc possible de créer
un utilitaire simple pour convertir une structure de graphe simpliste en langage interprétable par
un des programmes de Graphviz : “dot”.
Le langage interprétable par Graphviz se présente comme suite :
1 digraph G
2 {
3 edge [ color=purple , arrowsize =2];
4 node [ color=pink , s t y l e=f i l l e d ] ;
5 0 −> 1 [ l a b e l =1];
6 0 −> 3 [ l a b e l =2];
7 1 −> 2 [ l a b e l =3];
8 1 −> 3 [ l a b e l =4];
9 2 −> 3 [ l a b e l =5];
10 }
Ce qui permet d’obtenir le tracé suivant :
Figure 1.1 – Exemple de grape obtenu à l’aide des outils de Graphviz
2
4. Mais cet utilitaire prend tous son sens lors de tracés de graphes plus importants, lorsque le
tracé devient réellement fastidieux, comme dans cet exemple :
Figure 1.2 – Second exemple de graphe obtenu à l’aide des outils de Graphviz
1.2 Conversion d’une méthode de saisie simple vers graphviz
Le langage qu’interprète Graphviz implique certaines redondances dans son écriture. Afin de
faciliter l’écriture d’un graphe, nous avons développé un petit utilitaire travaillant sur une méthode
de saisie simple qui permet d’obtenir instantanément un code de type Graphviz.
Le méthode de saisie que nous avons souhaité utilisé se construit comme suit : Chaque ligne cor-
respond à un sommet suivi du nombre et de la liste de ses successeurs avec l’affectation du poids
de l’arc entre eux. Pour plus de facilité, la première ligne doit comporter le nombre de sommets
du graphe. Ce qui se traduit par l’écriture suivante pour le cas de la figure 1.1 :
1 4
2 0 2 1 1 3 2
3 1 2 2 3 3 4
4 2 1 3 5
5 3 0
Le code source de ce programme est donné ci après. Les explications se trouvent à la suite du
code.
3
5. 1 #include <stdio . h>
2 #include <s t d l i b . h>
3 #include <s t r i n g . h>
4
5 // Structure de donnee permettant d ’ obtenir des l i s t e s chainees
6 typedef struct l i s t e l i s t e ;
7 struct l i s t e
8 {
9 int noeud ;
10 int poid ;
11 l i s t e ∗ suiv ;
12 };
13
14 int main ( int argc , char ∗argv [ ] )
15 {
16
17 // Ouverture du f i c h i e r comportant l e code s i m p l i f i e du graphe .
18 FILE∗ src_graph=fopen ( argv [ 1 ] , " r " ) ;
19
20 // Afin de ne pas surcharger l e code , l e t e s t l i e a l ’ existence du
parametre ou du f i c h i e r n ’ apparait pas .
21
22 // L ’ u t i l i s a t i o n des l i s t e s chainees et de l ’ a l l o c a t i o n memoire via
des malloc permet d ’ optimiser l ’ espace memoire u t i l i s e pour
stocker un graphe . Ceci permet egalement de t r a i t e r toute t a i l l e
de graphe .
23
24 l i s t e ∗∗deb=NULL;
25 l i s t e ∗∗ parc_tbl=NULL;
26
27 l i s t e ∗parc_chaine=NULL;
28 l i s t e ∗ prec=NULL;
29
30 int nbr_noeud=0,no_noeud=0, nbr_successeurs =0;
31 int no_succ=0, poid_arc=0;
32
33 int i =0;
34
35 f s c a n f ( src_graph , "%d" , &nbr_noeud ) ;
36
37 deb = ( l i s t e ∗∗) malloc ( nbr_noeud ∗ sizeof ( l i s t e ∗ ) ) ;
38 parc_tbl=deb ;
39
40 for ( i =0; i<nbr_noeud ; i++)
41 {
42
43 f s c a n f ( src_graph , "%d %d" , &no_noeud,& nbr_successeurs ) ;
44 prec = NULL;
45
46 i f ( nbr_successeurs )
47 {
48 while ( nbr_successeurs != 0)
49 {
50 parc_chaine = ( l i s t e ∗) malloc ( sizeof ( l i s t e ) ) ;
51 f s c a n f ( src_graph , "%d %d" , &no_succ ,&poid_arc ) ;
52 parc_chaine−>noeud = no_succ ;
4
6. 53 parc_chaine−>poid = poid_arc ;
54 parc_chaine−>suiv = NULL;
55 i f ( prec == NULL) ∗ parc_tbl = parc_chaine ;
56 else prec−>suiv = parc_chaine ;
57 prec = parc_chaine ;
58 nbr_successeurs −−;
59 }
60 }
61 else
62 {
63 ∗ parc_tbl = NULL;
64 }
65 parc_tbl++;
66 }
67
68 // Ci apres , la l e c t u r e du graphe donnant l e code necessaire au bon
fonctionnement des u t i l i t a i r e s de graphviz .
69
70 // Cet algorithme revient a parcourir simplement la structure de
donnee u t i l i s e e pour stocker l e graphe . Cette structure est
presentee dans un autre paragraphe .
71
72 parc_tbl = deb ;
73 p r i n t f ( " digraph G {n" ) ;
74 p r i n t f ( "edge [ color=purple , arrowsize =2];n" ) ;
75 p r i n t f ( "node [ color=pink , s t y l e=f i l l e d ] ; n" ) ;
76 for ( i =0; i<nbr_noeud ; i++)
77 {
78 parc_chaine=∗parc_tbl ;
79 while ( parc_chaine!=NULL)
80 {
81 p r i n t f ( "%d −> %d [ l a b e l=%d ] ; n" , i , parc_chaine−>noeud ,
parc_chaine−>poid ) ;
82 parc_chaine=parc_chaine−>suiv ;
83 }
84 parc_tbl++;
85 }
86 p r i n t f ( "}n" ) ;
87
88 f c l o s e ( src_graph ) ;
89
90 return 0;
91 }
Le fonctionnement de ce programme est simple, il prend en paramètre (lors de l’appel de
l’exécutable) un nom de fichier (lien relatif) et en effectue la traduction vers le langage Graphviz.
Afin de ne pas surcharger ce programme, nous avons préféré effectuer l’affichage en console du code
généré, plutôt que le le faire passer via des tubes et des sous-process directement à l’utilitaire ’dot’.
De ce fait, afin d’obtenir un graphique sous forme d’image (ici, de type png) la commande d’appel
nécessite d’utiliser un pipe en console afin de router directement les code généré vers l’utilitaire :
1 ./ conv graph1 . txt | dot −Tpng −o t e s t . png
Ce programme utilise une structure de données pour le stockage du graphe basée sur les listes
chainées qui sera détaillée dans la prochaine section.
5
7. 1.3 Structure de donnée liée au stockage du graphe
La structure de donnée ici présentée est celle mise en place dans le programme de conversion
précédent. Elle sera également utilisé lors de l’implémentation de l’algorithme de Djikstra et celui
des k-plus courts chemins.
Pour faire simple, cette structure de donnée se présente sous la forme d’un tableau d’éléments
de type liste correspondant à chaque sommet. Chacun de ces sommets est ensuite lié à tous ses
successeurs via une liste chainée. Cela permet non seulement de donner un sens à un arc, mais
également de pouvoir tirer profit de la simplicité de représentation de cette structure de donnée
pour y adapter tout type d’algorithme issu de la théorie des graphes.
La structure de donnée est la suivante :
1 // Structure de donnee permettant d ’ obtenir l e s l i s t e s chainees
2 typedef struct l i s t e l i s t e ;
3 struct l i s t e
4 {
5 int noeud ;
6 int poid ;
7 l i s t e ∗ suiv ;
8 };
On peut ainsi représenter le stockage du graphe pris en exemple précédemment comme suit :
Figure 1.3 – Représentation du stockage du graphe via des listes chainées
En outre, l’utilisation de cette structure permet de parcourir le graphe de manière intuitive. En
effet, on connait immédiatement tous les successeurs de chaque sommet, et les poids qui les relient.
1.4 Structure de donnée liée à l’algorithme de Dijkstra
Pour l’algorithme de Dijkstra, deux structures sont nécessaire, l’une pour créer le tableau des
distances, l’autre pour créer le tableau des prédécesseurs. Dans la pratique, une seule structure
suffit car on peut référencer la distance ou le prédécesseur comme une simple donnée de type int.
Comme le préconise l’algorithme de Dijkstra, cette structure doit également comporter un verrou,
afin de ne pas écraser des données déjà traitées.
La structure de donnée est la suivante :
1 typedef struct str_dijkstra str_dijkstra ;
6
8. 2 struct str_dijkstra
3 {
4 int noeud ;
5 int data ; // La donnee peut etre : distance , ou n_o du predecesseur
6 int verrou ;
7 };
Ce verrou est présent dans les deux tableau, ce qui constitue une redondance puisque chaque
élément des deux tableaux sont verrouillés en même temps. Mais cela constitue aussi une vérification
quand au bon fonctionnement de l’algorithme.
1.5 Structure de donnée nécessaire à l’algorithme des k-plus
courts chemins
Pour résoudre ce problème nous allons utiliser deux structures de données spécifiques. Une
structure appelée "nœud", qui sera la structure de l’arbre. Cette structure contiendra la valeur
du sommet mis dans l’arbre ainsi que le poids de l’arc avec le sommet précédent (dans l’arbre).
"nœud" contiendra également un pointeur vers son nœud père et, le nombre de fils pouvant varier,
une liste de fils.
1 struct noeud
2 {
3 int valeur_sommet ;
4 int poid_prec_noeurd ;
5 l_Arbre∗ f i l s ;
6 noeud∗ pere ;
7 };
La liste de fils constitue la deuxième structure utilisée. Cette structure contient un pointeur
vers un "nœud" de l’arbre, qui est le fils en question, ainsi qu’un pointeur vers le maillon suivant
de la liste, le fils suivant.
1 struct l_Arbre
2 {
3 noeud∗ noeud_A ;
4 l_Arbre∗ suiv ;
5 };
C’est en combinant l’utilisation de ces deux structures que nous parviendrons à créer l’arbre
représentant les chemins. Voici alors le schémas de la structure de l’arbre :
7
9. Figure 1.4 – Représentation des structures de donnée nécessaire à la création d’un arbre
8
10. Chapitre 2
Problème du plus court chemin
2.1 Présentation du problème
Le cheminement dans les graphes, et plus précisément la recherche du plus court chemin, est un
problème récurrent dans l’étude des graphes. Ces applications sont multiples : réseaux électriques
ou réseaux d’information ou encore calcul d’itinéraire via des cartes, plusieurs algorithmes issus
de la théorie des graphes permettent de répondre à ces problèmes mathématiques. Nous allons ici
nous intéresser à l’algorithme de Dijkstra puis tenter de l’implémenter en langage C.
L’algorithme de Dijkstra utilise deux tableaux, l’un de prédécesseurs, l’autre des plus courtes
distances entre deux points quelconques. Avec l’application successive de l’algorithme de Dijkstra,
on finit par obtenir le tableau du plus court chemin reliant la source à tous les autres sommets.
Cela revient donc à effectuer un parcours ordonné du graphe en mettant à jour certaines in-
formation dans les deux tableaux selon un certain nombre de règles.
2.2 Analyse de l’algorithme de Dijkstra
Cet algorithme effectue l’initialisation de ses deux tableaux puis les remplie au fur et à mesure de
son évolution dans le graphe et selon des contraintes bien définies. On peut repésenter l’algorithme
comme suit :
1 Pour k de 1 a n , f a i r e :
2 distances [ k ] <−− cout ( s , k) ;
3 predecesseurs [ k ] <−− s ;
4 FinPour ;
5 M <−− Supprimer ( s , M) ;
6
7 TantQue (M non vide ) Faire :
8 i <−− LePlusProche (M) ;
9 Si ( distances [ i ] = i n f ) Alors retour ;
10 M <−− Supprimer ( i , M) ;
11
12 Pour k de 1 a d+( i ) Faire :
13 j <−− k−eme_successeur ( i ) ;
14 Si ( EstSupprime ( j , M) <> 1)
15 v <−− distances [ i ] + cout ( i , j ) ;
16 Si (v < distances [ j ] )
17 Alors distances [ j ] <−− v ;
18 Alors predecesseurs [ j ] <−− i ;
19 FinSi
20 FinSi
9
11. 21 FinPour
22 FinTantQue
2.3 Commentaires sur l’implémentation
Pour implémenter cet algorithme, nous avons repris la structure de stockage de graphe vue dans
le programme de conversion de langage, c’est à dire un tableau de listes chainées. Il est donc néces-
saire de ré-implémenter sous forme de fonctions les algorithmes de génération de cette structure de
graphe. Il y a ensuite un certains nombre de fonction à développer pour faire tourner l’algorithme
de Dijkstra. Certaines fonctionnalités trop souvent utilisées seront également isolée en tant que
fonction a part entière pour plus de clarté.
Pour permettre a l’algorithme de Dijkstra de fonctionner, il est nécessaire de quantifier les dis-
tances infinies. Nous avons donc choisi une distance maximale qu’aucun chemin ne doit dépasser
afin que les comparaisons fonctionnent.
1 #define INF 999999999
2.4 Fonctions utiles à l’affichage
2.4.1 Fonction nécessaire à l’obtention du code Graphviz
Reprise de l’utilitaire de conversion, cette petite fonction permet de rendre compte du graphe
avec lequel on travail. Tout étant détaillé dans la partie correspondante au convertisseur, il ne
semble pas nécessaire d’expliquer plus en détail son fonctionnement.
1 int to_graphviz ( l i s t e ∗∗deb , int nbr_noeud )
2 {
3 l i s t e ∗∗ parc_tbl=NULL;
4 l i s t e ∗parc_chaine=NULL;
5 int i =0;
6
7 parc_tbl = deb ;
8
9 p r i n t f ( " digraph G {n" ) ;
10 p r i n t f ( "edge [ color=purple , arrowsize =2];n" ) ;
11 p r i n t f ( "node [ color=pink , s t y l e=f i l l e d ] ; n" ) ;
12
13 for ( i =0; i<nbr_noeud ; i++)
14 {
15 parc_chaine=∗parc_tbl ;
16
17 while ( parc_chaine!=NULL)
18 {
19 p r i n t f ( "%d −> %d [ l a b e l=%d ] ; n" , i , parc_chaine−>noeud ,
parc_chaine−>poid ) ;
20 parc_chaine=parc_chaine−>suiv ;
21 }
22
23 parc_tbl++;
24 }
25
10
12. 26 p r i n t f ( "}n" ) ;
27
28 return 1;
29 }
2.4.2 Fonction d’affichage des tableaux liés à la structure de donnée
spécifique à Dijkstra
Cette petite fonction permet de vérifier que l’algorithme de Dijkstra effectue correctement le
remplissage des tableaux en les affichant.
1 void aff_str_dijkstra ( str_dijkstra ∗ str , int nbr_noeud , char∗ mot)
2 {
3
4 int i =0;
5
6 for ( i = 0 ; i < nbr_noeud ; i ++)
7 {
8 p r i n t f ( "Noeud %d a pour %s %d du sommet %d . ( verrou : %d) n" , i ,
mot , ( s t r+i )−>data , ( s t r+i )−>noeud , ( s t r+i )−>verrou ) ;
9 }
10
11 }
2.5 Fonctions spécifiques au structures de données utilisées
2.5.1 Fonction de génération du graphe
Comme pour le passage à Graphviz, cette fonction est issue du convertisseur précédemment
traité. Elle prend en paramètre le fichier de l’utilisateur, et génère la structure de graphe de type
tableau de listes chainées.
1 l i s t e ∗∗ generation_liste (FILE∗ src_graph , int∗ noeuds )
2 {
3 int nbr_noeud=0,no_noeud=0, nbr_successeurs =0;
4 int nbr_successeurs_temp= 0 , no_succ=0, poid_arc=0;
5
6 int i =0;
7
8 l i s t e ∗∗deb=NULL;
9 l i s t e ∗∗ parc_tbl=NULL;
10 l i s t e ∗parc_chaine=NULL;
11 l i s t e ∗ prec=NULL;
12
13 f s c a n f ( src_graph , "%d" , &nbr_noeud ) ;
14
15 ∗noeuds = nbr_noeud ;
16
17 deb = ( l i s t e ∗∗) malloc ( nbr_noeud ∗ sizeof ( l i s t e ∗ ) ) ;
18
19 parc_tbl=deb ;
20
21 for ( i =0; i<nbr_noeud ; i++)
11
13. 22 {
23 f s c a n f ( src_graph , "%d %d" , &no_noeud,& nbr_successeurs ) ;
24 prec = NULL;
25
26 i f ( nbr_successeurs )
27 {
28 nbr_successeurs_temp = nbr_successeurs ;
29 while ( nbr_successeurs != 0)
30 {
31 parc_chaine = ( l i s t e ∗) malloc ( sizeof ( l i s t e ) ) ;
32
33 f s c a n f ( src_graph , "%d %d" , &no_succ ,&poid_arc ) ;
34
35 parc_chaine−>noeud = no_succ ;
36 parc_chaine−>poid = poid_arc ;
37 parc_chaine−>suiv = NULL;
38
39 parc_chaine−>nbr_successeurs = nbr_successeurs_temp ;
40
41 i f ( prec == NULL) ∗ parc_tbl = parc_chaine ;
42 else prec−>suiv = parc_chaine ;
43
44 prec = parc_chaine ;
45 nbr_successeurs −−;
46 }
47 }
48
49 else
50 {
51 ∗ parc_tbl = NULL;
52 }
53
54 parc_tbl++;
55 }
56
57 return deb ;
58 }
Cette fonction retourne le premier élément du tableau afin de pouvoir le parcourir dans les
autres fonctions.
2.5.2 Fonction d’affichage des tableaux liés à la structure de donnée
spécifique à Dijkstra
Cette petite fonction permet de vérifier que l’algorithme de Dijkstra effectue correctement le
remplissage des tableaux en les affichant.
1 void aff_str_dijkstra ( str_dijkstra ∗ str , int nbr_noeud , char∗ mot)
2 {
3
4 int i =0;
5
6 for ( i = 0 ; i < nbr_noeud ; i ++)
7 {
8 p r i n t f ( "Noeud %d a pour %s %d du sommet %d . ( verrou : %d) n" , i ,
mot , ( s t r+i )−>data , ( s t r+i )−>noeud , ( s t r+i )−>verrou ) ;
12
14. 9 }
10
11 }
2.6 Fonctions utiles à l’algorithme de Dijkstra
2.6.1 Fonction retournant le poids de l’arc entre deux sommets
Cette fonction permet de connaitre le poids de l’arc entre deux sommets. Si ces deux sommets
ne sont pas directement reliés par un arc, cette fonction renvoie la valeur de l’infini quantifié comme
expliqué précédemment.
1 int get_poid ( l i s t e ∗∗ deb , int nbr_noeud , int sommet , int suivant )
2 {
3 l i s t e ∗∗ parc_tbl = NULL;
4 l i s t e ∗parc_chaine = NULL;
5 int i =0, poid = INF ;
6
7 parc_tbl = deb ;
8
9 for ( i =0; i<nbr_noeud ; i++)
10 {
11 parc_chaine=∗parc_tbl ;
12
13 while ( parc_chaine!=NULL)
14 {
15 i f ( i == sommet && parc_chaine−>noeud == suivant )
16 {
17 poid = parc_chaine−>poid ;
18 return poid ;
19 }
20
21 parc_chaine=parc_chaine−>suiv ;
22 }
23
24 parc_tbl++;
25 }
26
27 return poid ;
28 }
2.6.2 Trouver la distances minimum depuis le tableau des distances
Cette fonction permet à l’algorithme de Dijkstra de travailler sur le tableau des distances, et
renvoie le sommet correspondant à la distance la plus petite qui n’a pas déjà été verrouillée.
1 int find_dist_min ( str_dijkstra ∗ distances , int nbr_noeud )
2 {
3 // Le premier noeud est toujours l e point source
4 int i = 0 , minimum = INF , sommet = −1;
5
6 for ( i = 0; i < nbr_noeud ; i++)
7 {
13
15. 8 i f (( distances+i )−>data < minimum && ( distances+i )−>verrou == 0)
9 {
10 minimum = ( distances+i )−>data ;
11 sommet = i ;
12 }
13 }
14
15 return sommet ;
16 }
2.6.3 Fonction permettant de retrouver les successeurs d’un sommet
Cette fonction travail sur un tableau temporaire et y stock tous les successeurs d’un sommet
donné. Elle retourne en outre le nombre de successeurs.
1 int find_successeurs ( int sommet , l i s t e ∗∗deb , int nbr_noeud ,
str_dijkstra ∗ temp)
2 {
3 int i = 0;
4 l i s t e ∗parc_chaine = NULL;
5
6 parc_chaine=∗(deb+sommet) ;
7
8 while ( parc_chaine!=NULL)
9 {
10 temp [ parc_chaine−>noeud ] . data = 1;
11 parc_chaine=parc_chaine−>suiv ;
12 i ++;
13 }
14
15 return i ;
16 }
2.6.4 Fonction de verrouillage des deux tableaux
Comme expliqué précédemment, les deux tableau se verrouillent systématiquement ensemble.
Il est donc plus simple de créer une fonction à cet égard.
1 void v e r r o u i l l e r ( int sommet , str_dijkstra ∗ distances , str_dijkstra ∗
predecesseurs )
2 {
3 ( distances+sommet)−>verrou = 1;
4 ( predecesseurs+sommet)−>verrou = 1;
5 }
2.6.5 Initialisation nécessaire à l’algorithme de Dijkstra
L’algorithme de Dijkstra travaille sur deux tableau, comme explicité précédemment, mais il
faut au préalable préparer ces tableaux. Initialiser le tableau des distances ainsi que le tableau des
prédécesseurs et verrouiller le premier élément, le sommet source.
14
16. 1 void i n i t ( l i s t e ∗∗ deb , str_dijkstra ∗ predecesseurs , str_dijkstra ∗
distances , int nbr_noeud )
2 {
3 /∗
4 Permet d ’ i n i t i a l i s e r correctement l e s deux
5 tableaux contenant l e s distances et l e s predecesseurs
6 en parcourant la l i s t e de l i s t e s chainees
7 ∗/
8
9 l i s t e ∗parc_chaine = NULL;
10
11 int i =0, poid = INF ; // INF = 999999999
12
13 // I n i t i a l i s a t i o n des valeurs de verrou et des predecesseurs
14 for ( i = 0 ; i < nbr_noeud ; i ++)
15 {
16 ( distances+i )−>data = poid ;
17 ( predecesseurs+i )−>data = 0;
18
19
20 ( distances+i )−>verrou = 0;
21 ( predecesseurs+i )−>verrou = 0;
22 }
23
24 distances −> verrou = 1; // Verouillage du premier element
25 predecesseurs −> verrou = 1; //
26
27 parc_chaine=∗deb ;
28
29 // i n i t i a l i s a t i o n des valeurs des distances
30 while ( parc_chaine!=NULL)
31 {
32 distances [ parc_chaine−>noeud ] . data = parc_chaine−>poid ;
33 parc_chaine=parc_chaine−>suiv ;
34 }
35 }
2.6.6 L’algorithme en lui même
Ci après le code de l’algorithme de Dijkstra comme défini plus haut, en utilisant la structure
de stockage du graphe donnée.
1 int algo ( l i s t e ∗∗ deb , str_dijkstra ∗ predecesseurs , str_dijkstra ∗
distances , int nbr_noeud )
2 {
3
4 /∗
5 Fonction recursive implementant l ’ algorithme de Dijkstra .
6 Cette fonction remplie correctement l e s deux tableaux
7 de distances et de predecesseurs .
8 Les noeux sans predecesseurs ont une distance a la source de
999999999.
9 ∗/
10
11 str_dijkstra ∗temp = NULL;
15
17. 12 temp = ( str_dijkstra ∗) c a l l o c ( nbr_noeud , sizeof ( str_dijkstra ) ) ;
13
14
15 int noeud_plus_proche = 0;
16 int nbr_successeurs = 0;
17 int i = 0 , temp_int = 0;
18
19 noeud_plus_proche = find_dist_min ( distances , nbr_noeud ) ;
20
21 i f ( noeud_plus_proche == −1)
22 {
23 perror ( "nPas de p o s s i b i l i t e d ’ appliquer l ’ algo encore une f o i s " ) ;
24 return 1;
25 }
26
27 // Le tableau temp contient la data = 1 s i l e sommet correspondant a
la position dans l e tableau est un predecesseur .
28 nbr_successeurs = find_successeurs ( noeud_plus_proche , deb , nbr_noeud ,
temp) ;
29
30 // Remplissage des tabelaux de distances et des predecesseurs .
31 for ( i = 0 ; i < nbr_noeud ; i++ )
32 {
33 i f (( temp+i )−>data == 1 && ( distances+i )−>verrou == 0)
34 {
35 temp_int = ( distances+noeud_plus_proche )−>data + get_poid ( deb ,
nbr_noeud , noeud_plus_proche , i ) ;
36
37 ( predecesseurs+i )−>data = noeud_plus_proche ;
38
39 i f (( distances+i )−>data > temp_int ) ( distances+i )−>data =
temp_int ;
40 }
41 }
42
43 // Verrouillage des noeux concernes .
44 v e r r o u i l l e r ( noeud_plus_proche , distances , predecesseurs ) ;
45
46 // Desallocation de la memoire .
47 f r e e (temp) ;
48
49 return algo ( deb , predecesseurs , distances , nbr_noeud ) ;
50 }
2.6.7 La fonction main
Ci après, la fonction main() donne l’ordre d’appel des différentes fonctions.
1 int main ( int argc , char ∗argv [ ] )
2 {
3 i f ( argv [ 1 ] == NULL)
4 {
5 p r i n t f ( " Error : no input f i l e . n" ) ;
6 return 1;
7 }
16
18. 8
9 FILE∗ src_graph=fopen ( argv [ 1 ] , " r " ) ;
10
11 l i s t e ∗∗deb = NULL;
12
13 str_dijkstra ∗ distances = NULL;
14 str_dijkstra ∗ predecesseurs = NULL;
15
16
17 int nbr_noeud = 0;
18
19 deb = generation_liste ( src_graph , &nbr_noeud ) ;
20
21 distances = ( str_dijkstra ∗) c a l l o c ( nbr_noeud , sizeof ( str_dijkstra ) ) ;
22 predecesseurs = ( str_dijkstra ∗) c a l l o c ( nbr_noeud , sizeof ( str_dijkstra )
) ;
23
24 i n i t ( deb , predecesseurs , distances , nbr_noeud ) ;
25
26 algo ( deb , predecesseurs , distances , nbr_noeud ) ;
27
28 p r i n t f ( "n" ) ;
29 aff_str_dijkstra ( distances , nbr_noeud , " distance " ) ;
30 p r i n t f ( "n" ) ;
31
32 aff_str_dijkstra ( predecesseurs , nbr_noeud , " predecesseur " ) ;
33 p r i n t f ( "n" ) ;
34
35 /∗ to_graphviz ( deb , nbr_noeud ) ; ∗/
36 noeud∗ r=(noeud ∗) malloc ( sizeof ( noeud ) ) ;
37
38 r=algo_k ( r , deb , ( ( ∗ deb )−>nbr_successeurs ) ) ;
39
40 f c l o s e ( src_graph ) ;
41
42 // Desallocation de la memoire .
43 f r e e ( distances ) ;
44 f r e e ( predecesseurs ) ;
45
46 return 0;
47 }
17
19. Chapitre 3
Problème des K-plus courts chemins
3.1 Présentation du problème
Le but est d’implémenter un algorithme qui permettra d’afficher tous les chemins d’un nœud de
départ à nœud d’arrivée dans un graphe. Pour résoudre ce problème nous avons décider de stocker
ces chemins dans un arbre qui contiendra les numéros des nœuds et le poids de l’arc avec le nœud
parent.
Figure 3.1 – Schéma du graphe considéré
Ainsi ce graphe donnerai :
Figure 3.2 – Arbre du graphe considéré
Cet arbre représente les chemins possibles pour aller de 0 à 3. La branche qui se termine par le
4 est une branche "terminale", c’est à dire qu’elle ne pourra jamais mener à 3, elle ne représente
donc pas un chemin. pour trouver les k plus court chemins il nous suffit de parcourir l’arbre et de
compter la somme des poids de chaque branche. Chaque branche se terminant par une feuille 3
représente un chemin.
18
20. 3.2 Analyse de l’algorithme des k-plus court chemins
Comme dit précédemment, l’algorithme des k-plus court chemins utilisé est un algorithme qui
créé un arbre contenant la liste de tout les chemins possibles d’un point à un autre.
L’algorithme théorique se définit comme suit :
1 Algorithme k plus court ( noeud i n i t i a l , noeud f i n a l )
2 racine de l ’ arbre = noeud i n i t i a l
3 de 0 a nombre de noeud f a i r e :
4 s i nombre de f i l s = 0 f a i r e
5 retourner noeud i n i t i a l
6 sinon f a i r e
7 chaque successeur devient un f i l s .
8 pour chaque f i l s f a i r e :
9 s i f i l s d i f f e r e n d de noeud f i n a l f a i r e
10 f i l s = Algorithme k plus court ( f i l s , noeud f i n a l )
11 sinon s i f i l s = noeud f i n a l f a i r e
12 retourner f i l s
13 f i n de
14 retourner noeud i n i t i a l
15 f i n Algorithme k plus court
L’algorithme consiste à créer, de manière récursive, la liste de fils du nœud actuel (en fonction
des successeurs). Puis pour chacun des fils on répète l’opération, ainsi à la fin on obtient bien une
structure semblable à celle présenté dans la partie structure de données utilisée.
3.3 Commentaires sur l’implémentation
En ce qui concerne l’implémentation de l’algorithme nous n’avons pas réussit à aboutir malgré
plusieurs essais et reprises du code. Nous avons cependant réussit à implémenter la partie qui crée
les fils correspondants aux successeurs. Il manque donc la partie récursive de notre algorithme.
3.3.1 Fonction d’affichage de l’arbre
Cette fonction à simplement pour but d’afficher l’arbre contenant tout les successeurs pour
pouvoir visualiser les chemins.
1 int afficher_arbre_RGD ( noeud∗ racine )
2 {
3 p r i n t f ( "Debut a f f i c h a g e n" ) ;
4 i f ( racine==NULL)
5 {
6 p r i n t f ( " racine null . n" ) ;
7 return 0;
8 }
9 else
10 {
11 l_Arbre∗ parc=(racine −>f i l s ) ;
12 // on a f f i c h e l e pere
13 p r i n t f ( "Sommet %d : " , racine −>valeur_sommet ) ;
14 p r i n t f ( "Poid_prec %d | " , racine −>poid_prec_noeurd ) ;
15 // on passe au f i l s
16 while ( parc−>noeud_A!=NULL )
17 {
18 afficher_arbre_RGD ( parc−>noeud_A) ;
19
21. 19 parc=parc−>suiv ;
20 }
21 return 1;
22
23 }
24 }
3.3.2 Fonction de création des nœuds des successeurs
Celte fonction à pour but de créer la liste des nœuds fils contenant les successeurs du nœud
racine envoyé en paramètre. Elle retourne un pointeur vers cette liste de fils.
1 l_Arbre∗ creerSucc ( noeud∗ racine , l i s t e ∗∗ deb , int nbr_noeud )
2 {
3 int i =0, j =0;
4
5 i f (∗ deb==NULL) // s i la l i s t e des successeurs est vide on ne cree
rien
6 {
7 return NULL;
8 }
9 // on recupere l e nombre de successeurs
10 int nbr_successeurs =((∗deb )−>nbr_successeurs ) ;
11 p r i n t f ( " nbr_successeurs=%dn" , nbr_successeurs ) ;
12
13 racine −>f i l s = ( l_Arbre ∗) malloc ( sizeof ( l_Arbre ) ) ;
14 l_Arbre∗ parc= racine −>f i l s ;
15 l i s t e ∗ parc_l = ∗deb ;
16
17 while ( i<nbr_successeurs ) // pour chacun des successeurs
18 {
19
20 p r i n t f ( " f i l s %d n" , i ) ;
21 // on cree un noeud f i l s correspondant au successeur
22 noeud∗ temp = ( noeud ∗) malloc ( sizeof ( noeud ) ) ;
23 // on r e l i e ce noeud a son pere
24 parc−>noeud_A = temp ;
25 // on met l e s bonnes valeurs dans ce noeud
26 parc−>noeud_A−>valeur_sommet=parc_l−>noeud ;
27 parc−>noeud_A−>poid_prec_noeurd=parc_l−>poid ;
28 parc−>noeud_A−>pere=racine ;
29 parc−>noeud_A−>f i l s=NULL;
30 p r i n t f ( "%d , %d n" , parc−>noeud_A−>valeur_sommet , parc−>noeud_A−>
poid_prec_noeurd ) ;
31 i f ( i != nbr_successeurs ) // s i ce n ’ est pas l e dernier successeur
32 {
33 // on a l l o u e de la place pour l e noeud suivant
34 parc−>suiv=(l_Arbre ∗) malloc ( sizeof ( l_Arbre ) ) ;
35 }
36 // on passe au suivant
37 parc=parc−>suiv ;
38 parc_l=parc_l−>suiv ;
39 i ++;
40 }
41 parc=NULL;
20
22. 42
43 return racine −>f i l s ; // on renvoie la l i s t e des f i l s du noeud pere
44
45 }
3.3.3 Fonction algorithme des k plus court chemins
Cette fonction à normalement pour but de dérouler l’algorithme. Mais dans notre cas comme
l’implémentation n’as rien donnée elle ne déroulera l’algorithme que sur le nœud de départ et ses
fils.
1 noeud∗ algo_k ( noeud∗ racine , l i s t e ∗∗ deb , int nbr_noeud )
2 {
3
4 int ret =0, i =0, nbr_succ=0;
5 l_Arbre∗ f i l s =(l_Arbre ∗) malloc ( sizeof ( l_Arbre ) ) ;
6 l_Arbre∗ tmp=(l_Arbre ∗) malloc ( sizeof ( l_Arbre ) ) ;
7 l i s t e ∗∗ parc_deb = deb ;
8
9 // i n i t i a l i s a t i o n : sommet 0
10 // on cree l e s f i l s du somet 0
11 f i l s=creerSucc ( racine , parc_deb , nbr_noeud ) ;
12
13 nbr_succ=(∗parc_deb )−>nbr_successeurs ;
14
15 // pour chacun des successeurs
16
17 for ( i =0; i<nbr_succ ; i++)
18 {
19 // successeur i
20 p r i n t f ( "n Sommet : %d n " , f i l s −>noeud_A−>valeur_sommet ) ;
21 parc_deb= ( deb + f i l s −>noeud_A−>valeur_sommet ) ;
22 // on cree l e s f i l s de chacun des sommet correspondant au
successeurs .
23 tmp=creerSucc ( f i l s −>noeud_A , parc_deb , nbr_noeud ) ;
24 f i l s=f i l s −>suiv ;
25 }
26
27 p r i n t f ( "lancement a f f i c h a g e n" ) ;
28
29 // a f f i c h a g e de l ’ arbre
30 ret = afficher_arbre_RGD ( racine ) ;
31 p r i n t f ( "%dn" , ret ) ;
32
33 return racine ;
34 }
21