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.