Graphes et algorithmes - TP 1 - CORRIGE

Michel Couprie, Gilles Bertrand


1. Préliminaires

1.2 Premiers algorithmes

1.2.1 Calcul du symétrique d'un graphe

Editez le fichier exo121.c, lisez-le, compilez et exécutez. Analyser la fonction Sym. Pourquoi n'est-elle pas efficace ?

Les deux boucles "for" imbriquées parcourent tous les arcs possibles du graphe complet sur n sommets, même s'ils ne sont pas présents dans notre graphe. De plus, le test EstSuccesseur(g, i, j) demande, au pire, un parcours complet de la liste des successeurs du sommet i.

Quelle est la complexité de calcul de l'algorithme ?

O(n(n+m))

1.2.2 Mesure du temps de calcul

Editez le fichier exo122.c, lisez-le, compilez et exécutez. Notez les temps de calculs obtenus pour diverses tailles de graphes.

Exemple:

couprie@linux:~/Graphes > ./exo122.exe 10 25
temps de traitement : 1.6e-05 secondes
couprie@linux:~/Graphes > ./exo122.exe 100 250
temps de traitement : 0.000847 secondes
couprie@linux:~/Graphes > ./exo122.exe 1000 2500
temps de traitement : 0.083336 secondes
couprie@linux:~/Graphes > ./exo122.exe 10000 25000
temps de traitement : 8.03387 secondes
couprie@linux:~/Graphes > ./exo122.exe 100000 250000
temps de traitement : 803.758 secondes  

1.2.3 Algorithme linéaire pour le calcul du symétrique

Dans le fichier exo122.c, modifiez la fonction Sym pour rendre l'algorithme linéaire.

/* ====================================================================== */
graphe * Symetrique(graphe * g)
/* ====================================================================== */
{
  graphe *g_1;
  int nsom, narc, al_arcs, k, i, j;
  pcell p;

  nsom = g->nsom;
  narc = g->narc;
  g_1 = InitGraphe(nsom, narc);
  for (i = 0; i < nsom; i++) /* pour tout i sommet de g */
  {
    for (p = g->gamma[i]; p != NULL; p = p->next)
    { /* pour tout j successeur de i */
      j = p->som;
      AjouteArc(g_1, j, i);
    }
  }
  return g_1;
} /* Symetrique() */

Refaire les mesures de temps de calcul avec la nouvelle version, et comparer.

couprie@linux:~/Graphes > ./exo123.exe 10 25
temps de traitement : 8e-06 secondes
couprie@linux:~/Graphes > ./exo123.exe 100 250
temps de traitement : 4.8e-05 secondes
couprie@linux:~/Graphes > ./exo123.exe 1000 2500
temps de traitement : 0.000395 secondes
couprie@linux:~/Graphes > ./exo123.exe 10000 25000
temps de traitement : 0.006711 secondes
couprie@linux:~/Graphes > ./exo123.exe 100000 250000
temps de traitement : 0.112156 secondes           
couprie@linux:~/Graphes > ./exo123.exe 1000000 2500000
temps de traitement : 1.26103 secondes 


2. Friends

Formulez ce problème en termes de graphe et donnez une solution pour ce cas particulier.

On trace le graphe des "inimitiés" (à gauche):

    

Le problème a une solution si ce graphe est biparti (voir ci-dessous). On trouve facilement ici une solution, qui consiste en un bi-coloriage du graphe (à droite). Cet exemple provient du site suivant : http://campus.northpark.edu/wicksBook/GraphTheory/Bipartite/index.html.


3. Graphes bipartis

3.1 Algorithme

Proposer un algorithme, linéaire en temps de calcul, qui indique si un graphe G donné est biparti ou pas, et si oui, qui indique un bi-coloriage de G.

Algo Biparti
  Donnees: G = (E, gamma), G_1 = symetrique de G, n = |E|
  Resultats: biparti (booleen), couleur (tableau de taille n)
  // le graphe est suppose connexe
  // on commence le coloriage au sommet 0
  C := {0}; T1 := {0}; T2 := {}; 
  coul := 0; couleur[0] := coul;
  Tant que T1 non vide faire
    coul := (coul + 1) modulo 2
    Tant que il existe un sommet i dans T1 faire
      T1 := T1 \ {i}
      Pour tout s successeur de i dans G ou dans G_1
        Si s n'est pas dans C alors
          C := C u {s}
          T2 := T2 u {s}
          couleur[s] := coul
        Sinon
          Si couleur[s] != coul alors biparti := false; RETURN
        fin Sinon
      fin Pour
    fin Tant que
    T1 := T2
    T2 := {}
  fin Tant que
  biparti := true 
fin Algo

3.2 Implémentation

Evidemment ce n'est qu'une solution parmi d'autres !

/* ====================================================================== */
/*! \fn boolean Biparti(graphe * g, graphe *g_1)
    \param   g (entrée) : un graphe.
    \param g_1 (entrée) : le graphe symétrique de g.
    \return TRUE si le graphe g est biparti, FALSE sinon
    \brief vérifie si le graphe g est biparti, si oui on peut récupérer la 
           bipartition dans g->v_sommets, sous la forme de deux "couleurs" 0 et 1.
*/
boolean Biparti(graphe * g, graphe *g_1)
/* ====================================================================== */
{
  Lifo * T1;   /* liste temporaire geree en pile (Last In, First Out) */
  Lifo * T2;   /* liste temporaire geree en pile (Last In, First Out) */
  Lifo * T;    /* pour l'echange */
  int i, n, s;
  pcell p;
  boolean * C;  /* pour eviter de reparcourir un sommet plusieurs fois */
  int couleur;

  n = g->nsom;
  T1 = CreeLifoVide(n);
  T2 = CreeLifoVide(n);
  C = EnsembleVide(n);

  C[0] = TRUE;
  LifoPush(T1, 0);  /* on part d'un sommet arbitraire (0 en l'occurence) */
  couleur = 0;
  g->v_sommets[0] = (double)couleur;
  while (!LifoVide(T1)) /* boucle jusqu'a stabilite */
  {
    couleur++;
    couleur = couleur % 2;
    while (!LifoVide(T1)) /* boucle pour le parcours d'un niveau */
    {
      i = LifoPop(T1);
      for (p = g->gamma[i]; p != NULL; p = p->next)
      { /* pour tout s successeur de i */
        s = p->som;
        if (!C[s]) 
        {
          C[s] = TRUE;
          LifoPush(T2, s);
          g->v_sommets[s] = (double)couleur;
        }
        else
          if (g->v_sommets[s] != (double)couleur) return FALSE;
      }
      for (p = g_1->gamma[i]; p != NULL; p = p->next)
      { /* pour tout s successeur de i dans g_1 */
        s = p->som;
        if (!C[s]) 
        {
          C[s] = TRUE;
          LifoPush(T2, s);
          g->v_sommets[s] = (double)couleur;
        }
        else
          if (g->v_sommets[s] != (double)couleur) return FALSE;
      }
    } // fin du niveau
    T = T2;
    T2 = T1;
    T1 = T;
  }

  LifoTermine(T1);
  LifoTermine(T2);
  free(C);
  return TRUE;
} /* Biparti() */

3.3 Propriété

Montrer qu'un graphe est biparti si et seulement si il ne contient pas de cycle de longueur impaire (à faire hors TP).

3.4 Détection de cycles impairs


/* ====================================================================== */
/*! \fn boolean Biparti(graphe * g, graphe *g_1)
    \param   g (entrée) : un graphe.
    \param g_1 (entrée) : le graphe symétrique de g.
    \return TRUE si le graphe g est biparti, FALSE sinon
    \brief vérifie si le graphe g est biparti, si oui on peut récupérer la 
           bipartition dans g->v_sommets, sous la forme de deux "couleurs" 0 et 1,
           sinon les valeurs dans g->v_sommets indiquent un cycle de longueur impaire.
*/
boolean Biparti(graphe * g, graphe *g_1)
/* ====================================================================== */
{
  Lifo * T1;   /* liste temporaire geree en pile (Last In, First Out) */
  Lifo * T2;   /* liste temporaire geree en pile (Last In, First Out) */
  Lifo * T;    /* pour l'echange */
  int i, n, s, t, k;
  pcell p;
  boolean * C;  /* pour eviter de reparcourir un sommet plusieurs fois */
  int couleur;
  int * back; /* le "fil d'Arianne" (pointeurs arriere) */

  n = g->nsom;
  T1 = CreeLifoVide(n);
  T2 = CreeLifoVide(n);
  C = EnsembleVide(n);
  back = (int *)malloc(n * sizeof(int));

  C[0] = TRUE;
  LifoPush(T1, 0);  /* on part d'un sommet arbitraire (0 en l'occurence) */
  couleur = 0;
  g->v_sommets[0] = (double)couleur;
  while (!LifoVide(T1)) /* boucle jusqu'a stabilite */
  {
    couleur++;
    couleur = couleur % 2;
    while (!LifoVide(T1)) /* boucle pour le parcours d'un niveau */
    {
      i = LifoPop(T1);
      for (p = g->gamma[i]; p != NULL; p = p->next)
      { /* pour tout s successeur de i */
        s = p->som;
        if (!C[s]) 
        {
          C[s] = TRUE;
          LifoPush(T2, s);
          g->v_sommets[s] = (double)couleur;
          back[s] = i;
        }
        else
          if (g->v_sommets[s] != (double)couleur) goto NONBIPARTI;
      }
      for (p = g_1->gamma[i]; p != NULL; p = p->next)
      { /* pour tout s successeur de i */
        s = p->som;
        if (!C[s]) 
        {
          C[s] = TRUE;
          LifoPush(T2, s);
          g->v_sommets[s] = (double)couleur;
          back[s] = i;
        }
        else
          if (g->v_sommets[s] != (double)couleur) goto NONBIPARTI;
      }
    } // fin du niveau
    T = T2;
    T2 = T1;
    T1 = T;
  }

  LifoTermine(T1);
  LifoTermine(T2);
  free(C);
  free(back);
  return TRUE;

NONBIPARTI:
  // re-initialisation de T1 et du champ v_sommets
  LifoFlush(T1);
  for (k = 0; k < n; k++) g->v_sommets[k] = (double)0;

  // remonte les pointeurs "back" en partant de s jusqu'au sommet 0
  // stocke la valeur -1 dans le champ v_sommets des sommets parcourus (sauf s)
  t = s;
  do
  { t = back[t];
    g->v_sommets[t] = (double)(-1);
  } while (t != 0);

  // remonte les pointeurs "back" en partant de i jusqu'a un sommet marque "-1"
  // stocke les sommets dans T1 (y compris le dernier)
  t = i;
  while (g->v_sommets[t] != (double)(-1)) 
  { LifoPush(T1, t);
    t = back[t];
  } // while (g->v_sommets[t] != (double)(-1)) 
  LifoPush(T1, t);

  // remonte les pointeurs "back" en partant de s jusqu'au sommet t (le dernier empile)
  // numerote les sommets
  k = 1;
  do
  { g->v_sommets[s] = (double)k;
    k++;
    s = back[s];
  } while (s != t);

  // retire les sommets de la lifo T1 
  // numerote les sommets
  while (!LifoVide(T1))
  {
    s = LifoPop(T1);
    g->v_sommets[s] = (double)k;
    k++;
  } // while (!LifoVide(T1))

  LifoTermine(T1);
  LifoTermine(T2);
  free(C);
  free(back);
  return FALSE;
} /* Biparti() */

3.5 Trouver des graphes bipartis

Celui qui en arrive là en trois heures ne devrait pas rencontrer de pb...


File translated from TEX by TTH, version 2.89.
On 9 Mar 2002, 17:13.