Algorithme A*

13 Mar 2026 13 Mar 2026 247 vues ESSADDOUKI Mostafa 10 min de lecture
Complexité algorithmique
1 Introduction à l'analyse des algorithmes 2 Notations asymptotiques 3 Opérations élémentaires et modèles de coût 4 Complexité temporelle et spatiale 5 Méthode de comptage des pas 6 Méthode de comptage des pas pour les boucles
Diviser pour régner & algorithmes de tri
7 Rappel sur l'approche récursive 8 Diviser pour régner 9 la recherche dichotomique 10 Tri par fusion 11 Tri rapide 12 Analyse des fonctions récursives
Les arbres binaires
13 Introduction aux arbres binaires 14 Définitions récursives des arbres 15 Propriétés des arbres binaires 16 Types d'arbre binaire 17 Parcours en profondeur des arbres binaires 18 Parcours en largeur des arbres binaires 19 Exercices corrigés sur les arbres - TD 1 20 Exercices corrigés sur les arbres - TD 2 21 Exercices corrigés sur les arbres - TD 3 22 DS - Arbres binaires de recherche équilibrés (AVL) 23 DS - Codage de Huffman - Compression de données
Algorithmes gloutons
24 Introduction aux algorithmes gloutons 25 Problème de la sélection d'activités 26 Problème de séquencement des tâches 27 Problème du Sac à Dos fraction
Programmation dynamique
28 Introduction à la programmation dynamique 29 Le concept de sous-structure optimale 30 Le concept de sous-problèmes superposés 31 Méthodes de la programmation dynamique 32 Différence entre la programmation dynamique, l'approche diviser pour régner, et les algorithmes gloutons 33 Calculer les nombres de catalan en C++ et Python 34 Calculer le coefficient binomial 35 Le nombre de façons pour construire un mur de dimension 4*N 36 Défi de conversion de mots 37 Décomposition de phrases à partir d'un dictionnaire 38 La collection de pièces dans un labyrinthe 39 Nombre de façons de regrouper les étudiants 40 Compter tous les chemins possibles dans une grille MxN
Méta heuristique
41 Algorithmes heuristiques et métaheuristiques 42 Algorithme de Recuit simulé 43 Algorithme Colonies de fourmis
Théorie des graphes
44 Introduction et notions fondamentales 45 Chemins, cycles et connexité 46 Représentations des graphes 47 Parcours de graphes 48 Algorithme de Dijkstra 49 Algorithme A* 50 Algorithme de Bellman-Ford 51 Algorithme Floyd-Warshall 52 Coloration des graphes
Base de données et SQL
53 Introduction au langage SQL 54 Concepts de base de SGBDR 55 Syntaxes de différentes instructions SQL 56 Création et suppression d'une base de données 57 Opérateurs SQL 58 Les contraintes en SQL 59 Création et suppression des tables en SQL 60 Insertion et modifications des enregistrements 61 Extraction des données - SELECT 62 Filtrer les données - WHERE 63 Recherche de motifs - LIKE 64 Trier les données - ORDER BY 65 Les jointures en SQL - JOIN 66 Fonctions d'agrégation en SQL - SUM, COUNT, AVG, MIN et MAX 67 Organiser des données identiques en groupes - GROUP BY et HAVING 68 Les sous-requêtes en SQL 69 Les fonctions SQL de manipulation de date 70 DS - langage SQL 71 Exercices corrigés de langage SQL
Introduction à l'intelligence artificielle
Introduction à la théorie des jeux
Préparation aux concours
72 Réseau de distribution d'eau 73 Arbre d'expression arithmétique 74 Exploration d'une grotte souterraine

Algorithme A* (A-star)

L'algorithme A* ne se contente pas de regarder le chemin parcouru ; il projette également l'effort restant. C'est un algorithme best-first search (recherche du meilleur d'abord).

Fonction d'évaluationIl repose sur une équation fondamentale qui définit la priorité de chaque sommet \(v\) : \[ f(v) = g(v) + h(v) \] où :
  • \(g(v)\) (Le coût réel) : C'est la distance exacte parcourue depuis la source jusqu'à \(v\). C'est la mémoire de l'algorithme.
  • \(h(v)\) (L'heuristique) : C'est une estimation du coût restant pour atteindre la cible depuis \(v\). C'est l'intuition de l'algorithme.
  • \(f(v)\) (L'espérance totale) : C'est l'estimation du coût total du chemin passant par \(v\).
Différence conceptuelle
  • Dijkstra choisit le prochain sommet tel que \(g(v)\) soit minimal.
  • A* choisit le prochain sommet tel que \(f(v)\) soit minimal.

Le rôle de l'Heuristique \(h\)

L'efficacité de A* dépend entièrement du choix de la fonction \(h\). Elle doit être rapide à calculer, car elle est appelée à chaque expansion de sommet.

Le choix de l'heuristique dépend de la liberté de mouvement dans l'espace :

  • Distance Euclidienne : Utilisée dans un espace libre (vol d'oiseau).
  • Distance de Manhattan : Idéale pour les grilles où l'on ne se déplace qu'horizontalement et verticalement.
  • Distance de Tchebychev : Utilisée si les déplacements en diagonale sont autorisés au même coût que les déplacements cardinaux.

La condition d'optimalité : L'admissibilité

C'est le point crucial pour les examens et la mise en pratique. Pour que A* garantisse le chemin le plus court, l'heuristique \(h\) doit être admissible.

Admissibilité Une heuristique est dite admissible si elle ne surestime jamais le coût réel pour atteindre la cible. \[ \forall v \in S,\ 0 \le h(v) \le d^{*}(v) \] où \(d^{*}(v)\) est le coût réel minimal de \(v\) à la cible.

Pourquoi est-ce vital ?

  • Si \(h(v)\) est trop optimiste (admissible), A* explorera prudemment.
  • Si \(h(v)\) est trop pessimiste (non-admissible), l'algorithme risque de trouver un chemin "suffisamment bon" mais de passer à côté du chemin optimal car il a cru qu'un raccourci potentiel était trop coûteux.
Remarque Si \(h(v)=0\) pour tout \(v\), alors \(f(v)=g(v)\). A* redevient alors strictement l'algorithme de Dijkstra. On dit que Dijkstra est un cas particulier de A*.

Types d'heuristiques admissibles

Heuristique basée sur le nombre minimal d'arcs

Heuristique basée sur le nombre minimal d'arcs\[ h(s) = k(s) \times w_{min} \] où :
  • \(k(s)\) est le nombre minimal d'arcs séparant \(s\) de la destination,
  • \(w_{min}\) est le plus petit poids d'arête du graphe.

Chaque arc coûte au moins \(w_{min}\), donc le coût réel est forcément : \(\ge k(s) \times w_{min}\). Donc, l'heuristique est admissible.

Heuristique géométrique (distance euclidienne)

Lorsque les sommets sont placés dans le plan, on peut utiliser la distance géométrique vers la destination.

Heuristique géométrique \[ h(s) = \alpha \cdot \text{dist}_{euc}(s, \text{destination}) \] où \(\alpha\) est un facteur garantissant : \(h(s) \le d^{*}(s, \text{destination})\)

La distance géométrique fournit une borne inférieure naturelle du coût restant.

Heuristique fondée sur un chemin connu

On estime le coût restant en considérant un chemin valide connu, mais pas forcément optimal, vers la destination.

Heuristique fondée sur un chemin connu \[ h(s) = \text{coût d'un chemin connu reliant } s \text{ à la destination} \]

Le plus court chemin réel ne peut être plus long que ce chemin connu. Donc, l'heuristique est admissible.

Déroulement de l'algorithme

L'algorithme utilise trois tableaux principaux :

  • \(g[v]\) : le coût réel du chemin le plus court trouvé de la source \(s\) à \(v\).
  • \(f[v]\) : l'estimation du coût total passant par \(v\) (\(g[v] + h(v)\)).
  • \(\pi[v]\) : le prédécesseur de \(v\) (pour reconstruire le chemin).

Algorithme A*


Algorithme A* Pseudo-code
Pour tout sommet v:
    g[v] ← +∞
    f[v] ← +∞
    π[v] ← None
FinPour

g[s] ← 0
f[s] ← 0
h[s] ← heuristique(s, destination)

F ← ∅                    // Liste des sommets visités/fermés
Q ← {s}                  // Liste des sommets à explorer/ouverts

TantQue Q ≠ ∅ Faire
    u ← sommet de Q avec f[u] minimal
    Si u = destination : 
        Arrêter (Cible atteinte, reconstruire le chemin via π)

    Retirer u de Q et l'ajouter à F
    Pour chaque voisin v ∉ F de u Faire
        cout_provisoire ← g[u] + w(u,v)
        Si v ∉ Q ou cout_provisoire < g[v] Alors
            g[v] ← cout_provisoire
            h[v] ← heuristique(v, destination)
            f[v] ← g[v] + h[v]
            π[v] ← u

            Si v ∉ Q Alors
                ajouter v à Q
            FinSi
        FinSi
    FinPour
TantQue

Exemple : A* de A vers F

On considère le graphe suivant et on effectue l'algorithme de A* à partir du sommet \(A\) vers le sommet \(F\).

   
Représentation du graphe Python
graphe = [
    [(1, 10), (3, 5), (2, 12)], # A (0)
    [(0, 10), (4, 11)],         # B (1)
    [(0, 12), (3, 6), (4, 11), (5, 8)], # C (2)
    [(0, 5), (2, 6), (5, 14)],  # D (3)
    [(1, 11), (2, 11)],         # E (4)
    [(2, 8), (3, 14)]           # F (5)
]

Heuristique \(h(s)\) (admissible)

On choisit l'heuristique basée sur le nombre minimal d'arcs :

\[ h(s) = \text{nombre minimal d'arcs de } s \text{ à la destination} \times 1 \]

(car le plus petit poids d'arc du graphe vaut 1, donc ça ne surestime jamais).

Distances en "nombre d'arcs" vers F :
  • \(h(F) = 0\)
  • \(h(C) = h(D) = 1\)
  • \(h(A) = h(E) = 2\)
  • \(h(B) = 3\)
Étape 0 : Initialisation
Sommets à explorer (Q)Sommets visités (F)
SommetgfπSommetgfπ
A02None



BNone



CNone



DNone



ENone



FNone



Étape 1 : Depuis A

On regarde les voisins directs de \(A\) : \(B\), \(C\) et \(D\). On calcule \(g\) (coût réel) et \(f = g + h\).

  • B : \(g(B) = 10\). Priorité \(f(B) = 10 + h(B) = 10 + 3 = 13\).
  • C : \(g(C) = 12\). Priorité \(f(C) = 12 + h(C) = 12 + 1 = 13\).
  • D : \(g(D) = 5\). Priorité \(f(D) = 5 + h(D) = 5 + 1 = 6\).
Sommets à explorer (Q)Sommets visités (F)
SommetgfπSommetgfπ
B1013AA02None
C1213A



D56A



ENone



FNone



Le minimum de \(f\) entre \(B:13\), \(C:13\) et \(D:6\) est \(D\). On verrouille \(D\).

Étape 2 : Depuis D

On regarde les voisins de \(D\) : \(F\) et \(C\).

  • C : \(g(C) = g(D) + 6 = 11\). \(f(C) = 11 + 1 = 12\). (On met à jour car \(12 < 13\)).
  • F : \(g(F) = g(D) + 14 = 19\). \(f(F) = 19 + 0 = 19\).
Sommets à explorer (Q)Sommets visités (F)
SommetgfπSommetgfπ
B1013AA02None
C1112DD56A
ENone



F1919D



Le minimum dans \(Q\) est \(f(C) = 12\). On verrouille \(C\).

Étape 3 : Depuis C

On regarde les voisins de \(C\) : \(E\) et \(F\).

  • E : \(g(E) = g(C) + 11 = 11 + 11 = 22\). \(f(E) = 22 + 2 = 24\).
  • F : \(g(F) = g(C) + 8 = 11 + 8 = 19\). \(f(F) = 19 + 0 = 19\).
Sommets à explorer (Q)Sommets visités (F)
SommetgfπSommetgfπ
B1013AA02None
E2224CD56A
F1919DC1112D

Le minimum dans \(Q\) entre \(B : 13\) et \(F : 19\) est \(B\). On verrouille \(B\).

Étape 4 : Depuis B

Le seul voisin sortant de \(B\) est \(E\).

  • E : \(g(E) = g(B) + 11 = 10 + 11 = 21\). \(f(E) = 21 + 2 = 23\).
Sommets à explorer (Q)Sommets visités (F)
SommetgfπSommetgfπ
E2123BA02None
F1919DD56A




C1112D




B1013A

Le minimum est désormais \(F\) avec \(f = 19\). \(F\) est le nœud cible, la recherche est donc terminée.

Résultat final
Sommetgfπ
A02None
D56A
C1112D
B1013A
F1919D
Résultats
  • Chemin optimal : \(A \rightarrow D \rightarrow F\)
  • Coût réel \(g(F)\) : \(5 + 14 = 19\)

Implémentation Python

   
Fonction a_star Python
import heapq

def a_star(g_adj, h, s, cible):
    n = len(g_adj)
    # Q : File de priorité (f_score, u)
    Q = [(h[s], s)]
    
    # Initialisation des coûts réels g à l'infini
    g_score = [float('inf')] * n
    g_score[s] = 0
    
    # Dictionnaire des parents pour la reconstruction (initialisé à None)
    parents = [None] * n
    
    # F : Liste fermée (sommets verrouillés)
    F = set()

    while Q:
        # Extraire le sommet u ayant le f_score minimal
        f_u, u = heapq.heappop(Q)

        # SI CIBLE ATTEINTE : Déclencher la reconstruction
        if u == cible:
            return reconstruire_chemin(parents, cible), g_score[cible]

        if u in F:
            continue
            
        F.add(u)

        # Exploration des voisins
        for v, poids in g_adj[u]:
            if v in F:
                continue
            
            # g_score provisoire pour le voisin v via le sommet u
            g_provisoire = g_score[u] + poids
            
            if g_provisoire < g_score[v]:
                # On a trouvé un meilleur chemin vers v !
                parents[v] = u
                g_score[v] = g_provisoire
                f_score = g_provisoire + h[v]
                heapq.heappush(Q, (f_score, v))
                
    return None, float('inf')


def reconstruire_chemin(parents, actuel):
    """Reconstruit le chemin depuis la source jusqu'à la cible"""
    chemin = []
    while actuel is not None:
        chemin.append(actuel)
        actuel = parents[actuel]
    return chemin[::-1]  # On inverse la liste pour avoir l'ordre Départ -> Cible


# Exemple d'utilisation
graphe = [
    [(1, 10), (3, 5), (2, 12)], # A (0)
    [(0, 10), (4, 11)],         # B (1)
    [(0, 12), (3, 6), (4, 11), (5, 8)], # C (2)
    [(0, 5), (2, 6), (5, 14)],  # D (3)
    [(1, 11), (2, 11)],         # E (4)
    [(2, 8), (3, 14)]           # F (5)
]

# Heuristique : nombre minimal d'arcs vers F
h = [2, 3, 1, 1, 2, 0]

chemin_indices, cout_total = a_star(graphe, h, 0, 5)
print(f'Chemin de A(0) à F(5) : {chemin_indices}')
print(f"Le coût total : {cout_total}")
Sortie
Chemin de A(0) à F(5) : [0, 3, 5]
Le coût total : 19

Comparaison A* vs Dijkstra

CritèreDijkstraA*
Fonction de priorité\(g(v)\) (coût réel depuis la source)\(f(v) = g(v) + h(v)\)
Connaissance de la cibleAucune (exploration aveugle)Utilise une heuristique vers la cible
OptimalitéToujours optimal (poids positifs)Optimal si \(h\) est admissible
Cas particulierCas particulier de A* avec \(h=0\)Généralisation de Dijkstra
Analyse de complexité
  • Complexité temporelle : \(O((|S| + |A|) \log |S|)\) dans le pire cas (avec une file de priorité)
  • Complexité spatiale : \(O(|S|)\) pour stocker \(g\), \(f\) et les parents
  • Efficacité pratique : Bien meilleure que Dijkstra si l'heuristique est bien choisie
Cas particuliers
  • \(h = 0\) : A* devient Dijkstra
  • \(h\) non admissible : A* peut trouver une solution sous-optimale mais plus rapidement
  • \(h\) parfaite : \(h(v) = d^*(v)\), A* explore uniquement le chemin optimal

Sortie
// La sortie apparaîtra ici…
Prêt · Ctrl+Entrée pour exécuter

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.