Vous êtes sur la page 1sur 24

[Dpartement] : Genie MIS

[Module] : Algorithmique et structure de donnes

RAPPORT DE
PROJET
METHODE DU PLUS
COURT CHEMIN DANS
UN GRAPHE MODLIS
PAR UNE LISTE
DADJACENCE
Anne universitaire 2015/2016
Ralis par :

CHIKHI HAMZA & ENNASSIR MEHDI

Encadr par : Mme EL KETTANI

TABLE OF CONTENTS

Contents
Introduction _______________________________________________________________ 1
Les graphes ________________________________________________________________ 2
Les classes templates utilises ___________________________________________________ 6
Reprsentation de la liste dadjacence _____________________________________________ 8
Parcours en largeur __________________________________________________________11
Le code ___________________________________________________________________13

Introduction

La notion de graphes issue de lanalyse combinatoire est trs


importante en informatique et en mathmatiques. Elle permet de
modliser dune manire simple et intuitive des problmes
complexes, gnralement constitus de plusieurs nuds.
Notre projet tudie lalgorithme du plus court chemin dans un
graphe, modlis par une liste dadjacence.
Le langage de programmation utilis est le C++ dans sa version
98, vu sa grande flexibilit, et surtout sa grande rapidit surtout
lorsquil sagit de traiter des quantits importantes de donnes.

Page 1

Les graphes

En informatique, un graphe cest une structure de donnes, oui


tout comme les tableaux. En effet, cest une structure de donnes
compose de nuds interconnects entre eux par des artes.
Chaque nud contient une ou plusieurs donnes de nimporte
quel type. Comme une image vaut mieux quun long discours

Un graphe non orient de 6 nuds et de 7 artes.


Sur ce graphe, partir du nud 1, on peut accder au nud 5
et inversement, il ny a aucun sens pour le parcours du graphe.
Cest un graphe non orient.

Page 2

Il existe aussi des graphes orients qui imposent le sens


dans lequel on va dun nud un autre. Sur les schmas de ce
genre de graphes, les artes sont flches indiquant le sens dans
lequel on va dun nud un autre comme ceci :

Dans le graphe ci-dessus par exemple, on ne peut aller du nud


1 vers le nud 2 mais pas inversement.

Reprsentation des graphes en mmoire :


Le fait quun graphe soit orient ou non ninflue pas sur sa
reprsentation. Cest le rsultat de la reprsentation qui sera
diffrent en fonction du type du graphe. Il existe plusieurs
manires de reprsenter les graphes en mmoire. Ces 2
reprsentations sont les plus connues.

Matrice dadjacence
Liste dadjacence

Page 3

Dans notre cas on va sintress a cette mthode :


Liste dadjacence :
Cette reprsentation consiste en un tableau G de N (avec N le
nombre de nuds du graphe) listes chanes. Pour chaque nud
i du graphe, on aura sa liste dadjacence G[i] qui sera compose
des nuds adjacents i. Toujours avec notre graphe non orient,
on aura :

1: 2 -> 5
2: 1 -> 3 -> 5
3: 2 -> 4
4: 3 -> 5 -> 6
5: 1 -> 2 -> 4
6: 4
Le nud 1 a dans sa liste dadjacence les nuds 2 et 5, le nud
2 a dans sa liste les nuds 1, 3 et 5 ainsi de suite.
Quelle reprsentation choisir entre les deux ?
Chacune de ces 2 reprsentations a des avantages et des
inconvnients. Par exemple, pour vrifier si 2 nuds sont
connects dans une reprsentation par liste dadjacence, on est
oblig de parcourir squentiellement G[i] pour y rechercher j
alors quavec une reprsentation par matrice dadjacence, il suffit
de vrifier si G[i][j] vaut 1, ce qui prend un temps constant.
Cependant, plus le graphe est grand, plus la reprsentation par

Page 4

matrice dadjacence utilise de la mmoire. On choisira telle ou


telle reprsentation en fonction du problme. Dans la suite de cet
article, nous reprsenterons les graphes par liste dadjacence.

Page 5

Les classes templates utilises


Afin de rpondre nos besoins, la STL (standard template
library) implmente un grand nombre de classe Template
dcrivant des containers gnriques pour le langage C++. La STL
fournit en plus des algorithmes permettent de manipuler
aisment ces containers (pour les initialiser, rechercher des
valeurs, ...).
La STL introduit galement le concept d'iterator qui permet de
parcourir trs facilement un container en s'affranchissant
compltement de la manire dont il est implment

std::list<T,...>
La classe list fournit une structure gnrique de listes chanes
pouvant ventuellement contenir des doublons.
Complexit :

Page 6

Insertion (en dbut ou fin de liste) : O(1)


Recherche : O(n) en gnral, O(1) pour le premier et le
dernier maillon

std::vector<T,...>
La classe vector est proche du tableau du C. Tous les
lments contenus dans le vector sont adjacents en mmoire,
ce qui permet d'accder immdiatement n'importe quel
lment. L'avantage du vector compar au tableau du C est sa
facult se rallouer automatiquement en cas de besoin lors
d'un push_back ou d'un resize par exemple. Cependant une
case n'est accessible par l'oprateur que si elle a t alloue au
pralable (sinon une erreur de segmentation se dclenche).

Page 7

Reprsentation de la liste dadjacence


Pour faciliter la tche, on va se reposer sur le type list de la STL.

#include<iostream>
#include<list>

using namespace std;

const int N = 10; // Le nombre de noeuds du graphe


const int MAX = N + 1; // Le nombre de noeuds du graphe + 1

int main()
{
list<int> graph[MAX]; // On dclare un tableau de listes de
taille MAX pour qu'on puisse commencer l'indice 1 au lieu de 0

return 0;
}

Page 8

Aussi simple que , pour parcourir la liste dadjacence dun nud,


on utilise un itrateur. Un itrateur en C++ est un objet qui
permet de parcourir une collection dlments (ici une liste).
On pourrait par exemple afficher un graphe avec le code cidessous :
#include<iostream>
#include<list>
using namespace std;
const int N = 10;
const int MAX = N + 1;
int main()
{

list<int> graph[MAX];

for(int i = 1; i <= N; ++i)


{

printf("%d ->", i);

/* On utilise un itrateur (it) pour parcourir la liste


d'adjacence de i */
for(list<int>::iterator it = graph[i].begin(); it !=
graph[i].end(); ++it)
printf(" %d", *it); // On utilise *it pour
accder la valeur pointe par it
printf("\n");
}

Page 9

return 0;
}
Pour ajouter un lment la liste dadjacence dun nud, il suffit
dappeler lune des fonctionspush_front() (insertion en tte de la
liste) ou push_back() (insertion en queue de la liste) de la STL.
Par exemple pour ajouter le nud 2 la liste dadjacence de 1,
on fera :

graph[1].push_front(2);

// ou alors

graph[1].push_back(2);

Vous pourriez avoir des rsultats diffrents selon que vous


utilisiez push_front() ou push_back()en fonction de lopration
que vous tes en train de faire sur votre graphe, mais ceux-ci
seront toujours corrects pour autant que votre algorithme le soit.
Par exemple, pour un algorithme de recherche plus court chemin,
le plus court chemin que vous trouverez en construisant votre
graphe avec push_front() sera peut-tre diffrent de celui que
vous auriez trouv si vous laviez construit avec push_back().

Page 10

Parcours en largeur
Le parcours en largeur dun graphe ou breadth first
search (en anglais) est un algorithme de parcours trs simple.
A partir dun nud origine, il passe par tous les arcs du graphe
pour dcouvrir les nuds accessibles depuis ce nud origine.
Donc, il permet partir dun nud de connatre tous les nuds
qui lui sont accessibles. Mais pas seulement, il calcule aussi la
distance entre ce nud et les nuds qui lui sont accessibles. Et
il permet aussi de construire le chemin qui relie ce nud origine
chacun des nuds qui lui sont accessibles. Le parcours en
largeur a une proprit trs intressante (peut-tre la plus
intressante), prenons un graphe G sur lequel on a fait un
parcours en largeur partir dun nud u; pour tout nud v
accessible depuis u, le chemin de u vers v dans G constitue un
plus court chemin de u vers v dans G. Ce qui veut donc dire que
le parcours en largeur calcule les plus courts chemins du nud
origine vers chacun des nuds qui lui sont accessibles.
Fonctionnement :
Lalgorithme utilise une file FIFO (premier entr, premier sorti)
pour parcourir le graphe. On insre dabord le nud origine src
dans la file. Tant que cette file nest pas vide, on rcupre son
1er lment, (notons le u), on parcourt la liste dadjacence de u;
chaque nud v rencontr, si le nud n a pas encore t
dcouvert, on le marque comme dcouvert, sinon on passe, on
indique ensuite que le prdcesseur ou parent de v dans le
graphe est u et on calcule la distance qui mne de src v en
ajoutant 1 la distance qui mne de src u.
Pour calculer les distances, on stocke en parallle un
tableau dist[N] (N, le nombre de noeuds du graphe), qui pour
chaque nud u stocke la distance menant de la source u. Au

Page 11

dbut de lalgorithme, on donnera la distance de chaque noeud,


la valeur infini . Ainsi, si aprs le parcours en largeur, la
distance dun nud est gale infini , cela veut dire que ce
nud nest pas accessible depuis le noeud origine.
De mme, on aura un tableau parent[N], qui pour chaque nud
u du graphe stocke son parent. Ici aussi, au dbut la valeur
de parent[] pour chaque nud vaut NIL; donc la fin du
parcours, un nud dont le parent vaut NIL nest pas accessible
depuis lorigine.
Pour marquer quun nud a t dcouvert, chaque fois que
lalgorithme rencontre pour la premire fois un nud, il le colorie
en gris et le colorie en noir, une fois quil a fini de traiter sa liste
dadjacence et au dpart, tous les nuds sont blancs. Pour la
couleur, on grera aussi simplement un tableau qui indique la
couleur de chaque nud.

Page 12

Le code
Code :
#include<iostream>
#include<vector>
#include<list>
#include<queue>
using namespace std;

const int INF = 2147483647; // Notre valeur "infini"


const int NIL = -1; // On choisit -1 comme valeur pour NIL
const int N = 10;
const int MAX = N + 1;

enum {WHITE, GREY, BLACK}; // Une numration pour les couleurs BLANC, GRIS
et NOIR

vector<int> color(MAX); // Tableau pour marquer les couleurs


vector<int> dist(MAX); // Tableau pour calculer les distances
vector<int> parent(MAX); // Tableau pour marquer les parents

void bfs(int src, list<int> graph[])


{

Page 13

/* Pour commencer */
for(int i = 1; i <= N; ++i)
{
color[i] = WHITE; // On colorie tous les noeuds en blanc
dist[i] = INF; // On affecte INF la distance de chaque noeud
parent[i] = NIL; // On affecte NIL au parent de chaque noeud
}

color[src] = GREY; // On colorie la source en gris, on vient de le dcouvrir


dist[src] = 0; // Puisque c'est le 1er noeud dcouvert, sa distance vaut 0
parent[src] = NIL; // La source n'a pas de parent

queue<int> q;
q.push(src); // On y insre notre source

while(!q.empty())
{
int u = q.front(); // on rcupre le noeud en tte de la queue
q.pop(); // On dfile la queue

/* On parcourt la liste d'adjacence de u */


for(list<int>::iterator it = graph[u].begin(); it != graph[u].end(); ++it)
{

Page 14

int v = *it; // Simple convnience, on va utiliser v au lieu de *it

if(color[v] == WHITE) // Si c'est la premire fois qu'on dcouvre v


{
dist[v] = dist[u] + 1; // On calcule sa distance
color[v] = GREY; // On le marque comme dj dcouvert
parent[v] = u; // On affecte son parent u

q.push(v); // On insre v la tte de la file


}
}

color[u] = BLACK;
}
}

Et voil. Je crois que le code parle de lui mme. Comme toujours,


on se base sur les types prdfinis de la STL, notamment les
types vector et queue. Si vous ne connaissez pas la STL, je vous
invite jeter un coup doeil sur les liens prcdents.
Aprs le parcours en largeur, on peut se servir du
tableau parent[] pour retracer le chemin qui mne du noeud src
vers nimporte quel autre noeud qui lui est accessible avec cette
fonction
void print_path(int src, int dest)
{

Page 15

if(src == dest)
printf("%d", src);
else if(parent[dest] == NIL)
printf("Il n'y a pas de chemin de %d vers %d\n", src, dest);
else
{
print_path(src, parent[dest]);
printf(" %d", dest);
}
}

Exemple
Nous allons excuter un parcours en largeur partir du noeud 1
sur le graphe ci-dessous et afficher tous les plus courts chemins
menant de 1 chacun des autres noeuds.

Page 16

Voici lentre quon devra fournir notre programme. La


premire ligne contient le nombre n de connexions. Ensuite, il y
a n lignes successives composes chacune de 2 entiers u et v tels
que les nuds u et v soient connects dans le graphe.
7
12
15
25
23
34
45
46

Voici le code
#include<iostream>
#include<vector>
#include<list>
#include<queue>

using namespace std;

const int INF = 2147483647;


const int NIL = -1;

Page 17

const int N = 6;
const int MAX = N + 1;

enum {WHITE, GREY, BLACK};

vector<int> color(MAX);
vector<int> dist(MAX);
vector<int> parent(MAX);

/* Parcours en largeur */
void bfs(int src, list<int> graph[])
{
for(int i = 1; i <= N; ++i)
{
color[i] = WHITE;
dist[i] = INF;
parent[i] = NIL;
}

color[src] = GREY;
dist[src] = 0;
parent[src] = NIL;

Page 18

queue<int> q;
q.push(src);

while(!q.empty())
{
int u = q.front();
q.pop();

for(list<int>::iterator it = graph[u].begin(); it != graph[u].end(); ++it)


{
int v = *it;
if(color[v] == WHITE)
{
color[v] = GREY;
dist[v] = dist[u] + 1;
parent[v] = u;

q.push(v);
}
}

color[u] = BLACK;
}

Page 19

/* Affiche le chemin menant de src dest */


void print_path(int src, int dest)
{
if(src == dest)
printf("%d", src);
else if(parent[dest] == NIL)
printf("Il n'y a pas de chemin de %d vers %d", src, dest);
else
{
print_path(src, parent[dest]);
printf(" %d", dest);
}
}

int main()
{
int n = 0;
scanf("%d", &n);

list<int> graph[MAX];

Page 20

while(n--)
{
int u = 0, v = 0;
scanf("%d %d", &u, &v);

/* Puisque c'est un graphe non orient, il faut indiquer que ... */


graph[u].push_front(v); // u est connect v ...
graph[v].push_front(u); // et que v aussi est connect u

int src = 1;
bfs(src, graph); // On fait le parcours en largeur

/* Il ne nous reste plus qu' afficher le chemin menant chaque noeud */


for(int i = 2; i <= N; ++i)
{
printf("Chemin de %d vers %d : ", src, i);
print_path(src, i);
printf("\n");
}

return 0;

Page 21

Ce qui nous donne comme rsultat


Chemin de 1 vers 2 : 1 2
Chemin de 1 vers 3 : 1 2 3
Chemin de 1 vers 4 : 1 5 4
Chemin de 1 vers 5 : 1 5
Chemin de 1 vers 6 : 1 5 4 6

Page 22