Académique Documents
Professionnel Documents
Culture Documents
Algorithmique
Initiée par le grand mathématicien suisse Euler, avec le célèbre problème des 7 ponts de Königsberg, les applications
de la théorie des graphes et de la recherche opérationnelle sont aujourd'hui immenses tant au plan civil que militaire :
La théorie des graphes n'est pas une branche indépendante des mathématiques, elle se rattache à la programmation
linéaire, la programmation convexe (où le concept plus général de fonction convexe remplace les fonctions linéaires
et affines), la topologie, le calcul des probabilités.
D’un point de vue mathématique, un graphe est la donnée d'un certain nombre de points du plan, appelés sommets,
certains étant reliés par des segments de droites ou de courbes (simples) appelés arêtes, la disposition des sommets
et la forme choisie pour les arêtes n'intervenant pas. Le nombre de sommets du graphe est son ordre. Sauf indication
contraire, un graphe sera considéré comme non orienté et les arêtes pourront être parcourues dans les deux sens. Ce
type de graphe peut se présenter dans des problèmes élémentaires de type combinatoire. La plupart des applications
de la théorie graphes, (problèmes d'optimisation, communications, trafics aériens, ...) conduisent à des graphes
orientés où les arêtes orientées sont appelées arcs. Le degré d'un sommet est le nombre d'arêtes auxquelles il est
relié. Deux sommets d'un graphe sont dits adjacents s'il existe une arête (ou un arc) qui les relie. Un graphe est
dit complet si deux quelconques de ses sommets sont adjacents
S1 S1
S2 S2
Sommet Sommet
S3 S3
Dans les 2 exemples ci-dessus, les graphes sont d’ordre 3 et tous leurs sommets sont de degré 2. Les 2 graphes sont
en outre complets.
A B
?
A B C
D
C D
Situation réelle Situation modélisée par un graphe non orienté
Vous travaillez dans un parc et devez entretenir les ponts représentés en rouge sur la figure ci-dessus. Pour économiser
du temps et de l'énergie, vous désirez passer sur chaque pont une et une seule fois.
Q1. Etablir le graphe modélisant la situation décrite.
Q2. En partant de la zone C, trouvez tous les chemins possibles.
Q3. Cela est-il possible en commençant depuis l'île B ? Expliquez.
Q4. Quel est l’ordre du graphe ?
Q5. Précisez le degré de chaque sommet du graphe.
Q6. Pourquoi, selon vous, le graphe modélisant le situation étudiée est-il qualifié de non orienté.
Précurseur avec Leibniz de la théorie des graphes et de la topologie, Euler résolut (1735) le problème des sept ponts
de Königsberg (aujourd'hui Kaliningrad, Fédération de Russie) :
A B
?
A B C
D
C D
Situation réelle Situation modélisée par un graphe non orienté
1. Le promeneur doit revenir à son point de départ à la fin de son parcours, c’est-à-dire suivre ce qu’on appelle
un cycle.
2. Chaque pont doit être traversé une fois exactement (on appelle aujourd’hui « cycle eulérien » un tel cycle).
Essayer tous les chemins possibles peut devenir très long lorsque la taille du graphe augmente. Le génie d’Euler a été
de trouver une condition très simple pour savoir si un tel chemin existe. Euler a remarqué ceci :
• chaque pont ne doit être parcouru qu’une fois donc il faut autant de ponts pour quitter un quartier que pour y
revenir (sinon l’on ne reviendrait jamais au point de départ) ;
• il faut donc que chaque quartier comporte un nombre pair de ponts.
Un cycle est forcément non eulérien si au moins un de ses sommets possède un nombre impair d’arêtes.
Au premier trimestre 2020, Facebook© revendiquait 2,6 milliards d'utilisateurs actifs chaque mois, en hausse de 9,2% par
rapport à début 2019. Le réseau social américain a passé la barre symbolique des 2 milliards au deuxième trimestre 2017. A
noter que 42% des utilisateurs actifs mensuels de Facebook viennent d'Asie-Pacifique, 15,6% sont Européens et 9,7% sont
Nord-américains. Facebook permet à ses utilisateurs d’entrer des informations personnelles et d’interagir avec
d’autres utilisateurs. Les interactions entre utilisateurs reposent sur la notion « d’amis ».
Distances A B C D E F
A 0
B 0
C 0
D 0
E 0
F 0
4.2. Implémentation d'un graphe non orienté à l'aide d'une matrice d'adjacence
◼ Un graphe non orienté peut être transcrit sous la forme d’une matrice d’adjacence qui sera simple à coder dans un
programme Python. Une matrice est un tableau à double entrée :
En Python on code une matrice d’adjacence sous la forme d’une liste de listes. Chaque sous-liste représente une
colonne de la matrice d’adjacence.
Remarques :
• Une matrice d’adjacence est qualifiée de matrice carrée car elle comporte toujours le même nombre de lignes
et de colonnes.
• Dans le cadre d’un graphe non orienté de type « est ami avec », les éléments de la matrice d’adjacence
associée sont symétrique par rapport à la grande diagonale des 0.
Q14. Ecrire la matrice d’adjacence correspondant au graphe non orienté décrivant la situation de la question Q9.
Q15. Compléter la matrice d’adjacence et écrire son codage Python correspondant au graphe non orienté suivant
traduisant la relation « est ami avec »
Dans le modèle du mini-réseau social précédent, le graphe est non orienté : il traduit seulement le fait qu’un utilisateur
est ami avec un autre (relation bijective). La notion de « followers » que l’on rencontre dans de nombreux réseaux
sociaux (Twitter en est un exemple), nécessite quant à elle d’orienter les arêtes du graphe (elles deviennent alors des
arcs) afin de traduire la relation « X » suit « Y ».
Ainsi dans l’exemple de graphe orienté ci-dessous, Alice (origine) suit Zoé et Chloé (extrémités).
Q17. Comment peut-on obtenir facilement le nombre de personnes suivies par une personne donnée ?
Q18. Comment peut-on obtenir facilement le nombre de personnes qui suivent une personne donnée ?
Q19. Compléter le programme Python de la question Q16 afin qu’il affiche en plus du nombre d’amis pour une
personne donnée, le nombre de personnes qu’elle suit et le nombre de personnes qui la suivent.
5.1. Modélisation d’un réseau d’hyperliens à l’aide d’un graphe et d’une liste d’adjacence
On peut modéliser un réseau de pages web reliées entre elles par des hyperliens à l’aide d’un graphe orienté.
Dans l’exemple ci-dessous, la page web possède 2 liens sortants (1 vers la page et 1 vers la page ) et
2 liens entrants (1 depuis la page et 1 depuis la page ).
G = [[0,2],[1,0],[2,1,3],[3,2]]
Ecriture simplifiée :
H = [[2],[0],[1,3],[2]]
Modélisation d’un réseau de pages web par un
graphe orienté Pages : 0 1 2 3 (indices)
Q20. Modifier le programme précédent pour calculer la fréquence des visites de chaque page lors d’un parcours
aléatoire comportant nbEtapes :
5.2. Pour aller plus loin : qu’est-ce-ce que le « Page Rank » (PR) ?
Le PageRank est l’algorithme d’analyse des liens hypertextes utilisé pour le classement des pages Web par le moteur
de recherche Google. En première approximation un « page rank » simplifié se rapproche des scores de fréquentation
de chaque page web calculés précédemment à l’aide des listes d’adjacence. Le PageRank n’est qu’un indicateur parmi
d’autres dans l’algorithme qui permet de classer les pages du Web dans les résultats de recherche. Ce système a été
inventé par Larry Page, cofondateur de Google. Ce mot est une marque déposée.
◼ La toute première « formule magique » de calcul du PR par Google (la toute dernière est secrète) :
Dans tout ce qui suit, on supposera que ce graphe est connexe, c'est-à-dire qu'à partir d'une page donnée, toute
autre page est accessible par une série de liens (une chaîne).
L’objectif est de mettre en œuvre technique permettant de tester toutes les pages de ce site, c'est-à-dire à
parcourir ce graphe en passant par tous ses sommets.
◼ Comment ça marche ?
Pour le parcours en largeur (BFS pour Breadth First Search), on commence avec un nœud donné et on explore chacun
de ses voisins avant d'explorer leurs enfants. Autrement dit, on commence d'abord par aller sur la plus grande largeur
possible. Son implémentation repose sur une file (queue) dont le principe du premier entré, premier sorti (FIFO : First
In / First Out) permet de s'assurer que les nœuds découverts d'abord seront explorés en premier :
d
Illustration d’un parcours en profondeur sur un arbre distance = 0
Exploration
distance = 1
distance = 2
distance = 3
Remarque : on donne le nom de parcours en largeur d'un graphe car cet algorithme va visiter tous les sommets à
distance 1 du sommet de départ puis tous les sommets à distance 2 du sommet de départ puis tous les sommets à
distance 3 du sommet de départ etc...
http://math.univ-lyon1.fr/irem/Formation_ISN/formation_parcours_graphes/largeur/2_description1.html
https://youtu.be/NrQGxfFMYzs
On va procéder à un parcours en largeur du graphe en mettant les sommets successifs dans une file (structure
FIFO). Voici la description intuitive de l'algorithme :
En d'autres termes, on défile toujours prioritairement les sommets (les pages) les plus tôt découverts.
◼ Programmation en Python :
Nous avons vu précédemment la notion de graphe et le fait que tout graphe pouvait être caractérisé par sa matrice
d'adjacence composée de 1 et de 0 selon que deux sommets sont ou ne sont pas reliés par une arête.
Une nouvelle façon d'encoder un graphe sous Python est d'utiliser un dictionnaire qui sera la représentation de sa
matrice d'adjacence. La clé associée à chaque sommet sera la liste des sommets adjacents :
Q24. Montrer, en partant du point B, que la méthode de parcours en largeur (BFS) du graphe précédent se ramène à
un arbre dont le sommet est b. On représentera en pointillés les arêtes entre deux sommets déjà explorés.
• Un dictionnaire P tel que, en fin de parcours, pour tout sommet S du graphe, P[S] sera le père de S, c'est-à-
dire le sommet à partir duquel le sommet S a été découvert lors du parcours.
• Un dictionnaire couleur[] tel que, pour tout sommet S, couleur[S] soit blanc si le sommet S n'est pas passé
dans la file, gris si le sommet S est dans la file et noir si le sommet S est sorti de la file.
• Une liste Q utilisée comme file (FIFO) : on enfile un sommet lorsqu'il est découvert et on le défile lorsqu'il est
terminé (traitement prioritaire des sommets découverts au plus tôt).
http://math.univ-lyon1.fr/irem/Formation_ISN/formation_parcours_graphes/largeur/3_python1.html
Q25. Assurez-vous que le résultat obtenu est conforme à celui obtenu à la main à la question Q24.
Q26. Tester le parcours du graphe en largeur depuis différents sommets et comparer le résultat obtenu avec la
méthode manuelle.
Il est également possible de coder le programme en faisant intervenir la notion de classe comme dans le programme
ci-après.
◼ Comment ça marche ?
Pour le parcours en profondeur (DFS pour Depth First Search), on commence avec un nœud donné et on explore
chaque branche complètement avant de passer à la suivante. Autrement dit, on commence d'abord par aller le plus
profond possible. Cet algorithme s'écrit naturellement de manière récursive et permet de trouver tous les nœuds
auquel un nœud est connecté.
◼ Principe de l’algorithme :
Ressources à consulter :
http://math.univ-lyon1.fr/irem/Formation_ISN/formation_parcours_graphes/profondeur/2_description2.html
https://youtu.be/kcedjJOjDpg
On va procéder à un parcours en profondeur du graphe en mettant les sommets successifs dans une pile (structure
LIFO). Voici la description intuitive de l'algorithme :
En d'autres termes, on traite toujours en priorité les liens des pages les plus tard découvertes.
Q27. Montrer, en partant du point g, que la méthode de parcours en profondeur (DFS) du graphe précédent se ramène
à un arbre dont le sommet est g. On représentera en pointillés les arêtes entre deux sommets déjà explorés.
Avec la représentation du graphe sous la forme d’un dictionnaire comme dans le paragraphe précédent, la
programmation du parcours en profondeur (DFS) se réalisera avec une fonction dfs(graphe,
sommet_de_depart) dont les variables seront les suivantes :
• Un dictionnaire P tel que, en fin de parcours, pour tout sommet S du graphe, P[S] sera le père de S, c'est-à-
dire le sommet à partir duquel le sommet S a été découvert lors que parcours.
• Un dictionnaire couleur[] tel que, pour tout sommet S, couleur[S] soit blanc si le sommet S n'est pas passé
dans la file, gris si le sommet S est dans la file et noir si le sommet S est sorti de la file.
• Une liste Q utilisée comme pile (LIFO).
Q28. Tester le parcours du graphe en largeur depuis différents sommets et comparer le résultat obtenu avec la
méthode manuelle.
Il est également possible de coder le programme en faisant intervenir la notion de classe comme dans le programme
ci-après.
Un cycle est un sous graphe dont les sommets peuvent être organisés en une séquence fermée.
◼ Graphe orienté :
3 4 5
2
5 Cycle
2
5
3 3 4 5
Cycle
Q29. Représenter en Python chacun des graphes précédents par leur matrice d’adjacence. Ces graphes serviront à
tester les programmes des paragraphes suivants.
L'idée est très simple, pour vérifier s'il existe un cycle, il suffit de vérifier s'il existe un chemin partant d'un sommet
disons "v" et revenant à ce sommet pour tous les sommets. S'il existe un tel chemin, nous disons que le graphe
contient un cycle. Maintenant, la question est de savoir comment vérifier un tel chemin ? Là encore réponse à cette
question est très simple, exécutez une traversée à partir d'un sommet "v" si nous revenons à ce sommet, nous
retournons True sinon retournons False.
La fonction suivante vérifie s'il existe un chemin partant d'un sommet « u » vers un sommet « v » :
La fonction ci-dessus est la fonction principale, maintenant pour vérifier s'il existe un cycle, il suffit de vérifier tous les
sommets s'il existe un chemin partant de ce sommet et y retournant :
Q30. A l’aide du programme ci-dessus vérifier que le graphe suivant est acyclique :
2
5
3
Pour un graphe non orienté, nous appliquons simplement le parcours en largeur pour détecter un cycle. L'idée est
d'utiliser un tableau pour mémoriser le parent de chaque sommet (De quel sommet nous avons découvert chaque
sommet). En découvrant les sommets, on vérifie si on retourne au sommet déjà visité et que ce sommet n'est pas le
parent du sommet courant, si c'est le cas, alors il existe un cycle.
Q31. A l’aide du programme ci-dessus vérifier que le graphe suivant est acyclique :
2
5
3
L'utilisation de la parcours en profondeur est un peu plus compliquée, car l'algorithme est récursif, et comme vous le
savez, derrière chaque algorithme récursif, il existe une pile utilisée pour les appels récursifs. Parce que pendant
l'exécution de l'algorithme, nous n'avons pas accès à cette pile, nous allons devoir créer une pile et suivre les sommets
déjà dans la pile. Comment cette pile est utile pour détecter un cycle ? Pour bien comprendre ce qui se passe, on
peut étudier l’exemple ci-dessous traitant de la fonction « factorielle de n » qui illustre la façon dont les appels récursifs
sont empilés vers la pile et dépilés :
Comme on peut l’observer sur l’exemple précédent, pendant les appels récursifs, on ajoute les appels récursifs à la pile, puis
une fois que l’on arrive au cas de base, on commence à supprimer les appels récursifs de la pile.
Revenons maintenant à la question de savoir comment il est utile de savoir quel élément est déjà dans la pile
Lors de l'exécution de l'algorithme, si nous revenons à un élément « i » (Visités[i] = True) qui existe déjà dans la pile
(Pile[i] = True), cela signifie qu'il existe un cycle. Une fois qu’un sommet est découvert, on le rend comme visité
(Visités[i] = True) et on ajoute également à la pile (Pile[i] = True). Une fois que l’on a fini de découvrir tous ses
sommets adjacents, on le supprime de la pile (Pile[i] = False)
Q32. A l’aide du programme ci-dessus vérifier que le graphe suivant est acyclique :
2
5
3
La même idée générale peut être appliquée pour le graphe orienté, mais ici on doit également vérifier lorsque l’on
revient à un sommet, que ce sommet n'est pas le parent du sommet de l'appelant.
Q33. A l’aide du programme ci-dessus vérifier que le graphe suivant est acyclique :
2
5
3