Carrière & Préparation

Questions d'Entretien sur le DFS

Les questions sur le DFS ne portent pas sur le parcours. Elles portent sur les informations que le DFS rend disponibles et que le BFS n'offre pas : l'ordre de fin, le fait qu'un sommet soit encore sur la pile, et jusqu'où un sous-arbre peut remonter. Huit questions qui reviennent sans cesse, chacune avec la solution, la relance que pose ensuite le recruteur et l'erreur qui coûte l'offre.

17 Min de lecture Mis à jour : Septembre 2026 Niveau intermédiaire
Mohammed Islam Hadjoudj
Mohammed Islam Hadjoudj
Expert Operations Research Engineer

1. Ce qu'une question sur le DFS évalue vraiment

Les questions sur le DFS ne portent pas sur le parcours. N'importe quel candidat sait parcourir un graphe. Ce qu'on vérifie, c'est si vous connaissez les informations de suivi que le DFS rend disponibles et que le BFS n'offre pas : l'ordre dans lequel les sommets se terminent, le fait qu'un sommet soit encore sur la pile, et jusqu'où un sous-arbre peut remonter.

C'est tout le sujet. Détection de cycles, tri topologique, composantes fortement connexes, ponts et points d'articulation : tous se ramènent à un parcours plus un tableau supplémentaire. Si une question porte sur l'ordre, les dépendances, les cycles ou sur ce qui casse si l'on retire ceci, c'est une question de DFS. Si elle demande le moins de quelque chose, c'est une question de BFS.

Les huit ci-dessous sont celles qui reviennent, chacune avec le problème, la solution, la relance et l'erreur qui coûte l'offre. Chaque exemple détaillé a été exécuté par script.

2. Le modèle, récursif et itératif

Le DFS récursif tient en quatre lignes, et vous devez pouvoir l'écrire sans réfléchir.

def dfs(u, adj, seen):
    seen.add(u)
    for v in adj[u]:
        if v not in seen:
            dfs(v, adj, seen)

C'est sur la version itérative que les candidats trébuchent, car la traduction évidente diffère subtilement de la version récursive.

def dfs_iter(src, adj):
    seen, stack = set(), [src]
    while stack:
        u = stack.pop()
        if u in seen:            # un sommet peut être empilé plusieurs fois
            continue
        seen.add(u)
        for v in adj[u]:
            if v not in seen:
                stack.append(v)

Deux points à remarquer, et à dire à voix haute.

Le point le plus délicat : la version itérative simple n'a pas de post-ordre. Elle sait quand un sommet est découvert, jamais quand son sous-arbre se termine, et le temps de fin est exactement ce dont ont besoin les sections 5, 9 et 10. Pour le retrouver, empilez chaque sommet deux fois ou gardez un indice d'enfant dans le cadre. Savoir que la récursion n'est pas qu'une question de forme ici mérite d'être dit.

La technique est ancienne : c'est la règle de Trémaux pour parcourir un labyrinthe, consignée par Lucas en 1882.

3. Détection de cycles dans un graphe orienté

La question. Un graphe orienté contient-il un cycle ? Formulé comme détection d'interblocage, boucles de dépendances de compilation ou « ce programme de cours peut-il être suivi jusqu'au bout ? ».

La mauvaise réponse est un unique ensemble visited : atteindre un sommet déjà vu ne signifie pas un cycle, ce peut être une seconde route vers une partie déjà terminée du graphe. La bonne réponse utilise trois couleurs : blanc pour non découvert, gris pour découvert mais encore sur la pile de récursion, noir pour terminé.

WHITE, GREY, BLACK = 0, 1, 2

def has_cycle(adj, n):
    colour = [WHITE] * n

    def visit(u):
        colour[u] = GREY
        for v in adj[u]:
            if colour[v] == GREY:      # arc arrière : v est un ancêtre
                return True
            if colour[v] == WHITE and visit(v):
                return True
        colour[u] = BLACK              # ce n'est qu'ici que u est terminé
        return False

    return any(colour[s] == WHITE and visit(s) for s in range(n))

Un arc vers un sommet gris est un arc arrière, et un graphe orienté a un cycle si et seulement si un DFS trouve un arc arrière. Un arc vers un sommet noir est sans danger. Cette équivalence, et la classification des arcs en quatre types dont elle fait partie, est le traitement standard de Cormen, Leiserson, Rivest et Stein.

Un graphe orienté à six sommets parcouru en profondeur depuis le sommet 0, avec les temps de découverte et de fin de chaque sommet et la classification de chaque arc. Cinq arcs sont des arcs d'arbre, l'arc de 0 vers 3 est un arc avant, et les arcs de 2 vers 3 et de 4 vers 5 sont des arcs transverses. Il n'y a aucun arc arrière, donc le graphe est acyclique. Un second panneau ajoute l'arc de 5 vers 0, qui devient un arc arrière vers un sommet gris et révèle le cycle 0, 1, 3, 5, puis retour à 0.
Le graphe d'exemple. Quatre types d'arcs, aucun arc arrière, donc aucun cycle. Ajoutez un arc et l'arc arrière apparaît, avec le cycle lisible directement sur le chemin de l'arbre.

Sur le graphe d'exemple, un DFS depuis le sommet 0 classe ses 8 arcs en 5 arcs d'arbre, 1 arc avant et 2 arcs transverses, sans arc arrière, il est donc acyclique. Ajoutez le seul arc 5 → 0 et exactement un arc arrière apparaît.

La relance : affichez le cycle, pas seulement un booléen. L'arc arrière vous le donne : s'il s'agit de u → v, remontez les pointeurs parents de u jusqu'à v et fermez la boucle. Ici, l'arc arrière est 5 → 0 et le cycle est 0 → 1 → 3 → 5 → 0. Un tableau de parents coûte une ligne et transforme un oui ou non en diagnostic, celui qu'un vrai outil de compilation doit fournir.

Le piège. Placer colour[u] = BLACK au mauvais endroit, ou l'oublier. Laissez les sommets terminés en gris et chaque seconde route vers eux ressemble à un cycle : vous signalez de faux positifs sur tout DAG contenant un losange, comme le graphe d'exemple.

4. Détection de cycles dans un graphe non orienté

La question. Même problème, graphe non orienté. Il ressemble à la question précédente, et ce n'en est pas une variante.

Les trois couleurs sont fausses ici. Chaque arête non orientée se parcourt dans les deux sens : en passant de u à v, l'arête qui revient vers u ressemble à une arête vers un sommet gris, et chaque arête signale un cycle. Il faut plutôt ignorer l'arête par laquelle vous êtes arrivé.

def has_cycle_undirected(adj, n):
    seen = [False] * n

    def visit(u, parent):
        seen[u] = True
        for v in adj[u]:
            if not seen[v]:
                if visit(v, u): return True
            elif v != parent:          # un voisin déjà vu, autre que le parent
                return True
        return False

    return any(not seen[s] and visit(s, -1) for s in range(n))

Sans la garde v != parent, un graphe réduit à la seule arête 0-1 signale un cycle. Avec elle, un arbre à 4 sommets n'en signale correctement aucun et un triangle en signale correctement un. Ce sont les trois cas de test à vérifier au tableau, et les vérifier spontanément fait très bonne impression.

La relance : et les arêtes multiples ? Alors v != parent ne suffit plus : deux arêtes distinctes entre u et v forment réellement un cycle de longueur 2, et le test du parent avale la seconde. Mémorisez l'arête par laquelle vous êtes entré, pas le sommet. Voir graphes simples vs multigraphes.

Le piège. Un graphe non connexe. La boucle extérieure sur chaque sommet non visité n'est pas facultative, et une solution qui ne part que du sommet 0 passe tous les cas de test connexes.

5. Tri topologique par post-ordre

La question. Ordonnez les sommets d'un DAG de sorte que chaque arc pointe vers l'avant. La réponse par BFS est l'épluchage par degré entrant de Kahn ; la réponse par DFS est plus courte, et c'est celle qu'attend une question sur le DFS.

Lancez un DFS, ajoutez chaque sommet quand il se termine, inversez la liste. C'est tout l'algorithme, et la raison tient en une phrase : un sommet ne se termine qu'après tout ce qui est accessible depuis lui, il se termine donc après ses successeurs, et l'inversion le place devant eux.

def topological_sort(adj, n):
    colour = [0] * n           # 0 blanc, 1 gris, 2 noir
    order = []

    def visit(u):
        colour[u] = 1
        for v in adj[u]:
            if colour[v] == 1: raise ValueError("cycle")
            if colour[v] == 0: visit(v)
        colour[u] = 2
        order.append(u)        # post-ordre : APRÈS les enfants

    for s in range(n):
        if colour[s] == 0: visit(s)
    return order[::-1]
Le même graphe orienté acyclique à six sommets, annoté avec l'ordre dans lequel le parcours en profondeur termine chaque sommet. Le post-ordre est 5, 3, 1, 4, 2, 0. L'inverser donne 0, 2, 4, 1, 3, 5, et un panneau de vérification confirme qu'aucun des huit arcs ne pointe vers l'arrière dans cet ordre.
L'ordre de fin inversé est un ordre topologique : aucun arc n'y pointe vers l'arrière.

Sur le DAG d'exemple, le post-ordre est 5, 3, 1, 4, 2, 0 ; l'inverser donne 0, 2, 4, 1, 3, 5, et les huit arcs y pointent tous vers l'avant. Précisez qu'il s'agit d'un ordre topologique, et non du seul : un DAG en a généralement plusieurs.

La relance : comment détecter un cycle ici ? Avec le test du gris de la section 3. C'est tout l'intérêt : un seul parcours ordonne le DAG et rejette en même temps ce qui n'en est pas un, là où Kahn a besoin d'un comptage séparé à la fin. La formulation par DFS est due à Tarjan.

Le piège. Ajouter en pré-ordre, quand le sommet est découvert plutôt que quand il se termine. Le résultat semble plausible, il est faux, et sur de petits graphes il coïncide souvent avec un ordre valide, si bien qu'il survit à des tests superficiels.

6. Clone graph

La question. Étant donné une référence vers un nœud d'un graphe non orienté connexe, renvoyez une copie profonde.

La seule difficulté, ce sont les cycles : une copie récursive naïve boucle indéfiniment. La solution est une table qui associe chaque nœud d'origine à sa copie, qui sert aussi d'ensemble des visités, et elle doit être remplie avant la récursion.

def clone_graph(node, made=None):
    if node is None: return None
    if made is None: made = {}
    if node in made:
        return made[node]
    copy = Node(node.val)
    made[node] = copy              # enregistrer AVANT la récursion
    for nb in node.neighbors:
        copy.neighbors.append(clone_graph(nb, made))
    return copy

Enregistrer la copie avant les appels récursifs, c'est toute la question. Faites-le après, et un cycle vous ramène au point de départ avant que l'entrée existe : la récursion continue jusqu'à ce que la pile meure. C'est la même forme que la mémoïsation de toute structure autoréférentielle.

La relance : en itératif, ou en BFS ? Les deux fonctionnent, avec une table identique. Dites que c'est la table, et non l'ordre de parcours, qui rend la solution correcte. Le coût est de O(V + E) dans les deux cas.

7. Tous les chemins : le DFS comme backtracking

La question. Listez tous les chemins d'une source à une cible dans un DAG. Variantes : tous les chemins de la racine aux feuilles, somme de chemin, permutations.

C'est la famille où le DFS cesse d'être un parcours de graphe pour devenir du backtracking, et la différence tient en une ligne : vous annulez votre choix en repartant.

def all_paths(adj, src, dst):
    out, path = [], []

    def walk(u):
        path.append(u)
        if u == dst:
            out.append(path[:])    # COPIE, pas la liste vivante
        else:
            for v in adj[u]:
                walk(v)
        path.pop()                 # l'étape de backtracking

    walk(src)
    return out

Sur le DAG d'exemple, il y a exactement 4 chemins de 0 à 5 : 0→1→3→5, 0→2→3→5, 0→2→4→5 et 0→3→5.

Deux détails portent la réponse. Ajoutez une copie, path[:], car path est modifiée ensuite, et la référence vous donne une liste de listes vides identiques. Et il n'y a pas d'ensemble des visités : vous énumérez des chemins, pas des sommets, donc un sommet apparaît légitimement dans de nombreux chemins. Le path.pop() maintient l'état correct sans lui.

La relance : la complexité ? Pas O(V + E). Un DAG peut avoir un nombre exponentiel de chemins, donc les lister est exponentiel en la taille de la sortie ; la réponse honnête est O(V × 2V). Dire linéaire ici révèle que vous n'avez pas réfléchi à ce qu'est la sortie. Si l'on demande seulement combien de chemins existent, c'est un autre problème : comptez par programmation dynamique sur l'ordre topologique, en O(V + E).

Le piège. Ajouter un ensemble des visités parce que « le DFS en a toujours un ». Sur un graphe cyclique, vous excluez bien les sommets déjà présents sur le chemin courant, mais c'est le chemin, pas un ensemble global, et un ensemble global renvoie silencieusement un sous-ensemble des réponses.

8. Word search sur une grille

La question. Étant donné une grille de lettres et un mot, déterminez si le mot peut être formé en passant d'une case à une case adjacente horizontalement ou verticalement, sans utiliser deux fois la même case.

C'est du backtracking sur un graphe de grille implicite, et la clause « jamais deux fois la même case » est ce qui impose l'annulation.

def exist(board, word):
    R, C = len(board), len(board[0])

    def walk(r, c, i):
        if i == len(word): return True
        if not (0 <= r < R and 0 <= c < C): return False
        if board[r][c] != word[i]: return False

        board[r][c] = '#'                      # marquer, pour que le chemin ne la réutilise pas
        found = any(walk(r + dr, c + dc, i + 1)
                    for dr, dc in ((1,0), (-1,0), (0,1), (0,-1)))
        board[r][c] = word[i]                  # ANNULER en repartant
        return found

    return any(walk(r, c, 0) for r in range(R) for c in range(C))

Le marqueur doit être restauré : une case bloquée par une tentative ratée doit rester disponible pour un autre départ, et l'oublier donne une fonction qui ne réussit que si le premier chemin essayé fonctionne par chance. Écraser le plateau au lieu de tenir un ensemble des visités est une astuce légitime, mais dites-le, car cela modifie l'entrée de l'appelant.

La relance : la complexité. O(R × C × 3L) pour un mot de longueur L : chaque case est un départ possible, et après le premier pas vous ne revenez jamais par où vous êtes venu, donc chaque pas suivant offre au plus 3 choix, pas 4. Ce 3 est le détail qui montre que vous y avez réfléchi.

9. Composantes fortement connexes

La question. Partitionnez un graphe orienté en ensembles maximaux de sommets mutuellement accessibles. Cela apparaît sous la forme « trouvez les dépendances circulaires », ou comme prétraitement avant un algorithme sur DAG.

Il existe deux réponses par DFS, et vous devez savoir laquelle vous écrivez.

Kosaraju-Sharir se fait en deux passes et il est bien plus facile de le réussir sous pression. Faites un DFS du graphe en notant l'ordre de fin, puis un DFS du graphe inversé en prenant les sommets par ordre de fin décroissant ; chaque arbre de la seconde passe est une composante.

def kosaraju(adj, radj, n):
    seen, order = [False] * n, []
    def pass1(u):
        seen[u] = True
        for v in adj[u]:
            if not seen[v]: pass1(v)
        order.append(u)                  # ordre de fin
    for s in range(n):
        if not seen[s]: pass1(s)

    comp, c = [-1] * n, 0
    def pass2(u):
        comp[u] = c
        for v in radj[u]:
            if comp[v] == -1: pass2(v)
    for u in reversed(order):            # temps de fin décroissant
        if comp[u] == -1:
            pass2(u); c += 1
    return comp, c

Sur un graphe de deux triangles, 0→1→2→0 et 3→4→5→3, reliés par le seul arc 2→3, cela renvoie exactement deux composantes, {0,1,2} et {3,4,5}. L'accessibilité le confirme : 0 atteint 3, et 3 n'atteint pas 0.

L'algorithme de Tarjan le fait en une seule passe avec une pile et une valeur low-link : plus rapide en pratique, bien plus facile à rater au tableau. Les deux sont en O(V + E), et chacun a un visualiseur ici. L'article de Tarjan de 1972 a donné la méthode en une passe ; la version en deux passes est attribuée à Kosaraju et a été publiée pour la première fois par Sharir en 1981.

Pour approfondir : le guide des composantes fortement connexes déroule les deux algorithmes sur un même graphe et montre les bogues qui passent les petits tests.

La relance : pourquoi le graphe inversé fonctionne-t-il ? Inverser chaque arc laisse les composantes inchangées, puisque l'accessibilité mutuelle est symétrique. Le sommet de plus grand temps de fin se trouve dans une composante source de la condensation, et l'inversion transforme une source en puits : un DFS lancé depuis là ne peut donc pas en sortir.

10. Ponts et points d'articulation

La question. Quelles arêtes, si on les retire, déconnectent le graphe ? Quels sommets ? Formulé comme points de défaillance uniques, ou connexions critiques dans un cluster.

C'est la question standard la plus profonde sur le DFS, et elle repose sur une seule idée : en plus du temps de découverte de chaque sommet, suivez low[u], le plus petit temps de découverte accessible depuis le sous-arbre de u en utilisant au plus une arête hors de l'arbre.

def bridges(adj, n):
    disc, low = [-1] * n, [-1] * n
    out, clock = [], 0

    def visit(u, parent):
        nonlocal clock
        disc[u] = low[u] = clock; clock += 1
        for v in adj[u]:
            if v == parent:
                parent = -2                 # ignorer UNE copie de l'arête du parent
                continue
            if disc[v] == -1:
                visit(v, u)
                low[u] = min(low[u], low[v])
                if low[v] > disc[u]:
                    out.append((u, v))      # rien sous v n'atteint u ou plus haut
            else:
                low[u] = min(low[u], disc[v])

    for s in range(n):
        if disc[s] == -1: visit(s, -1)
    return out
Un graphe non orienté formé de deux triangles, les sommets 0, 1, 2 et les sommets 3, 4, 5, reliés par une seule arête entre le sommet 2 et le sommet 3. Chaque sommet porte son temps de découverte et sa valeur low-link : les sommets 0, 1 et 2 ont tous low 0, et les sommets 3, 4 et 5 ont tous low 3. L'arête de 2 à 3 est mise en évidence comme seul pont, car le low de 3 dépasse le temps de découverte de 2, et les sommets 2 et 3 sont marqués comme points d'articulation.
Deux triangles reliés par une arête. À l'intérieur de chaque triangle, chaque sommet peut revenir au point d'entrée du triangle, donc low s'effondre ; à travers la jonction, c'est impossible, et c'est le pont.

low[v] > disc[u] signifie que le sous-arbre sous v n'a aucun moyen de revenir à u ou plus haut, donc u-v est la seule route et la retirer coupe le graphe. Sur le graphe à deux triangles, les temps de découverte vont de 0 à 5 et les valeurs low sont 0, 0, 0, 3, 3, 3. Le seul pont est 2-3 ; les points d'articulation sont 2 et 3. La force brute confirme : retirer cette arête, ou l'un de ces deux sommets, laisse 2 composantes, et aucun autre retrait isolé ne déconnecte quoi que ce soit.

Les points d'articulation utilisent le même parcours avec deux règles : un sommet non racine u en est un si un enfant v vérifie low[v] >= disc[u], et la racine en est un si elle a plus d'un enfant dans le DFS. Notez le >= contre le > des ponts. Ce seul caractère sépare les deux réponses, et les confondre est l'erreur la plus courante ici.

Le piège. Le test du parent. if v == parent: continue sans garde à usage unique est faux sur un multigraphe : deux arêtes parallèles vers le parent signifient que la paire n'est pas un pont, et les ignorer toutes les deux le masque. Mémorisez l'indice de l'arête, ou n'ignorez que la première occurrence comme ci-dessus. L'algorithme est dû à Hopcroft et Tarjan, en 1973, et il existe un visualiseur pour lui.

11. Complexité, et la question de la profondeur de récursion

Chaque algorithme ci-dessus est un seul parcours, donc la borne en temps bouge à peine. C'est dans l'espace que se trouve la question intéressante.

ProblèmeTempsEspaceLa justification à donner
DFS, liste d'adjacenceO(V + E)O(V)Chaque sommet visité une fois, chaque arête examinée une fois par sens
Détection de cycles orientéeO(V + E)O(V)Un tableau de couleurs sur le même parcours
Tri topologiqueO(V + E)O(V)Liste en post-ordre plus la pile de récursion
CFC de Kosaraju-SharirO(V + E)O(V + E)Deux parcours, et le graphe inversé est une seconde copie
Ponts, points d'articulationO(V + E)O(V)Deux tableaux d'entiers, disc et low
Word search, longueur du mot LO(R × C × 3L)O(L)Chaque case est un départ ; 3 choix suivants après le premier pas
Tous les cheminsO(V × 2V)O(V)La sortie elle-même peut être exponentielle

La question de la profondeur de récursion est posée dans presque tous les entretiens sur le DFS : ayez la réponse prête. Le DFS descend aussi profond que le plus long chemin qu'il suit, ce qui sur un graphe chemin vaut V. La limite par défaut de CPython est 1000, donc quelques milliers de sommets en ligne le font planter, et une grille de 1000 sur 1000 cases de terre peut descendre à un million de niveaux.

La solution est la version itérative de la section 2, pas sys.setrecursionlimit, qui ne fait que transformer une exception propre en véritable débordement de pile. Dites-le explicitement. L'espace en O(V) correspond à cette pile, et la comparaison honnête avec le BFS est que le DFS garde un chemin de la racine à une feuille alors que le BFS garde une couche entière : aucun n'est toujours plus petit, cela dépend de la profondeur ou de la largeur du graphe. Aho, Hopcroft et Ullman donnent l'analyse globale ; Sedgewick et Wayne, le traitement clair le plus court.

12. Les erreurs qui font échouer l'entretien

Classées par fréquence ; les trois premières expliquent la plupart des solutions rejetées.

L'habitude qui évite la plupart de ces erreurs : avant d'écrire, nommez le tableau supplémentaire. Couleur, parent, ordre de fin ou low-link. Les questions sur le DFS se distinguent par ce tableau, pas par le parcours, et le nommer d'abord rend le reste mécanique. McDowell défend la même idée en général ; ici, elle est d'une littéralité inhabituelle.

13. Questions fréquentes

Quand faut-il choisir le DFS plutôt que le BFS ?

+

Quand la question porte sur l'ordre, les dépendances, les cycles ou ce qui casse si l'on retire quelque chose. Tout cela exige de savoir quand un sommet se termine, ou s'il est encore sur la pile, et seul le DFS le fournit. Si elle demande le moins de quelque chose, utilisez le BFS : le DFS trouve un chemin, pas le plus court.

Pourquoi faut-il trois couleurs pour la détection de cycles orientée ?

+

Parce qu'un simple ensemble des visités ne distingue pas un ancêtre d'un sommet terminé. Gris signifie encore sur la pile de récursion : un arc vers un sommet gris ferme une boucle et constitue un vrai cycle. Noir signifie terminé, et un arc vers un sommet noir n'est qu'une seconde route vers une partie déjà explorée du graphe, ce qui est permis dans un DAG.

Pourquoi inverser le post-ordre du DFS donne-t-il un tri topologique ?

+

Parce qu'un sommet ne se termine qu'après tout ce qui est accessible depuis lui, il se termine donc toujours après ses successeurs. Inverser l'ordre de fin place ainsi chaque sommet devant tout ce vers quoi il pointe, ce qui est la condition topologique. Ajoutez en post-ordre, quand le sommet se termine, pas quand il est découvert.

Qu'est-ce qu'une valeur low-link ?

+

Pour un sommet u, c'est le plus petit temps de découverte accessible depuis le sous-arbre de u en utilisant des arêtes d'arbre plus au plus une arête hors de l'arbre. Elle répond à la question « quelque chose sous u peut-il remonter au-dessus de u sans l'arête vers son parent ? ». Sinon, cette arête est un pont. C'est l'unique tableau supplémentaire qui transforme un DFS ordinaire en algorithme pour les ponts, les points d'articulation et les composantes fortement connexes de Tarjan.

Jusqu'à quelle profondeur un DFS récursif peut-il aller avant de casser ?

+

Aussi profond que le plus long chemin qu'il suit, ce qui sur un graphe chemin correspond au nombre de sommets. La limite par défaut de CPython est 1000, donc quelques milliers de sommets en ligne le font planter, et une grille de 1000 sur 1000 cases de terre descend à un million de niveaux. Réécrivez-le en itératif plutôt que de relever la limite, ce qui ne fait que transformer une exception propre en véritable débordement de pile.

Le DFS itératif est-il identique au DFS récursif avec une pile ?

+

Pas tout à fait. La version simple avec pile visite les voisins dans l'ordre inverse, empilez-les donc à l'envers pour obtenir le même ordre, et elle doit tester l'ensemble des visités au dépilement comme à l'empilement, puisqu'un sommet peut se trouver plusieurs fois sur la pile. Surtout, elle n'a pas de post-ordre : le tri topologique, les composantes fortement connexes et les algorithmes low-link exigent une version qui empile chaque sommet deux fois ou suit un indice d'enfant.

Ai-je besoin d'un ensemble des visités pour énumérer tous les chemins ?

+

Non, et en ajouter un est un bogue courant. Vous énumérez des chemins plutôt que des sommets, donc le même sommet apparaît légitimement dans de nombreux chemins, et un ensemble des visités global n'en renvoie silencieusement qu'une partie. Ce qu'il vous faut, c'est le chemin courant, annulé par un pop en repartant. Sur un graphe cyclique, vous excluez les sommets déjà présents sur ce chemin, ce qui n'est pas un ensemble global.

14. Références

Les articles qui ont introduit ces techniques et les ouvrages qui les analysent, par ordre chronologique.

  1. Lucas, É. (1882). Récréations Mathématiques, volume 1. Gauthier-Villars. (Consigne la règle systématique de Trémaux pour parcourir les labyrinthes, la plus ancienne description du parcours en profondeur.)
  2. Tarjan, R. E. (1972). “Depth-first search and linear graph algorithms.” SIAM Journal on Computing, 1(2), 146–160.
  3. Hopcroft, J. et Tarjan, R. E. (1973). “Algorithm 447: efficient algorithms for graph manipulation.” Communications of the ACM, 16(6), 372–378.
  4. Aho, A. V., Hopcroft, J. E. et Ullman, J. D. (1974). The Design and Analysis of Computer Algorithms. Addison-Wesley.
  5. Tarjan, R. E. (1976). “Edge-disjoint spanning trees and depth-first search.” Acta Informatica, 6(2), 171–185.
  6. Sharir, M. (1981). “A strong-connectivity algorithm and its applications in data flow analysis.” Computers & Mathematics with Applications, 7(1), 67–72.
  7. Cormen, T. H., Leiserson, C. E., Rivest, R. L. et Stein, C. (2009). Introduction to Algorithms, 3e édition, section 22.3. MIT Press.
  8. Sedgewick, R. et Wayne, K. (2011). Algorithms, 4e édition, sections 4.1 à 4.2. Addison-Wesley.
  9. McDowell, G. L. (2015). Cracking the Coding Interview, 6e édition. CareerCup.
  10. Skiena, S. S. (2020). The Algorithm Design Manual, 3e édition, chapitre 5. Springer.

Regardez la pile se dérouler

Suivez pas à pas un parcours en profondeur et regardez chaque sommet devenir gris à la descente et noir à la remontée. Voir la pile est le moyen le plus rapide de comprendre pourquoi seul un arc vers un sommet gris ferme un cycle.

Lancer le visualiseur DFS