• Aucun résultat trouvé

Algorithme de Tarjan

B.3 R´ eduction ` a la forme normale

B.3.2 Algorithme de Tarjan

Il existe une tr`es ´etroite relation entre les s.c.c. et les coloriages d’un graphe puisque tous les ´el´ements d’une s.c.c. re¸coivent la mˆeme couleur. Le principe g´en´eral de l’algorithme de normalisation est d`es lors de reconnaˆıtre les s.c.c. du graphe, puis de leur attribuer une couleur. Cette recherche des s.c.c. peut ˆetre r´ealis´ee par l’algorithme de Tarjan [Tar72], d´ej`a ´evoqu´e `a la section 3.3.6 et dont nous allons pr´esenter une version simplifi´ee due `a [Nuu95]. L’expos´e restera tr`es intuitif. Le lecteur pourra ce r´ef´erer `a cette th`ese pour une preuve formelle.

Dans sa version classique, l’algorithme de Tarjan attribue un mˆeme num´ero unique `a tous les nœuds situ´es dans une mˆeme s.c.c. :

120.1 hVariables globales 120.1i

sccnum : array[TNoeudID] of integer;

Code utilis´e dans le fragment 118.

Tant qu’on a pas trouv´e la s.c.c. dans laquelle un nœud v doit se trouver, on aura par convention sccnum[v]= -1.

Recherche en profondeur d’abord

L’algorithme de Tarjan repose sur une recherche en profondeur d’abord classique. Cette recherche est appliqu´ee successivement `a tous les nœuds qui n’ont pas encore ´

et´e trait´es :

120.2 hAlgorithme de Tarjan 120.2i

procedure tarjan(var g : TGraph); varhVariables de Tarjan 121.1i

procedure visit(v : TNoeudID); varhVariables de Visit 121.4i

begin

hPr´etraitement d’un nœud 121.5i

h ´Etape r´ecursive121.6i

hD´etection d’une nouvelle s.c.c. 122.6i

end; begin

hInitialiser la recherche 121.2i

hLancer l’exploration 121.3i

end;

B.3. Minimisation d’automates faibles — R´eduction `a la forme normale 121 Nous avons besoin comme variables d’un pointeur qui nous permet de passer en revue tous les nœuds du graphe, d’une structure de donn´ees nous permettant de m´emoriser les nœuds d´ej`a examin´es et d’un compteur pour num´eroter les s.c.c. :

121.1 hVariables de Tarjan 121.1i

node : TNoeudID; seen : TNoeudSet; scccount : integer;

Voir aussi dans les fragments 121–23 et 125.2. Code utilis´e dans le fragment 120.2.

Nous pouvons maintenant pr´eciser le fonctionnement global de l’algorithme :

121.2 hInitialiser la recherche 121.2i

newset(seen); scccount:= 0;

for node:= 0 to g.n-1 do sccnum[node]:= -1;

Voir aussi dans les fragments 122.3, 123.2, et 125.3.

Code utilis´e dans le fragment 120.2.

121.3 hLancer l’exploration 121.3i

for node:= 0 to g.n-1 do

if not appartient(node, seen) then visit(node);

Code utilis´e dans le fragment 120.2.

121.4 hVariables de Visit 121.4i

w, succ : TNoeudID; i : integer;

Voir aussi dans les fragments 127.1 et 128.2. Code utilis´e dans le fragment 120.2.

121.5 hPr´etraitement d’un nœud 121.5i

insert(v, seen);

Voir aussi dans les fragments 122 et 123.3. Code utilis´e dans le fragment 120.2.

121.6 h ´Etape r´ecursive121.6i

for i:= 0 to g.s[v].nbsucc-1 do begin

succ:= g.s[v].succ[i];

if not appartient(succ, seen) then visit(succ);

hMise `a jour d’informations122.5i

end;

Code utilis´e dans le fragment 120.2.

Racine d’une composante fortement connexe

Il est possible de prouver que tous les nœuds appartenant `a une mˆeme s.c.c. pos- s`edent n´ecessairement un ancˆetre commun u dans le processus de recherche en pro- fondeur, u appartenant `a cette mˆeme s.c.c. [Tar72]. Pour une s.c.c. donn´ee, le plus vieil ancˆetre dans cette composante est appel´e (( racine )) de la s.c.c. Elle est unique. L’algorithme de Tarjan se r´eduit ainsi `a la recherche des racines des s.c.c. Pour ce faire, on cr´ee une variable root qui retient une racine candidate pour chaque nœud en cours de visite :

121.7 hVariables de Tarjan 121.1i+

root : array[TNoeudID] of TNoeudID;

B.3. Minimisation d’automates faibles — R´eduction `a la forme normale 122 Initialement, le nœud lui-mˆeme est la racine candidate. Cette approximation va ˆ

etre affin´ee au cours de l’algorithme :

122.1 hPr´etraitement d’un nœud 121.5i+

root[v]:= v;

Code utilis´e dans le fragment 120.2.

Pour appliquer le crit`ere, il faut pouvoir d´eterminer si un nœud est ancˆetre d’un autre dans le processus d’exploration. C’est pourquoi il faut m´emoriser l’ordre dans lequel les nœuds sont rencontr´es au cours de la recherche en profondeur :

122.2 hVariables de Tarjan 121.1i+

dfsnum : array[TNoeudID] of TNoeudID; dfscount : integer;

Code utilis´e dans le fragment 120.2.

122.3 hInitialiser la recherche 121.2i+

dfscount:= 0;

Code utilis´e dans le fragment 120.2.

122.4 hPr´etraitement d’un nœud 121.5i+

dfsnum[v]:= dfscount; dfscount:= dfscount+1;

Code utilis´e dans le fragment 120.2.

Quand un successeur succ de v a ´et´e trait´e, il est possible que de nouvelles racines candidates aient ´et´e d´ecouvertes. La d´efinition d’une racine nous apprend qu’une nouvelle racine pour v a ´et´e d´ecouverte si et seulement si la racine candidate de succ est devenue un ancˆetre de la racine candidate de v. Le principe de la r´ecursivit´e exige de propager cette d´ecouverte au nœud v, `a condition que succ n’ait pas entre-temps ´

et´e plac´e dans une s.c.c. (auquel cas v et succ ne figurent pas dans la mˆeme s.c.c.) :

122.5 hMise `a jour d’informations 122.5i

if sccnum[succ]= -1 then

if dfsnum[root[succ]] < dfsnum[root[v]] then root[v]:= root[succ];

Code utilis´e dans le fragment 121.6.

Cr´eation d’une nouvelle composante

Si au terme du traitement du nœud v, la racine candidate se confond avec v, nous avons d´etect´e une racine pour la s.c.c. `a laquelle v appartient :

122.6 hD´etection d’une nouvelle s.c.c. 122.6i

if root[v]=v then begin

hNouvelle s.c.c. de racine v d´etect´ee123.4i

end;

B.3. Minimisation d’automates faibles — R´eduction `a la forme normale 123 Quand une nouvelle racine v a ´et´e d´etect´ee, il faut pouvoir retrouver tous les nœuds qui appartiennent `a la s.c.c. dont v est la racine. Pour mener `a bien cette recherche, il faut se rappeler que l’on effectue une recherche en profondeur d’abord. De ce fait, les composantes fortement connexes les plus internes sont d´etect´ees en premier lieu dans le processus d’exploration. Ainsi, quand une s.c.c. est d´ecouverte, ses ´el´ements sont ceux qui ont ´et´e atteints en dernier lieu dans l’exploration sans avoir ´et´e affect´es pr´ealablement `a une s.c.c.

De ce fait, en parall`ele avec l’exploration des nœuds, l’algorithme de Tarjan m´emorise sur une pile auxiliaire les nœuds qui n’ont pas encore ´et´e affect´es `a une s.c.c. et ce, dans l’ordre dans lequel ils sont rencontr´es :

123.1 hVariables de Tarjan 121.1i+

stack : TStack;

Code utilis´e dans le fragment 120.2.

123.2 hInitialiser la recherche 121.2i+

newstack(stack);

Code utilis´e dans le fragment 120.2.

123.3 hPr´etraitement d’un nœud 121.5i+

push(v, stack);

Code utilis´e dans le fragment 120.2.

Quand une s.c.c. est d´ecouverte, les nœuds qui la composent sont au sommet de la pile. On n’a donc plus qu’`a les retirer de celle-ci jusqu’`a rencontrer le nœud v :

123.4 hNouvelle s.c.c. de racine v d´etect´ee 123.4i

hInitialiser des informations additionnelles 127.2i

repeat

w:= pop(stack); sccnum[w]:= scccount;

hRecueil d’informations additionnelles127.3i

until w=v;

scccount:= scccount+1;

hTraitement additionnel sur la s.c.c. 126.2i Code utilis´e dans le fragment 122.6.

Les fragments correspondant au traitement additionnel sur la s.c.c. sont introduits pour l’algorithme de normalisation. La complexit´e globale de l’algorithme de Tarjan estO(max(|V |,|E|)), o`u |V | est le nombre de nœuds du graphe et |E| est le nombre de transitions dans le graphe.

B.3. Minimisation d’automates faibles — R´eduction `a la forme normale 124 Structures de donn´ees annexes

Nous d´efinissons ici toutes les structures de donn´ees qui ont ´et´e utilis´ees :

124.1 hStructures de donn´ees 119.3i+

TNoeudSet = array[TNoeudID] of boolean; TStack =

record

top : integer;

pile : array[TNoeudID] of TNoeudID; end;

Code utilis´e dans le fragment 118.

124.2 hRoutines annexes 124.2i

procedure newset(var s : TNoeudSet); var i : integer;

begin

for i:= 0 to Nnoeuds-1 do s[i]:= false

end;

function appartient(n : TNoeudID ; s : TNoeudSet) : boolean;

begin

appartient:= s[n] end;

procedure insert(n : TNoeudID ; var s : TNoeudSet);

begin

s[n]:= true end;

procedure newstack(var s : TStack); begin

s.top:= 0 end;

procedure push(n : TNoeudID ; var s : TStack);

begin

s.pile[s.top]:= n; s.top:= s.top +1 end;

function pop(var s : TStack) : TNoeudID; begin

s.top:= s.top-1; pop:= s.pile[s.top] end;

function isempty(s : TStack) : boolean; begin

isempty:= s.top=0 end;

Code utilis´e dans le fragment 118.