Graphes — 05 — Le tri topologique : ordonner des dépendances

Le tri topologique : ordonner les sommets d'un DAG pour respecter toutes les dépendances. Les deux méthodes (Kahn par degrés entrants, et DFS), et leurs usages.

05 — Le tri topologique : ordonner des dépendances

Ce que tu vas apprendre

  • Ce que produit un tri topologique et à quoi il sert
  • L'algorithme de Kahn, par degrés entrants
  • L'alternative par DFS
  • Comment le tri topologique détecte aussi les cycles

Prérequis


Le tri topologique répond à une question d'ordonnancement omniprésente : dans quel ordre exécuter des tâches qui dépendent les unes des autres ? Compiler des modules qui s'importent, installer des paquets aux dépendances imbriquées, planifier des étapes de build : tous ces problèmes reviennent à ordonner les sommets d'un DAG de façon que chaque sommet vienne après ceux dont il dépend. C'est exactement ce que fait le tri topologique.

Ce qu'il produit

Un tri topologique est un ordre linéaire des sommets tel que, pour chaque arête A→B, A apparaît avant B. Si A→B signifie « A doit être fait avant B », alors le tri topologique donne un ordre d'exécution valide.

DAG de dépendances :

  vaisselle → essuyage
  courses → cuisine
  cuisine → vaisselle

Un tri topologique valide :
  courses, cuisine, vaisselle, essuyage

Il peut exister plusieurs ordres valides (ici, « courses » et « cuisine » pourraient précéder ou suivre des tâches indépendantes). Le tri topologique en produit un parmi les valides. Il n'existe que pour un DAG : un cycle rend tout ordre impossible.

L'algorithme de Kahn (par degrés entrants)

La méthode la plus intuitive repose sur le degré entrant de chaque sommet, soit le nombre d'arêtes qui pointent vers lui (le nombre de dépendances non encore satisfaites). L'idée : un sommet sans dépendance peut être traité tout de suite. Une fois traité, on retire ses arêtes, ce qui peut libérer d'autres sommets.

pythonfrom collections import deque

def tri_topologique_kahn(graphe):
    # calculer le degré entrant de chaque sommet
    degre_entrant = {s: 0 for s in graphe}
    for s in graphe:
        for voisin in graphe[s]:
            degre_entrant[voisin] += 1

    # commencer par les sommets sans dépendance
    file = deque([s for s in graphe if degre_entrant[s] == 0])
    ordre = []
    while file:
        sommet = file.popleft()
        ordre.append(sommet)
        for voisin in graphe[sommet]:
            degre_entrant[voisin] -= 1     # une dépendance satisfaite
            if degre_entrant[voisin] == 0:  # plus de dépendance : prêt
                file.append(voisin)

    if len(ordre) != len(graphe):
        raise ValueError("Le graphe contient un cycle")
    return ordre

On enfile les sommets dès que leur dernière dépendance est levée. La complexité est O(V + E) : on visite chaque sommet et chaque arête une fois.

La détection de cycle gratuite

L'algorithme de Kahn détecte les cycles sans effort supplémentaire. Si à la fin l'ordre ne contient pas tous les sommets, c'est qu'il en restait avec un degré entrant non nul : ils font partie d'un cycle (chacun attend un autre, indéfiniment). C'est le lien direct avec l'article précédent : trier topologiquement, c'est aussi vérifier que le graphe est bien un DAG.

L'alternative par DFS

On peut aussi obtenir un tri topologique avec un DFS. L'astuce : quand le DFS finit de traiter un sommet (tous ses descendants sont explorés), on l'empile. L'ordre topologique est la pile inversée.

pythondef tri_topologique_dfs(graphe):
    visites = set()
    pile = []

    def visiter(sommet):
        visites.add(sommet)
        for voisin in graphe[sommet]:
            if voisin not in visites:
                visiter(voisin)
        pile.append(sommet)   # empilé une fois tous les descendants traités

    for s in graphe:
        if s not in visites:
            visiter(s)
    return pile[::-1]          # l'ordre est la pile inversée

L'intuition : un sommet n'est empilé qu'après tous ceux qui dépendent de lui (ses descendants dans le DFS). En inversant, il se retrouve avant eux. Cette version est en O(V + E) également. Elle est concise, mais pour détecter un cycle, elle demande d'ajouter la coloration gris/noir de l'article précédent ; la méthode de Kahn détecte le cycle naturellement, ce qui la rend souvent préférable en pratique.

Méthode Structure Détection de cycle Complexité
Kahn file + degrés entrants naturelle (compte final) O(V + E)
DFS pile / récursion demande la coloration O(V + E)

Les usages

  • Systèmes de build : Make, et la plupart des outils de build modernes, ordonnent les tâches par tri topologique sur le graphe de dépendances.
  • Gestionnaires de paquets : déterminer l'ordre d'installation de paquets aux dépendances imbriquées.
  • Résolution de modules : l'ordre de chargement de modules qui s'importent mutuellement (et la détection des imports circulaires interdits).
  • Planification de tâches et pipelines : ordonnancer des étapes avec des contraintes d'antériorité.
  • Tableurs : recalculer les cellules dans l'ordre où leurs formules dépendent les unes des autres.

Le tri topologique conclut la partie « structure » des graphes. Les articles suivants passent à l'optimisation de chemins sur des graphes pondérés, en commençant par le plus connu : Dijkstra.


Sources

  • Kahn, A. B. (1962). Topological sorting of large networks. Communications of the ACM, 5(11). (Algorithme de Kahn)
  • Cormen, T. H., et al. (2009). Introduction to Algorithms (3rd ed.), Section 22.4 "Topological Sort". MIT Press.
  • Sedgewick, R., & Wayne, K. (2011). Algorithms (4th ed.), Section 4.2. Addison-Wesley.

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.