Le métro

 

Le programme suivant cherche le chemin le plus court pour aller d'une station de métro à une autre. Il fonctionne avec le métro parisien, mais il peut être bien évidemment être utilisé avec n'importe quel autre réseau.

Dans un premier temps, nous allons appliquer cet algorithme à un réseau très simple, avec quelques stations. C'est le programme qui suit. Le programme réel, avec toutes les stations du métro et les optimisations, est à télécharger.

Nous allons d'abord définir le type "arc", représentant le chemin entre deux stations; il contient le numéro de la station de départ, le numéro de la station d'arrivée et un indicateur qui nous dit si un arc a déjà été emprunté (afin d'éviter les boucles) :

 

arc = record
  depart, arrivee: integer;
  parcouru: boolean;
end;
 

Nous allons ensuite entrer les arcs correspondants :

 

procedure init_arcs;
begin
  t[1].depart := 1; t[1].arrivee := 2; t[1].parcouru := false;
  t[2].depart := 2; t[2].arrivee := 1; t[2].parcouru := false;
  t[3].depart := 2; t[3].arrivee := 3; t[3].parcouru := false;
  t[4].depart := 2; t[4].arrivee := 6; t[4].parcouru := false;
  t[5].depart := 2; t[5].arrivee := 11; t[5].parcouru := false;
  t[6].depart := 3; t[6].arrivee := 2; t[6].parcouru := false;
  t[7].depart := 3; t[7].arrivee := 4; t[7].parcouru := false;
  t[8].depart := 4; t[8].arrivee := 3; t[8].parcouru := false;
  t[9].depart := 6; t[9].arrivee := 4; t[9].parcouru := false;
  t[10].depart := 4; t[10].arrivee := 7; t[10].parcouru := false;
  t[11].depart := 4; t[11].arrivee := 9; t[11].parcouru := false;
  t[12].depart := 4; t[12].arrivee := 10; t[12].parcouru := false;
  t[13].depart := 5; t[13].arrivee := 6; t[13].parcouru := false;
  t[14].depart := 6; t[14].arrivee := 2; t[14].parcouru := false;
  t[15].depart := 6; t[15].arrivee := 4; t[15].parcouru := false;
  t[16].depart := 6; t[16].arrivee := 5; t[16].parcouru := false;
  t[17].depart := 6; t[17].arrivee := 9; t[17].parcouru := false;
  t[18].depart := 7; t[18].arrivee := 4; t[18].parcouru := false;
  t[19].depart := 8; t[19].arrivee := 9; t[19].parcouru := false;
  t[20].depart := 9; t[20].arrivee := 4; t[20].parcouru := false;
  t[21].depart := 9; t[21].arrivee := 6; t[21].parcouru := false;
  t[22].depart := 9; t[22].arrivee := 8; t[22].parcouru := false;
  t[23].depart := 10; t[23].arrivee := 4; t[23].parcouru := false;
  t[24].depart := 11; t[24].arrivee := 2; t[24].parcouru := false;
  min_stations := 10000; // initialisé à plus l'infini
end;
 

On doit faire ensuite une procédure qui pour un station donnée cherche toutes les stations qui sont à une distance de 1 de celle-ci. Ainsi, dans le dessin précédent, les stations situées à une distance de 1 de la station numéro 4 sont les suivantes: 3,6,9,7 et 10.

 

procedure trouve_partantes(depart: integer);
var i: integer;
begin
  nb_partantes := 0;
  for i := 1 to 24 do
  begin
    if t[i].depart = depart then
    begin
      inc(nb_partantes);
      partantes[nb_partantes] := i; // valeur de "i" utilisée dans "trouver"
    end;
  end;
end;
 

Ensuite, pendant le parcours du graphe (afin de chercher le chemin le plus court) si on a emprunté un arc, on doit le marquer; de cette façon, on empêche le programme de boucler. Inversement, si on a pris un chemin et qu'il s'est révélé infructueux, on doit faire demi-tour (en réalité on monte d'un niveau dans la pile de la procédure récursive) et marquer l'arc comme n'étant pas emprunté. Ce qui nous donne :

 

procedure marquer_arc(depart, arrivee: integer; marque: boolean);
var i: integer;
begin
  for i := 1 to 23 do
  begin
    if (t[i].depart = depart) and (t[i].arrivee = arrivee) then
      t[i].parcouru := marque;
    if (t[i].depart = arrivee) and (t[i].arrivee = depart) then
      t[i].parcouru := marque;
  end;
end;
 

Il nous reste maintenant à faire la fonction de recherche du plus court chemin entre deux stations. Cette fonction a les paramètres suivants :

- départ et arrivée, indiquant les numéros de stations

- nb_stations: variable indiquant (à un niveau n de la récursivité) le nombre de stations parcourues.

- liste: une chaîne de caractères, dans laquelle on mémorise les numéros des stations.

La programmation de cette fonction se fait de la manière suivante :

- pour chaque chemin trouvé, on mémorise le nombre de stations dans une variable, appelée "min_stations". De cette façon, on est sûr que la longueur des chemins trouvés va aller en diminuant.

- si on n'a pas trouvé le chemin (c'est à dire "départ<>arrivée") alors pour la station en cours (c'est à dire "départ") on mémorise toutes les stations partantes, et on les essaye une par une. Grâce à la récursivité, pour chacune de ces stations partantes on mémorise leur propres stations partantes et on les essaie (descente récursive et exploration de tous les chemins).

Il faut donc marquer l'arc emprunté, essayer la première station et si elle a échoué alors effacer l'arc emprunté et prendre la deuxième station; et ainsi de suite.

 

function trouver(depart, arrivee, nb_stations: integer; liste: string): boolean;
var i: integer;
  partantes1: tp;
  nb_partantes1: integer;
begin
  if depart = arrivee then
  begin
    trouver := true;
    if nb_stations <= min_stations then
    begin
      min_stations := nb_stations;
      form1.memo1.lines.add('Nouveau plus petit chemin :');
      form1.memo1.lines.add(inttostr(nb_stations) + ' stations.');
      form1.memo1.lines.add(liste);
      form1.memo1.lines.add('=============================');
    end;
  end
  else
  begin
    trouve_partantes(depart);
    move(partantes, partantes1, sizeof(tp));
    nb_partantes1 := nb_partantes;
    for i := 1 to nb_partantes1 do
      if t[partantes1[i]].parcouru = false then
      begin
        marquer_arc(depart, t[partantes1[i]].arrivee, true);
        trouver := trouver(t[partantes1[i]].arrivee, arrivee,
          nb_stations + 1, liste + ',' + inttostr(t[partantes1[i]].arrivee));
        marquer_arc(t[partantes1[i]].arrivee, depart, false);
      end;
  end;
end;
 

On n'a plus qu'à appeler la fonction ci-dessus :

 

procedure TForm1.Button1Click(Sender: TObject);
begin
  init_arcs;
  trouver(5, // station départ
    7, // station arrivée
    0, // stations parcourues
    '5'); // chemin parcouru
end;
 

Passons maintenant dans le cas réel, c'est à dire environ 300 stations de métro.

Là, prendre tous les chemins peut s'avérer extrêmement long. C'est pour cette raison qu'on introduit un nombre maximal de changements autorisés.

L'algorithme prend les chemins dans l'ordre de leur introduction en mémoire. Si on lui laisse un nombre maximal de 5 changements par exemple, alors le nombre de chemins possibles sera beaucoup plus grand que si on lui impose seulement 2 changements. C'est donc à vous de choisir et de faire des essais, pour déterminer le trajet et le temps de trajet qui vous convient le mieux.

Le programme fonctionnel des stations de métro est à télécharger.

On peut ensuite modifier l'algorithme de recherche en ne lui donnant que les stations qui se trouvent à un croisement, de cette façon la recherche sera encore plus rapide. Actuellement le programme met de 1 seconde à quelques minutes pour trouver un chemin optimal, mais l’affichage des solutions est progressif.

 

Télécharger le code source Delphi