TCA 101, Informatique

Initiation à l’informatique

Responsable et cours :  Cyril Gavoille & Robert Strandh

Planning :

6 séances de cours 6 séances de TD 6 séances de TP + environ 3h de travail individuel par semaine

Web : http ://dept-info.labri.u-bordeaux.fr/tca101/

Support de cours

Polycopié :  Initiation à l’Informatique

par Robert Strandh et Irène Durand

Transparents

Objectif et contenu

Objectif : Donner une idée fidèle du contenu des

études supérieures en informatique

Thème :  Étude d’un objet appelé graphe
Contenu : Théorie des graphes (cours)

Algorithmique des graphes (TD)

Programmation des algorithmes de graphes (TP)

Il faut activer les comptes

Vous avez reçu un compte sur l’ensemble des ordinateurs de l’université.

Il faut suivre les instructions pour activer le compte au moins 24 heures (de préférence plus) avant le premier TP.

C’est quoi l’informatique ?

– dans la vie quotidienne : ordinateur avec logiciels

– à l’université : une discipline universitaire L’informatique n’est pas une science (expérimentale). L’informatique est similaire aux mathématiques (étude d’objets abstraits). Les objets en mathématiques : nombres, relations, fonction, transformations, etc. Les objets en informatique : algorithmes, programmes, preuves, systèmes de réécriture, images numériques, graphes, etc.

Domaines en informatique fondamentale

Exemples de domaines :

– Algorithmique. Les méthodes les plus efficaces pour traiter un problème donné.

– Structures de données. La meilleure façon d’organiser un ensemble de données dans le but d’y accéder rapidement.

– Complexité. Une façon d’exprimer l’efficacité d’un algorithme, indépendamment d’un ordinateur ou d’un langage de programmation particulier.

Domaines de l’informatique

fondamentale (suite)

Exemples de domaines plus théoriques :

– Théorie des langages. Différentes façons de produire et reconnaître des suites de symboles.

– Calculabilité. Déterminer pour quels problèmes il est théoriquement possible/impossible d’écrire un programme.

– Logique. La puissance d’expression de différents types de logique.

Domaines de l’informatique pratique

Exemples de domaines :

– Programmation. Techniques pour organiser un programme de façon qu’il soit facilement modifiable.

– Génie Logiciel. Méthodes pour organiser le développement d’un logiciel de grande taille.

– Informatique multimédia. Méthodes d’analyse, modification et synthèse d’images et de sons.

– Systèmes d’exploitation. Techniques pour réaliser un système qui assure intégrité, sécurité et performance.

– Compilation. Techniques pour traduire un programme en code machine efficace.

Pourquoi étudier l’informatique

Deux sous-questions :

– pourquoi choisir une carrière en informatique ?

– pourquoi étudier l’informatique alors qu’on a choisi une carrière différente (physique, chimie, mathématique, etc.) ?

Pourquoi une carrière en informatique ?

Raisons techniques :

– garantie d’embauche,

– les produits de haute technologie contiennent de plus en plus de logiciels,

– la complexité des logiciels augmente,

– la compétition est de plus en plus sophistiquée. Raisons non techniques :

– contacts (souvent internationaux),

– voyages,

– mobilité (même internationale).

Pourquoi l’informatique pour les non

informaticiens

– Le travail d’un scientifique ou d’un ingénieur nécessite de plus en plus la manipulation de logiciels,

– Ces logiciels sont de plus en plus sophistiqués,

– Souvent, ces logiciels nécessitent de la programmation,

– Pour programmer de manière efficace, il faut des connaissances en informatique (algorithmique, programmation).

– C’est surtout nécessaire pour produire des programmes maintenables.

Un mot sur l’importance de

l’algorithmique

Il est facile de se tromper d’algorithme.

Une telle erreur peut faire la différence entre plusieurs années et quelques minutes de calculs sur une même machine.

C’est souvent une question d’utilisation de structures de données ou d’algorithmes connus dans la littérature.

Un mot sur la programmation

Il ne suffit pas de construire un programme qui marche.

L’essence de la programmation est l’organisation pour faciliter la maintenance (qui représente environ 80% du coût d’un logiciel).

Cela nécessite la construction d’abstractions (sous-programmes, modules, classes, extensions syntaxiques, fonctions de première classes, etc.).

Plusieurs styles de programmation adaptés aux types différents de problèmes : programmation impérative, fonctionnelle, orientée-objets, logique. Chaque type a ses idiomes de programmation qu’il faut apprendre.

Prérequis pour études supérieures en

informatique

Prérequis :

– Il faut être bien organisé (ça s’apprend),

– Il faut avoir une curiosité intellectuelle, car l’informatique nécessite un apprentissage permanent,

Non prérequis :

– Connaissance préalable d’un langage ou d’un système d’exploitation,

– Connaissance de la programmation (c’est souvent un handicap),

– Connaissance de logiciels destinés au grand public.

Choix d’un langage de programmation

Paramètres (langage ou implémentation du langage) :

– facilité d’apprentissage, facilité d’utilisation,

– rapidité d’exécution, rapidité de compilation,

– absence de défauts dans le compilateur,

– pérennité (fabricant, langage, implémentation),

– disponibilité de programmeurs,

– expressivité du langage (structuration, styles),

– normalisation, conformité des implémentations.

Choix d’un langage pour l’enseignement

– facilité d’apprentissage (moins important dans l’industrie),

– utilité plus tard,

– facilité de programmer de façon propre et modulaire.

Nous avons choisi le langage Python.

Caractéristiques de Python

– implémentation libre et gratuite existe,

– orienté-objets,

– facilité de manipulation de listes,

– grand nombre de bibliothèques,

– efficacité moyenne du code,

– structure de bloc indiquée par l’indentation (unique pour Python).

Le Graphe

C’est une collection d’objets munie d’une relation binaire entre ces objets.

Une relation binaire est un ensemble de couples d’objets.

En mathématiques, la collection est souvent infinie et non dénombrable (les réels par exemple), alors qu’en informatique, elle est souvent dénombrable et parfois finie.

En informatique, les objets représentés sont souvent des objets plus concrets (molécules, composants électroniques, villes, réseaux de téléphones mobiles, personnes).

Exemple de graphe : repas de Noël

Ensemble : toutes les personnes assistant à un repas de Noël.

Relation : l’ensemble des couples de personnes (p_{1}, p_{2}) tels que p_{1} est un ancêtre de p_{2} (relation non symétrique).

Exemple de graphe : molécules

Ensemble : les atomes d’une molécule.

Relation : l’ensemble des couples d’atomes (a_{1}, a_{2}) tels que a_{1} et a_{2} partagent au moins un électron (liaison covalente, relation symétrique).

Exemple de graphe : réseau de

stations-relais

Ensemble : les stations-relais d’un réseau de communication de téléphones mobiles.

Relation : l’ensemble des couples de stations (s_{1}, s_{2}) tels qu’il existe un lien direct de communication (par microondes, câble, etc.) entre s_{1} et s_{2}.

Représentation graphique d’un graphe

Les cousins (relation symétrique, graphe non orienté) :

Représentation graphique d’un graphe

x → y = “x a pour parent y” (relation non symétrique, graphe orienté) :

Représentation graphique d’un graphe

Molécule (relation symétrique, graphe non orienté) :

Concepts et notation

Il s’agit de donner un nom et un façon d’écrire certaines notions fréquemment utilisées. Raison pour introduire des concepts et des notations :

– évite la répétition de phrases compliquées,

– précision ; on évite l’ambiguïté, Exemples connus : racine carrée.

Concepts et notations ensemblistes

Nous supposons que la notion d’ensemble est connue.

Par contre, en informatique, il faut souvent préciser la fonction de comparaison utilisée entre deux éléments de l’ensemble.

Exemple :

L’objet a est une Renault Clio immatriculée 1234AB33, l’objet b est une Renault Mégane immatriculée 1234AB24, et l’objet c une Renault Clio immatriculée 1234AB40. Est-ce que c est élément de l’ensemble {a, b}?

Élément d’un ensemble

Nous utilisons la notation x ∈ E pour dire que x estélément de l’ensemble E.

Cette notation ne précise pas le test d’égalité qu’il faut donc préciser séparément.

Sous-ensembles

Nous utilisons la notation E ⊆ F pour dire que E est un sous-ensemble de F, à savoir que tout élément de E est aussi élément de F. Attention, il est possible que E = F. Sinon on écrit E ⊂ F

Pour l’ensemble des sous-ensembles d’un ensemble E (appelé les parties de E), nous utilisons la notation P(E).

Fonctions

Une fonction est un objet mathématique qui, à un objet d’un ensemble fait correspondre un objet d’un autre ensemble. Exemple : y = sin α.

En informatique les ensembles sont plus souvent des personnes, des voitures, des graphes, des sommets ou des arêtes.

On dit qu’une fonction est appliquée à un ou plusieurs arguments et qu’elle renvoie (ou retourne) une valeur. Ceci reflète l’aspect exécutable d’une fonction.

Domaine et image d’une fonction

L’ensemble de tous les arguments possibles d’une fonction ϕ est le domaine de la fonction : dom(ϕ).

L’ensemble de toutes les valeurs possibles d’une fonction est l’image de la fonction : img(ϕ).

Notation : ϕ : dom(ϕ) ⟶ img(ϕ). Par exemple sin : ℝ ⟶ [−1, 1].

Conditions nécessaires et suffisantes

Condition nécessaire : “A est une condition nécessaire pour B” est la même chose que “B implique A” ou B ⇒ A. Ici B est l’objectif.

Une autre façon de le dire : “B seulement si A”

Condition suffisante : “A est une condition suffisante pour B” est la même chose que “A implique B” ou A ⇒ B. B est encore l’objectif.

Une autre façon de le dire : “B si A”

Condition nécessaire et suffisante : “B si et seulement si A”. B est l’objectif et souvent un concept à définir.

Raisonnement par l’absurde

Nous avons besoin de calculer la négation d’une phrase. La négation de “B seulement si A” est “non A mais B”. (le “mais” est le “et” logique) La négation de “B si A” est “A mais non B”. La négation de “∀a A” est “∃a ¬A.” La négation de “∃b B” est “∀b ¬B.”

Complexité

Comment savoir si une méthode est efficace ? C’est le problème du domaine de la complexité asymptotique, ou simplement la complexité.

Complexité (suite)

On suppose l’existence d’un ensemble d’opérations simples et rapides (ou opérations élémentaires).

Une opération est simple et rapide si un ordinateur peut l’exécuter avec un nombre faible d’instructions.

Exemple d’opérations élémentaires :

– additionner, soustraire, multiplier ou diviser deux nombres,

– tester si une valeur est égale à une autre valeur,

– affecter une valeur à une variable.

Pour déterminer si une méthode est efficace, on compte d’abord le nombre d’opérations nécessaire à effectuer dans le pire des cas et en fonction de la taille du problème.

Par exemple, pendant un pot, on souhaite que chaque participant serre la main à chaque autre participant.

L’opération élémentaire est “serrer la main”. La taille du problème est le nombre de participants.

Complexité (suite)

En général, pour n personnes, il faut n(n − 1)/2 opérations élémentaires. Une autre façon d’écrire n(n − 1)/2 est \frac{1}{2}n^{2} − \frac{1}{2}n.

Pour obtenir la complexité asymptotique, on remplace d’abord toute constante (de type \frac{1}{2} ou 55) par 1. Cela nous donne n^{2} − n.

Puis, on garde uniquement le terme le plus grand pour n grand. Cela donne n^{2}.

Finalement, on indique que ces approximations ont été effectuées en rajoutant O() comme ceci : O(n^{2}).

En réalité, on effectue les approximations avant de compter exactement.

Définition d’un graphe (1)

Une première tentative :

Un graphe est un couple (V, E), où V est un ensemble d’objets appelés les sommets du graphe (V pour l’anglais “vertex”), et E ⊆ V × V est une relation binaire sur V × V. Les éléments de E sont appelés les arêtes du graphe (E pour l’anglais “edge”).

Problème de la définition

Cette définition a plusieurs problèmes :

– On ne peut pas avoir deux arêtes différentes entre deux sommets (les arêtes n’ont pas d’identité propre),

– Un couple (s_{1}, s_{2}) n’est pas le même que (s_{2}, s_{1}).

En fait, la définition donne ce que l’on appelle un graphe orienté simple.

Ici simple signifie qu’il y a au plus une arête entre deux sommets.

Deuxième tentative :

Un graphe est un triplet (V, E, ϕ), où V est un ensemble d’objets appelés les sommets du graphe, et E est un ensemble d’objets appelés les arêtes du graphe et ϕ est une fonction ϕ : E ⟶ P(V) telle que ∀e ∈ E, |ϕ(e)| ∈ {1, 2}.

Interprétation de la définition

Ici, les arêtes sont des objets à part. La fonction ϕ prend comme argument une arête et renvoie un ensemble de sommets (les points extrêmes de l’arête). Pour forcer une arête à avoir un ou deux points extrêmes, il faut une restriction sur la taille de l’ensemble renvoyé. La façon d’exprimer cela est : ϕ : E ⟶ P(V) telle que ∀e ∈ E, |ϕ(e)| ∈ {1, 2}.

Le graphe en tant que type abstrait

Cette définition a des conséquences sur la programmation :

– à partir d’un objet de type graphe, récupérer l’ensemble des sommets du graphe,

– à partir d’un objet de type graphe, récupérer l’ensemble des arêtes du graphe,

– à partir d’une arête du graphe, récupérer le(s) sommet(s) extrémités de l’arête.

On appelle une collection d’opérations comme celle-ci un type abstrait.

La notion de type abstrait

C’est une notion centrale en programmation.

Cela permet de créer des programmes modulaires (i.e., contenant des parties relativement indépendantes) et donc maintenables.

Pour programmer une application, on se pose la question : “Quels sont les objets manipulés par le programme, et quelles sont les opérations sur ces objets ?”

La notion de type abstrait sera traitée en TD.

Un graphe est un triplet (V, E, ψ), où V est un ensemble d’objets appelés les sommets du graphe, E est un ensemble d’objets appelés les arêtes du graphe et ψ est une fonction ψ : V ⟶ P(E) telle que ∀e ∈ E, |{s ∈ V, e ∈ ψ(s)}| ∈ {1, 2}.

Interprétation de la définition

Elle génère les mêmes objets que la précédente. Le rôle de la fonction est totalement différente. Ici, elle est appliquée à un sommet et renvoie un ensemble d’arêtes.

Quel type abstrait est le bon ?

Ça dépend de ce que vous voulez en faire (de l’application). Certaines opérations sont plus rapides et/ou plus simples à programmer selon le type choisi. Souvent, il n’est pas possible d’avoir que des opérations simples et rapides. Il faut donc choisir.

Degré d’un sommet

Le degré d’un sommet s, noté d(s), est le nombre de brins d’arêtes ayant s comme extrémité. Une boucle compte deux fois.

Exemple de degré

Ici d(A) = 1, d(B) = 3, d(C) = 4, d(D) = 2, d(E) = 0, d(F) = 2.

Un premier théorème

Pour un graphe G ayant au moins un sommet, _{s∈V (G)}^{∑} d(s) = 2|E(G)|

Technique de preuve par induction

1. Vérifier que ∑_{s∈V} d(s) = 2|E| pour un graphe sans arête,

2. Supposer que ∑_{s∈V} d(s) = 2|E| pour n’importe quel graphe avec au plus k arêtes,

3. Prouver que si ∑_{s∈V} d(s) = 2|E| est vrai pour n’importe quel graphe avec au plus k arêtes, c’est aussi vrai pour n’importe quel graphe avec k + 1 arêtes.

La preuve du théorème (1/2)

Par induction sur le nombre d’arêtes dans le graphe.

– (cas de base) La propriété est trivialement vraie pour un graphe avec |E| = 0, car le degré de chaque sommet du graphe est 0.

– (hypothèse d’induction) On suppose que pour un graphe G avec au moins un sommet et au plus k arêtes, ∑_{s∈V (G)} d(s) = 2|E(G)|.

La preuve du théorème (2/2)

– (induction) Nous construisons un graphe G′ en rajoutant une arête à G. Les sommets extrémités de la nouvelle arête, disons s_{1} et s_{2} (il est possible que s_{1} et s_{2} soient le même sommet) vont avoir leur degré augmenté de 1 chacun. Il est donc vrai que ∑_{s∈V (G′)} d(s) = ∑_{s∈V (G)} d(s)+2. De plus, nous avons rajouté une arête dans G′. Par conséquent |E(G′)| = |E(G)| + 1. Donc, ∑_{s∈V (G′)} d(s) = ∑_{s∈V (G)} d(s)+2 = 2|E(G)|+2 = 2(|E(G)|+1) = 2|E(G′)|. Nous avons donc ∑_{s∈V (G′)} d(s) = 2|E(G′)|

Nous avons donc prouvé la propriété par récurrence.

La notion de chaîne

Il est souvent nécessaire de savoir si l’on peut aller d’un sommet à un autre en suivant des arêtes.

Exemple d’utilité : Est-ce possible de prendre le train pour aller de Bordeaux à Rome ? De Bordeaux à Oslo ? De Bordeaux à Reykjavik ?

La notion de chaîne exprime cette idée.

Définition de chaîne (1)

Première tentative : (attention : cette définition n’est pas bonne). Une chaîne dans un graphe est une suite C = s_{1}, s_{2}, …, s_{k} de sommets du graphe, telle que ∀i, 1 ⩽ i < k, il y a une arête entre s_{i} et s_{i+1} .

Problème de la définition

S’il y a plusieurs arêtes entre deux sommets, on ne sait pas par laquelle il faut passer. Exemple :

Définition de chaîne (2)

Deuxième tentative : (attention : cette définition n’est pas bonne). Une chaîne dans un graphe est une suite C = e_{1}, e_{2}, …, e_{k} d’arêtes du graphe, telle que ∀i, 1 ⩽ i < n, e_{i} et e_{i+1} partagent un sommet.

Problème de la définition

La suite a, b, c dans le graphe suivant sera considérée comme une chaîne :

De plus, on ne sait pas par quel sommet la chaîne commence.

Définition de chaîne (3)

(la bonne)

Une chaîne dans un graphe est une suite

C = s_{1}, e_{1}, s_{2}, e_{2}, …, s_{k}, e_{k}, s_{k+1}

de k + 1 sommets et k arêtes en alternance, telle que ∀i, 1 ⩽ i ⩽ k, les extrémités de e_{i} sont s_{i} et s_{i+1} . On dit alors que C est une chaîne entre s_{1} et s_{k+1} .

Existence d’une chaîne

Vérifier s’il existe une chaîne entre un sommet s et un sommet t n’est pas forcément simple.

Pour y arriver, nous allons utiliser une technique pour marquer et démarquer les sommets.

Mais il nous faut d’abord un peu de théorie.

Chaîne simple

Une chaîne C = s_{1}, e_{1}, s_{2}, e_{2}, …, s_{k}, e_{k}, s_{k+1} est simple si et seulement si ∀i, j ∈ [1, k], s_{i} = s_{j} ⇒ i = j et ∀i, j ∈ [2, k + 1], s_{i} = s_{j} ⇒ i = j

Autrement dit “un sommet figure au plus une fois dans la chaîne”. [sauf pour le sommet de début et de fin. S’ils sont les mêmes, il s’agit d’un cycle]

Théorème

Dans un graphe G, s’il existe une chaîne entre s ∈ V (G) et t ∈ V (G), alors il existe une chaîne simple entre s et t.

La preuve est constructive. On prend une chaîne non simple et on supprime les cycles.

Preuve

Soit C = s_{1}, e_{1}, s_{2}, e_{2}, …, s_{k}, e_{k}, s_{k+1} la chaîne. Si C est simple, le travail est terminé. Sinon, il y a deux sommets s_{i} et s_{j} tels que i, j ∈ [1, k], pour lesquels i ≠ j et s_{i} = s_{j} ou bien deux sommets s_{i} et s_{j} tels que i, j ∈ [2, k + 1], pour lesquels i ≠ j et s_{i} = s_{j} En supposant que i < j (sinon on renverse les rôles de i et j), on peut alors écrire : C = s_{1}, e_{1}, …, s_{i}, e_{i}, …, s_{j}, e_{j}, …, s_{k}, e_{k}, s_{k+1} .

Preuve (suite)

Construire C′ plus courte en supprimant de C la partie e_{i}, …, s_{j}. On obtient alors : C′ = s_{1}, e_{1}, … s_{i}, e_{j}, …, s_{k}, e_{k}, s_{k+1} . Mais comment pouvons-nousêtre sûr que C′ est une chaîne ? Chaque arête de la chaîne doit être entourée de ses deux extrémités. C’est le cas dans C′. Répéter tant que C′ n’est pas simple.

Voici la méthode (en TD, vous allez voir une méthode plus algorithmique et récursive) :

1. démarquer tous les sommets

2. marquer s

3. tant que t n’est pas marqué,

(a) chercher une arête dont un sommet extrémité est marqué et l’autre ne l’est pas

(b) si une telle arête n’existe pas, renvoyer la valeur “faux”

(c) sinon marquer l’extrémité non encore marquée

4. renvoyer la valeur “vrai”

Au pire, cet algorithme passe par chaque arête du graphe pour trouver la première arête dont un sommet extrémité est marqué et l’autre ne l’est pas, ce qui coûte |E|.

Ensuite, la deuxième arête est trouvée au pire en |E| − 1 étapes. Et ainsi de suite. L’algorithme s’arrête quand tous les sommets ont été marqué (il n’y a plus rien à faire !). Donc au pire, à l’étape |V |, tous les sommets ont été marqué.

La complexité de cet algorithme est donc au plus :

|V | ; ∑ ; i=0(|E| − i) = O(|V | · |E|) .

(En TD vous verrez un algorithme en O(|V |))

Connexité

La notion de connexité exprime la possibilité d’aller de n’importe quel sommet du graphe à n’importe quel autre sommet du graphe.

Plus formellement :

Un graphe G est connexe si et seulement si ∀s, t ∈ V (G), il existe une chaîne entre s et t.

Nous allonsétudier des méthodes efficaces pour déterminer si un graphe est connexe.

Approche simple

La première idée est toujours d’appliquer la définition. Donc, pour déterminer si un graphe est connexe, vérifier si pour chaque couple (s, t) de sommets, il existe une chaîne entre s et t. Il y a n^{2} tels couples, si n = |V |. Au total, la méthode simple a une complexité O(n^{3} · |E|) (voir O(n^{3}) si la version TD est utilisée). Nous allons montrer que c’est possible de le faire en O(n).

Différence entre O(n^{3}) et O(n)

Pour apprécier la différence entre O(n^{3}) et O(n), imaginons un ordinateur capable d’exécuter une instruction élémentaire en 10ns (raisonnable).

Imaginons aussi un graphe de 100 000 sommets (un réseau routier par exemple).

Exécuter n instructions élémentaires nécessite 1ms, soit 0, 001s.

Exécuter n^{3} instructions élémentaires nécessite 10^{7}s soit environ 4 mois.

Approche plus efficace

Il nous faut encore un théorème :

Un graphe est connexe si et seulement si à partir de n’importe quel sommet s du graphe, il existe une chaîne entre s et chacun des sommets du graphe.

Preuve (partie “si”)

La preuve est par contradiction.

Nous allons supposer qu’à partir de n’importe quel sommet s du graphe, il existe une chaîne entre s et chacun des sommets du graphe, mais, ∃s_{1}, s_{2} ∈ V (G), tels qu’il n’existe pas de chaîne entre s_{1} et s_{2}, puis montrer que cela nous donne un résultat absurde.

Il suffit pour cela de construire les chaînes C_{1} = s, …, s_{1} et C_{2} = s, …, s_{2}.

Puis, on regarde C_{1}C_{2} = s_{1}, …, s, …, s_{2} qui est une chaîne entre s_{1} et s_{2}.

Preuve (partie “seulement si”)

Il faut prouver que si un graphe est connexe, alors à partir de n’importe quel sommet s du graphe, il existe une chaîne entre s et chacun des sommets du graphe.

C’est une conséquence immédiate de la définition de connexité.

Un algorithme plus efficace

Pour déterminer si un graphe est connexe, on peut donc faire la chose suivante : Choisir un sommet s arbitraire dans le graphe. Vérifier si pour chaque sommet t du graphe, il existe une chaîne entre s et t. Il faut donc faire O(n) vérifications. Le coût de chaque vérification est O(n) (algorithme version TD). Au total, la complexité est O(n^{2}).

Différence entre O(n^{3}), O(n^{2}) et O(n)

Imaginons encore un ordinateur capable d’exécuter une instruction élémentaire en 10ns et un graphe de 100 000 sommets.

Exécuter n instructions élémentaires nécessite 1ms, soit 0, 001s.

Exécuter n^{2} instructions élémentaires nécessite 10^{2}s soit environ 2 minutes.

Exécuter n^{3} instructions élémentaires nécessite 10^{7}s soit environ 4 mois.

Encore une amélioration

On observe que l’algorithme parcourt la même chaîne plusieurs fois.

Dans le graphe suivant :

il va parcourir entre s_{0} et s_{1}, puis entre s_{0} et s_{2}, refaisant alors le parcours entre s_{0} et s_{1}, puis entre s_{0} et s_{3}, refaisant alors le parcours entre s_{0} et s_{2}, refaisant alors le parcours entre s_{0} et s_{1}, etc.

Méthode finale pour la connexité

Voici la méthode (en TD, vous allez voir une méthode plus algorithmique et récursive) :

1. démarquer tous les sommets

2. marquer un sommet arbitraire s

3. chercher une arête dont un sommet extrémité est marqué et l’autre ne l’est pas

4. tant qu’une telle arête existe :

(a) marquer l’extrémité non encore marquée

5. si tout les sommets sont marqués, renvoyer “vrai”

6. sinon renvoyer “faux”

Graphes eulériens

Les ponts de la ville de Königsberg au 18^{e} siècle (maintenant Kaliningrad en Russie) :

Promenade

Peut-on commencer une promenade sur une île ou une rive, terminer la promenade sur n’importe quelle autre (ou la même) île ou rive en passant exactement une fois sur chacun des ponts ?

C’est le mathématicien Euler qui en 1735 a trouvé la réponse (d’où le nom “eulérien”).

C’est un problème de graphe

Dans un graphe G, est-il possible de trouver une chaîne C = s_{1}, e_{1}, s_{2}, e_{2}, …, s_{m}, e_{m}, s_{m+1} telle que {e_{1}, e_{2}, …, e_{m}} = E(G) et |E(G)| = m.

Dessiner une enveloppe

Est-ce possible de dessiner cette enveloppe sans lever le crayon :

Définition de graphe eulérien

Un graphe G est eulérien si et seulement si il est possible de trouver une chaîne C = s_{1}, e_{1}, s_{2}, e_{2}, …, s_{m}, e_{m}, s_{m+1} telle que {s_{1}, s_{2}, …, s_{m}, s_{m+1} } = V (G), {e_{1}, e_{2}, …, e_{m}} = E(G) et |E(G)| = m.

Conditions nécessaires

Théorème : Un graphe eulérien est forcement connexe.

D’autres façons de dire la même chose :

– Tout graphe eulérien est connexe,

– Si un graphe est eulérien, alors il est connexe,

– G eulérien ⇒ G connexe,

– Si un graphe n’est pas connexe, alors il n’est pas eulérien,

– G est connexe est une condition nécessaire pour que G soit eulérien.

Preuve du théorème

Par contradiction. On suppose l’existence d’un graphe G eulérien non connexe.

Le graphe G est eulérien. Donc, il existe une chaîne C = s_{1}, e_{1}, s_{2}, e_{2}, …, s_{m}, e_{m}, s_{m+1} telle que {s_{1}, s_{2}, …, s_{m}, s_{m+1} } = V (G), {e_{1}, e_{2}, …, e_{m}} = E(G) et |E(G)| = m.

Le graphe n’est pas connexe. Donc, ∃A, B ∈ V (G), tels qu’il n’existe aucune de chaîne entre A et B.

Preuve (suite)

La chaîne C contient tous les sommets, y compris A et B. Disons que A = s_{i} et B = s_{j}. On peut donc écrire la chaîne C comme ceci : C = s_{1}, e_{1}, s_{2}, e_{2}, …, A, e_{i}, …, e_{j−1}, B, …, s_{m}, e_{m}, s_{m+1} . Examinons la sous-chaîne : C′ = A, e_{i}, …, e_{j−1}, B de C. C′ est bien une chaîne entre A et B. Nous avons une contradiction.

Parité des sommets

Définition : un sommet est pair si son degré est pair. Un sommet est impair si son degré est impair.

Encore une condition nécessaire

Théorème : Dans un graphe eulérien, le nombre de sommets impairs est forcement 0 ou 2.

Le graphe de la ville de Königsberg n’est donc pas eulérien. Pour ce qui concerne l’enveloppe, le théorème ne nous donne pas le droit de constater que son graphe est eulérien.

Preuve

Par contradiction : On suppose l’existence d’un graphe eulérien dont le nombre de sommets impairs n’est ni 0 ni 2.

Le graphe est eulérien. Donc, il existe une chaîne C = s_{1}, e_{1}, s_{2}, e_{2}, …, s_{m}, e_{m}, s_{m+1} telle que {s_{1}, s_{2}, …, s_{m}, s_{m+1} } = V (G), {e_{1}, e_{2}, …, e_{m}} = E(G) et |E(G)| = m.

Preuve (suite)

Examinons un sommet autre que s_{1} et s_{m+1} dans cette chaîne. Un tel sommet s_{i} est entouré dans C de e_{i−1} et e_{i} ce qui donne une contribution de 2 au degré de s_{i}. Comme toutes les arêtes de C sont différentes, le degré de s_{i} est donc pair.

Pour s_{1} et s_{m+1} il existe deux cas :

Premier cas : s_{1} ≠ s_{m+1} s_{1} et s_{m+1} sont impairs. Il y a donc 2 sommets impairs.

Deuxième cas : s_{1} = s_{m+1} s_{1} = s_{m+1} est pair. Il y a donc 0 sommets impairs.

La preuve nous donne plus

Cette preuve nous indique comment trouver une chaîne eulérienne dans un graphe ayant deux sommets impairs : il faut commencer dans un sommet impair et terminer dans l’autre sommet impair.

Conditions suffisantes

Théorème : Un graphe G est eulérien si et seulement si G est connexe et le nombre de sommets impairs de G est 0 ou 2.

Il nous reste à établir que si G est connexe et possède 0 ou 2 sommets impairs, alors G possède une chaîne passant une seule fois par chacune des arêtes de G.

Nous allons donner une preuve “constructive” de ce théorème en construisant cette chaîne, dîte eulérienne.

Notion de cycle

Un cycle est une chaîne C = s_{1}, e_{1}, s_{2}, …, s_{m}, e_{m}, s_{m+1} telle que |{e_{1}, …, e_{m}}| = m et s_{1} = s_{m+1} .

Remarque : Si s_{1}, e_{1}, s_{2}, …, s_{m}, e_{m}, s_{m+1} est un cycle, alors s_{2}, e_{2}, s_{3}, …, e_{m}, s_{1}, e_{2}, s_{2} est aussi un cycle.

Cas 1 : tous les degrés sont pairs

Algorithme 1 : construit un cycle C contenant une seule fois chaque arête de G, G étant un graphe connexe où tous les sommets sont pairs.

1. Choisir un sommet s_{1} arbitraire, et former C = s_{1}

2. Tant que le dernier sommet de C possède une arête e qui n’appartient pas à C, ajouter e et son sommet extremité à C. (À prouver : C est un cycle.)

3. Si toutes les arêtes de G sont dans C, alors retourner C. Sinon, soit s_{i} un sommet de C ayant une arête e qui n’appartient pas à C. Former C′ = s_{i}, e_{i}, s_{i+1} , …, e_{m}, s_{1}, e_{1}, …, e_{i−1}, s_{i}.

4. Poser C = C′, puis continuer en 2

Cas 2 : il existe 2 sommets impairs

Algorithme 2 : construit une chaîne C contenant une seule fois chaque arête de G, G étant un graphe connexe avec 2 sommets impairs, disons A et B.

1. Former e = {A, B}, et un nouveau graphe G′ tel que V (G′) = V (G), et E(G′) = E(G) ∪ {e}. (Tous les sommets de G′ sont pairs.)

2. Calculer le cycle C′ pour G′ par l’Algorithme 1. (C′ est un cycle eulérien pour G′.) On supppose que C′ contient la sous-chaîne A, e, B dans ce sens (sinon inverser le rôle de A et B).

3. Former C′′ = A, e, B, e_{i}, s_{i+1} , …, A.

4. Retourner C = B, e_{i}, s_{i+1} , …, A.

Variations sur les graphes

Les graphes traités sont pour l’instant non orientés, et relativement généraux.

La généralité implique parfois des problèmes de performance de certains algorithmes.

Certaines variations sur les graphes améliorent la situation.

Graphe simple

Un graphe simple est un graphe sans multi-arêtes (plusieurs arêtes différentes avec les mêmes extrémités).

Ce type de graphe peut être utilisé pour modéliser des relations entre les sommets (voir la première tentative de la définition de graphe).

Parfois, on exige l’absence de boucles pour que le graphe soit simple.

Graphe orienté

Un graphe orienté est un graphe dont les arêtes sont orientés, (avec un sommet d’origine et un sommet de destination).

Dans ce cas, le mot arc est utilisé à la place du mot arête.

Les graphes orientés sont très important en informatique, car plus faciles à implémenter que les graphes non orientés (grâce à des pointeurs).

Définition de graphe orienté

Un arc est un objet a avec un sommet origine (noté T (a) pour l’anglais tail) et un sommet destination (noté H(a) pour l’anglais head).

Un graphe orienté est un objet G = (V, A) avec un ensemble V de sommet et un ensemble A d’arcs.

On dessine les arcs d’un graphe orienté avec des flèches.

Utilités de graphes orientés

Pour modéliser un réseau routier, les routes sont parfois en sens unique.

Le graphe orienté est nécessaire à chaque fois que la relation n’est pas symétrique (exemples : x parent de y, x plus grand que y, un objet de type x peut contenir des objets de type y, etc.)

Chemin dans un graphe orienté

Les complications concernant la définition d’une chaîne dans un graphe non orienté disparaissent.

On parle alors de chemin plutôt que de chaîne.

Un chemin dans un graphe orienté est une suite P = a_{1}, a_{2}, …, a_{n} d’arcs tels que ∀i, 1 ⩽ i < n, H(a_{i}) = T (a_{i+1} ). C’est un chemin de T (a_{1}) à H(a_{n}).

Connexité dans un graphe orienté

La connexité se complique dans un graphe orienté. Pour un graphe orienté, on parle souvent de “connexité forte”. Un graphe orienté G est fortement connexe si et seulement si ∀s, t ∈ V (G) il existe un chemin de s à t.

Graphe orienté sans cycle

Certaines relations sont bien modélisées avec un graphe sans cycle.

Un cycle dans un graphe orienté est un chemin P = a_{1}, a_{2}, …, a_{n}, tel que T (a_{1}) = H(a_{n}).

Un cycle est simple si et seulement si un sommet du graphe est la destination d’au plus un arc du cycle.

Un graphe sans cycle (anglais Directed Acyclic Graph, ou DAG) est un graphe orienté dans lequel il n’y a pas de cycles.

Degré entrant et sortant

Pour un graphe orienté, la notion de degré se complique. On préfère alors parler de degré entrant et degré sortant.

Le degré entrant d_{i}(s) d’un sommet s est définie comme ceci : d_{i}(s) = |{a ∈ A(V), H(a) = s}|, à savoir le nombre d’arcs ayant s comme destination.

Le degré sortant d_{o}(s) d’un sommet s est définie comme ceci : d_{o}(s) = |{a ∈ A(V), T (a) = s}|, à savoir le nombre d’arcs ayant s comme origine.

Degré entrant et sortant

102

Relation entre degré et arcs

Théorème : Dans un graphe orienté G = (V, A), ∑_{s∈V (G)} d_{i}(s) = ∑_{s∈V (G)} d_{o}(s) = |A(G)|. Preuve : par induction sur le nombre d’arcs. Vérification : ∑_{s∈V (G)} d_{i}(s) + d_{o}(s) = 2|A(G)|.

Arbre

Un arbre orienté est un graphe orienté sans cycle avec un sommet s_{0} tel que d_{i}(s_{0}) = 0 et dont tous les autres sommets s_{i} respectent d_{i}(s_{i}) = 1.

Le sommet s_{0} est appelé la racine de l’arbre.

Pour indiquer qu’un graphe est un arbre, nous allons utiliser la lettre T à la place de la lettre G.

Dessin d’un arbre

Les arbres en informatique sont dessinés avec la racine en haut :

Relation entre sommets et arcs

Théorème : Dans un arbre orienté T = (V, A), |V (T)| = |A(T)| + 1.

Preuve : Nous savons que ∑_{s∈V (T)} d_{i}(s) = |A(T)|. Par définition d’arbre d_{i}(s_{0}) = 0 et d_{i}(s_{i}) = 1 pour tous les autres sommets. Par conséquent, ∑_{s∈V (T)} d_{i}(s) = |V (T)| − 1 et donc |A(T)| = |V (T)| − 1.

Taille d’un arbre

Étant donné que la relation entre le nombre d’arcs et le nombre de sommets dans l’arbre est si forte, pour avoir une idée de la taille de l’arbre, il suffit de compter le nombre de sommets.

Définition : la taille d’un arbre orienté T = (V, A) (notée |T |) est |V (T)|.

Niveau d’un sommet

Définition : le niveau d’un sommet s (noté L(s) pour l’anglais level) dans un arbre orienté est la longueur du chemin entre la racine de l’arbre et s.

Hauteur

Définition : La hauteur d’un arbre orienté T = (V, A), notée H(T) est le niveau maximum parmi les sommet de T.

La hauteur est importante, car elle nous donne une indication du temps nécessaire pour trouver un sommet à partir de la racine.

Arbre binaire

Un arbre binaire est un arbre orienté T tel que ∀s ∈ V (T), d_{o}(s) ⩽ 2.

Les arbres binaires ont une grande utilité en informatique, en particulier pour stocker de manière efficace des informations.

Le type abstrait

Le type abstrait correspondantà un arbre binaire ne permet pas l’accès direct à tous les sommets, mais seulement à la racine.

Deux opérations sont disponibles : left(T) (ou l(T)) et right(T) (ou r(T)) pour récupérer le fils gauche et le fils droit respectivement. Un arbre vide est représenté par NULL.

On ne peut pas savoir le nombre de sommets de l’arbre sans les compter.

Propriétés des arbres binaires

Théorème : Dans un arbre binaire T = (V, A), |T | < 2^{H(T)+1} .

Preuve

Par induction sur H(T).

Cas de base : H(T) = 0. Il y a exactement un sommet dans l’arbre. Donc |T | = |V (T)| = 1. Mais 2^{H(T)+1} = 2^{0+1} = 2.

Hypothèse d’induction : On suppose que |T | < 2^{H(T)+1} pour H(T) < k.

Induction : Il faut prouver que |T | < 2^{H(T)+1} pour H(T) = k. Le fils gauche l(T) et le fils droit r(T) sont des arbres tels que H(l(T)) < k et H(r(T)) < k. Par hypothèse d’induction, nous savons donc que |l(T)| < 2^{k−1+1} = 2^{k} et |r(T)| < 2^{k−1+1} = 2^{k}. Une autre façon de dire la même chose est que |l(T)| ⩽ 2^{k} − 1 et |r(T)| ⩽ 2^{k} − 1 Mais |T | = 1 + |l(T)| + |r(T)| ⩽ 1 + 2^{k} − 1 + 2^{k} − 1 = 2^{k+1} − 1 < 2^{k+1} .

Arbre binaire de recherche

Un arbre binaire est souvent utilisé pour stocker des informations.

Un sommet peut par exemple représenter une personne (nom, adresse, numéro de téléphone, etc).

Il faut alors définir une clé unique pour chaque personne, par exemple le numéro de sécurité sociale, son numéro de téléphone (France Télécom fait ça) ou son nom de login.

Exemple d’arbre binaire de recherche

115

Organisation de l’arbre

On note C(s) la clé d’un sommet s.

On organise les sommets dans un arbre binaire de recherche T de telle façon à ce que max_{s∈l(T)}C(s) < C(R(T)) < min_{s∈r(T)}C(s) où R(T) indique la racine de T.

Recherche d’un sommet

Pour chercher un sommet s ayant une clé c dans un arbre T, on peut alors utiliser l’algorithme suivant :

1. si T est l’arbre vide, renvoyer “non trouvé”

2. sinon si C(R(T)) = c, renvoyer R(T)

3. sinon si C(R(T)) < c, chercher dans l(T)

4. sinon chercher dans r(T)

Complexité de la recherche

Il n’y a pas de garantie concernant la forme de l’arbre. Si l’arbre a la structure suivante :

alors la complexité est O(|T |).

Arbres Équilibrés

Un arbre équilibré est un arbre binaire de recherche qui respecte la condition suivante :

k|T | > 2^{H(T)} pour k petit, par exemple k ⩽ 10.

Autrement dit, la taille de l’arbre est très grande par rapport à la hauteur.

Si l’arbre est équilibré, alors la complexité de la recherche est O(log n).

Différence entre O(n) et O(log n)

Si n = 10^{6}, alors log n = 20, soit 50000 fois plus rapide. Si n = 10^{9}, alors log n = 30, soit 30000000 fois plus rapide.

121