ycleEnRISLANCOIRfsystemutrintrintln
Délégués
.5@percent
- 
Deux délégués pour IF 431
 - Délégués PC/TD
 
 
Plan
.5@percent
- 
Arbres de recouvrement
 - Détection de cycles
 - Tri topologique 
 - Composantes connexes 
 - Bi-connexité et points d'articulation
 
 
Rappel sur les listes
aluivant
 
  int val; Liste suivant;      Listes d'entiers
  Liste (int x, Liste s) { val = x; suivant = s; }
} 
class X {
  static Liste listeVide = null;
  public static void main (String[ ] args) {
    Liste troisElements =
           new Liste(8, new Liste(2, new Liste(29, null))); 
    troisElements.suivant.val = 11;
    System.out.println (troisElements.suivant.suivant.val);
    troisElements = new Liste (9, troisElements.suivant);
} }


Parcours en profondeur (1/2)
 
static numOrdre;
static void visiter (Graphe g) {
  int n = g.succ.length; int[ ] couleur = new int[n]; numOrdre = -1;
  for (int x = 0; x < n; ++x)  num[x] = -1;
  for (int x = 0; x < n; ++x)  couleur[x] = BLANC;
  for (int x = 0; x < n; ++x)
    if (couleur[x] == BLANC)
      dfs(g, x, couleur, num);
}
static void dfs (Graphe g, int x, int[ ] couleur, int[ ] num) {
  couleur[x] = GRIS;
  num[x] = ++numOrdre;
  Pour tout y successeur de x dans G
  faire {
    if (couleur[y] == BLANC)
      dfs(g, y, couleur, num);
  }
  couleur[x] = NOIR;
}
Parcours en profondeur (2/2)
 
static numOrdre;
static void visiter (Graphe g) {
  int n = g.succ.length; int[ ] couleur = new int[n]; numOrdre = -1;
  for (int x = 0; x < n; ++x)  num[x] = -1;
  for (int x = 0; x < n; ++x)  couleur[x] = BLANC;
  for (int x = 0; x < n; ++x)
    if (couleur[x] == BLANC)
      dfs(g, x, couleur, num);
}
static void dfs (Graphe g, int x, int[ ] couleur, int[ ] num) {
  couleur[x] = GRIS;
  num[x] = ++numOrdre;
  for (Liste ls = g.succ[x]; ls != null; ls = ls.suivant) {
    int y = ls.val;
    if (couleur[y] == BLANC)
      dfs(g, y, couleur, num);
  }
  couleur[x] = NOIR;
}
Sortie de labyrinthe
On cherche un chemin de x à s. 
hemin
 
  couleur[x] = GRIS;
  if (x == s)
    return new Liste (x, null);
  for (Liste ls = g.succ[x]; ls != null; ls = ls.suivant) {
    int y = ls.val;
    if (couleur[y] == BLANC) {
      Liste r = chemin (g, y, s);
      if (r != null)
        return new Liste (x, r);
    }
  }
  return null;
}
- 
chemin retourne la liste des sommets sur le
chemin trouvé vers la sortie s.
 - Þ Ne pas oublier que la fonction 
dfs peut
retourner un résultat.
 
| Complexité en temps en O(V+E) | 
Arbres de recouvrement (1/4)
4 sortes d'arcs par rapport aux branches d'un arbre de
recouvrement:
- 
d'un noeud de l'arbre à un fils dans l'arbre
 - d'un noeud de l'arbre à un ancètre dans l'arbre
 - d'un noeud de l'arbre à un descendant non
 direct dans l'arbre
 - d'un noeud à un cousin dans l'arbre (forêt)
 
Arbres de recouvrement (2/4)
Mais, dans un graphe non-orienté, deux sortes d'arcs dans
dfs:
Rappel: Dans notre représentation des graphes non-orientés,
un arc coexiste avec son arc inverse; donc les cas 1-2, 3-2
coexistent.
| Graphe non-orienté Þ pas d'arcs de type 4
º pas d'arcs transverses. | 
Arbres de recouvrement (3/4)
d(x) est le temps où x devient GRIS dans dfs
f(x) est le temps où x devient NOIR dans dfs
Proposition
 1 [Intervalles parenthésés] 
Soient s et t deux sommets. Alors 3 cas seulement:
- 
les intervalles [d(s), f(s)] et [d(t), f(t)] sont disjoints et
s et t sont cousins dans l'arbre de recouvrement;
 - l'intervalle [d(s), f(s)] contient [d(t), f(t)] et
s admet t comme descendant dans l'arbre de recouvrement;
 - l'intervalle [d(s), f(s)] est contenu dans [d(t), f(t)] et
s admet t comme ancêtre dans l'arbre de recouvrement;
 
Proposition
 2 [Caractérisation par numéros] 
Soit e un arc de s à t. Alors:
- 
e est de type 1 ou 3 ssi num[s] < num[t].
 - e est de type 2 ou 4 ssi num[s] > num[t].
 
Arbres de recouvrement (4/4)
Soit d(e) le temps où on considère l'arc e pour la première fois
dans dfs.
Proposition
 3 [Caractérisation par couleurs] 
Soit e un arc de s à t. Alors:
- 
e est de type 1 ssi couleur[t] = 
BLANC au temps d(e).
 - e est de type 2 ssi couleur[t] = 
GRIS au temps d(e).
 - e est de type 3 ou 4 ssi couleur[t] = 
NOIR au temps d(e).
 
Un chemin blanc est un chemin dont tous les sommets
intermédiaires sont BLANCs.
Proposition
 4[Chemins blancs] 
Le sommet t est un descendant de s dans un arbre de recouvrement
ssi il existe un chemin blanc de s GRIS vers t
BLANC lors de l'exploration de s par le dfs correspondant.
Détection de cycles (1/3)
  | 
 | 
  | 
|   | 
 | 
| graphe avec cycle | 
 | 
arbre de recouvrement | 
Cycle ss'il existe un arc de type 2
pendant la construction de l'arbre de recouvrement.
Détection de cycles (2/3)
Définition
 5 Le point d'attache d'un cycle est le premier
sommet du cycle visité par dfs.
Þ Le point d'attache est le sommet s du cycle dont
num[s] est minimal.
Proposition
 6 
Le graphe est cyclique ss'il existe un arc e vers un sommet
GRIS au temps d(e).
Démonstration- 
S'il existe un arc e de s à t 
GRIS au temps
 d(e), alors e est un arc de type 2. Il existe un cyle de t
 passant par s et revenant à t.
 - S'il existe un cycle de point d'attache t, au début de
 l'exploration du cycle le sommet t est 
GRIS, les
 autres sont BLANCs dans le cycle. Il existe un
 chemin blanc de t au prédécesseur t' de t dans le cycle. Donc
 t' est un descendant de t dans l'arbre de recouvrement. Et il existe
 un arc e de t' à t GRIS au temps d(e). 
Détection de cycles (3/3)
 
final static int BLANC = 0, GRIS = 1, NOIR = 2;
static boolean acyclique (Graphe g) {
  int n = g.succ.length; int[ ] couleur = new int[n];
  for (int x=0; x < n; ++x) couleur[x] = BLANC;
  for (int x=0; x < n; ++x)
    if ( couleur[x] == BLANC && cycleEn(g, x, couleur) )
      return false;
  return true;
}
static boolean cycleEn(Graphe g, int x, int[ ] couleur) {
  couleur[x] = GRIS;
  for (Liste ls = g.succ[x]; ls != null; ls = ls.suivant) {
    int y = ls.val;
    if ( couleur[y] == GRIS
      || couleur[y] == BLANC && cycleEn(g, y, couleur) )
      return true;
  }
  couleur[x] = NOIR;
  return false;
}
Tri topologique (1/4)
G=(V,E) est un graphe acyclique (dag: directed acyclic graph)
Donner une liste des sommets á vi | i=1..nñ dans un
ordre compatible avec l'ordre induit par G: vi < vj
Û vj ® vi.
(trouver un ordre total compatible avec un ordre
partiel donné)
Tri topologique (2/4)
  | 
| Ordre de lecture | 
 
| des chapîtres | 
 
| du livre de | 
 
| H. P. Barendregt | 
 
| The l-calculus: | 
 
| its syntax and
semantics | 
 
| North-Holland, 1980 | 
   | 
Autre exemple: trouver l'ordre d'exécution
des tâches d'un projet à partir du graphe de dépendances
(ordonnancement).
Tri topologique (3/4)
Si le sommet terminal est connu, un parcours en profondeur suffit.
 
final static int BLANC = 0, GRIS = 1, NOIR = 2;
static Liste aFaire (Graphe g, int x) {
  int n = g.succ.length;
  int[ ] couleur = new int[n];
  for (int x = 0; x < n; ++x)  couleur[x] = BLANC;
  return dfs(g, x, null, couleur);
}
static Liste dfs(Graphe g, int x, Liste r, int[ ] couleur) {
  couleur[x] = GRIS;
  for (Liste ls = g.succ[x]; ls != null; ls = ls.suivant) {
    int y = ls.val;
    if (couleur[y] == BLANC)
      r = dfs(g, y, r, couleur);
  }
  return new Liste (x, r);
}
r est un accumulateur pour le résultat º liste des sommets
à faire.
Tri topologique (4/4)
Pour trouver les sommets terminaux (dont aucun autre ne dépend),
un parcours linéaire des arcs suffit. 
erminal
 
static boolean[ ] calculerTerminaux (Graphe g) {
  int n = g.succ.length;
  int[ ] terminal = new boolean[n];
  for (int x = 0; x < n; ++x) terminal[x] = true;
  for (int x = 0; x < n; ++x)
    for (Liste ls = g.succ[x]; ls != null; ls = ls.suivant) {
      int y = ls.val;
      terminal[y] = false;
    }
  return terminal;
}
Autre algorithme [Knuth] sur le graphe inverse.
Connexité
- 
Existe-t'il un chemin entre deux sommets?
(dfs; cf. sortie de labyrinthe). Complexité O(V+E).
 - Trouver l'ensemble des sommets accessibles depuis un
 sommet. (dfs). Complexité O(V+E).
 - Trouver les ensembles de sommets interconnectés dans un graphe
 non-orienté. Composantes connexes.
(cf. plus loin;
 dfs) Complexité O(V+E).
 - Même problème dans un graphe orienté? Composantes
 fortement connexes. (cf. plus loin; dfs)
 Complexité O(V+E).
 
Composantes connexes (1/2)
Le graphe est non-orienté.
Composantes connexes (2/2)
mprimerComposanteDe
 
static void imprimerCompConnexes (Graphe g) {
  int n = g.succ.length;
  int[ ] couleur = new int[n];
  for (int x = 0; x < n; ++x) couleur[x] = BLANC;
  for (int x = 0; x < n; ++x) {
    if (couleur[y] == BLANC) {
      imprimerComposanteDe(g, x, couleur);
      System.out.println();
} } }
static void imprimerComposanteDe(Graphe g, int x, int[ ] couleur) {
  couleur[x] = GRIS;
  System.out.print (x + " ");
  for (Liste ls = g.succ[x]; ls != null; ls = ls.suivant) {
    int y = ls.val;
    if (couleur[y] == BLANC)
      imprimerComposanteDe(g, y, couleur);
} }
Exercice 1 Ecrire une fonction qui rend la liste des composantes connexes.
Bi-connexité (1/8)
- 
Dans un graphe non-orienté, existe-t-il 2 chemins différents entre
toute paire de noeuds? 
 - Un point d'articulation est un point qui sépare le graphe
en parties non connexes si on l'enlève.
 - Un graphe connexe sans point d'articulation est bi-connexe.
 - Exemples: les rues de Paris, la distribution
d'électricité, la topologie d'un réseau informatique, etc. Il vaut
mieux que ces graphes soient bi-connexes.
 
Bi-connexité (2/8)
Le point d'attache de x est le sommet t avec le plus petit
num[t] tel qu'un chemin relie x à t avec au 
plus un seul arc de retour.
Bi-connexité (3/8)
Définition
 7 Un circuit est un chemin 
formé par les arcs e1, e2, ...en (n ³ 0) tels que:
- 
ext(ei) = org(ei+1) pour 1 £ i < n
 - ext(en) = org(e1)
 
Définition
 8 Un circuit élémentaire est un chemin dont tous les sommets sont distincts (à l'exception du premier et du dernier). 
Proposition
 9 
Le point d'attache de x est le point d'attache t de numéro
num[t] minimum parmi tous les circuits élémentaires
passant par x.
Bi-connexité (4/8)
Vue de l'arbre de recouvrement
Un point d'articulation x a un descendant direct y dont le point
d'attache t vérifie 
num[x] ³  num[t]. Par
exemple: x Î {3, 10, 2}
ttachettache1
Bi-connexité (5/8)
Calcul (en dfs) du numéro de parcours du point
d'attache de x.
 
static int attache(Graphe g, int x) {
  int min = num[x] = ++numOrdre;
  for (Liste ls = g.succ[x]; ls != null; ls = ls.suivant) {
    int y = ls.val; int m;
    if (num[y] == -1) {
      m = attache(g, y);
      if (m >= num[x])
        articulation[x] = true;
    } else
      m = num[y];
    min = Math.min (min, m);
  }
  return min;
}
2244
Bi-connexité (6/8)
Avec le critère précédent, tout sommet à la racine de l'arbre de
recouvrement serait un point d'articulation.
Correction: le sommet à la racine de l'arbre de
recouvrement est un point d'articulation ssi ce sommet a plus qu'un
fils dans l'arbre.
 
static int numOrdre; static int[ ] num;
static boolean[ ] articulation;
static void trouverArticulations (Graphe g) {
  int n = g.succ.length; num = new int[n]; numOrdre = -1;
  articulation = new boolean[n];
  for (int x = 0; x < n; ++x) { num[x] = -1; articulation[x] = false; }
  for (int x = 0; x < n; ++x)
    if (num[x] == -1) {
      attache1(g, x);
    }
}
Bi-connexité (7/8)
 
static void attache1(Graphe g, int x) {
  num[x] = ++numOrdre;
  int nfils = 0;
  for (Liste ls = g.succ[x]; ls != null; ls = ls.suivant) {
    int y = ls.val;
    if (num[y] == -1) {
      ++nfils;
      attache(g, y);
    }
  }
  articulation[x] = nfils > 1;
}
On peut déplier cette fonction (inlining) dans trouverArticulations.
Bi-connexité (8/8)
 
static void trouverArticulations (Graphe g) {
  int n = g.succ.length;
  numOrdre = -1; num = new int[n];
  articulation = new boolean[n];
  for (int x = 0; x < n; ++x) { num[x] = -1; articulation[x] = false; }
  for (int x = 0; x < n; ++x)
    if (num[x] == -1) {
      num[x] = ++numOrdre;
      int nfils = 0;
      for (Liste ls = g.succ[x]; ls != null; ls = ls.suivant) {
        int y = ls.val;
        if (num[y] == -1) {
          ++nfils;
          attache(g, y);
        }
      }
      articulation[x] = nfils > 1;
    }
}
Exercices
Exercice 2 Ecrire trouverArticulations sans variables
globales.
Exercice 3 Ecrire un programme qui teste la bi-connexité d'un graphe.
Exercice 4 (difficile) 
Ecrire un programme qui imprime les composantes bi-connexes.
Exercice 5 Définir la notion de composante tri-connexe.
Exercice 6 La commande make du système Unix fait une analyse de
dépendances pour reconstruire un projet informatique. Réfléchir
à sa programmation. Certaines versions permettent la reconstruction
parallèle où plusieurs commandes peuvent s'exécuter en parallèle sur
p processeurs. Comment adapter l'analyse de dépendances?
This document was translated from LATEX by
HEVEA.