Ricerca nello spazio degli stati: BFS, DFS, UCS e A* con animazioni
Le basi dell’intelligenza artificiale in modo visivo: cos’è lo spazio degli stati, come funzionano BFS, DFS, UCS, la ricerca greedy e A*, cos’è un’euristica e che cosa c’entra l’apprendimento automatico.
Serie Basi di IA Parte 21 di 52
- Mappa dell’intelligenza artificiale: che cos’è ciascuna cosa e come si incastra tutto
- Breve storia dell’intelligenza artificiale: da Turing ai modelli linguistici
- Dati, modello, addestramento, inferenza: i concetti di base su un unico esempio
- Come leggere la notazione matematica: simboli, indici, sommatorie e quantificatori
- Insiemi e relazioni: i mattoncini con cui si costruiscono le definizioni nell’IA
- Funzioni e applicazioni: perché ogni modello di IA è una funzione
- Logica e dimostrazioni: le regole con cui si ragiona nell’IA
- Grafi e alberi: la struttura su cui poggiano la ricerca e le reti neurali
- Algebra lineare per l’apprendimento automatico: vettori, matrici e perché l’IA soprattutto moltiplica
- Derivate, gradiente e regola della catena: come un modello capisce in quale direzione imparare
- Discesa del gradiente: come impara un modello
- Che cos’è la probabilità: dal lancio di un dado agli assiomi di Kolmogorov
- Probabilità condizionata e teorema di Bayes: come cambiare una stima quando scopri qualcosa di nuovo
- Variabile aleatoria e funzione di ripartizione: una sola funzione che dice tutto sulla distribuzione
- Funzione di densità e funzione di massa: perché una variabile continua ha probabilità zero in un punto
- Valore atteso, varianza e covarianza: dove si trova una distribuzione, quanto è larga e che cosa è legato a che cosa
- Panoramica delle distribuzioni di probabilità: Bernoulli, binomiale, Poisson, uniforme, normale ed esponenziale
- Legge dei grandi numeri e teorema del limite centrale: perché le medie si comportano in modo così prevedibile
- Statistica e stima dei parametri: massima verosimiglianza, MAP e intervalli di confidenza
- Teoria dell’informazione: entropia, entropia incrociata e divergenza KL — da dove vengono le funzioni di perdita
- Ricerca nello spazio degli stati: BFS, DFS, UCS e A* con animazioni
- Giochi e avversari: minimax, potatura alfa-beta e Monte Carlo tree search
- Soddisfacimento di vincoli: backtracking, propagazione dei vincoli e AC-3 su un sudoku e una mappa delle regioni ceche
- Logica e sistemi basati sulla conoscenza: concatenazione in avanti e all’indietro, risoluzione e sistemi esperti
- Pianificazione: STRIPS, il mondo dei blocchi, euristiche dal rilassamento e pianificazione negli agenti IA
- Ricerca locale e ricottura simulata: un commesso viaggiatore tra i capoluoghi di regione cechi
- Algoritmi genetici: popolazione, incrocio, mutazione e selezione su uno zaino per l’escursione
- Strategie evolutive e programmazione genetica: dalla regola di un quinto alla legge di Keplero
- Ottimizzazione con colonie di formiche: come una traccia di feromone trova un percorso breve
- Ottimizzazione a sciame di particelle: inerzia, esperienza propria ed esperienza dello sciame
- Neuroevoluzione: evoluzione dei pesi e della forma delle reti neurali e l’algoritmo NEAT
- Insiemi fuzzy e funzioni di appartenenza: come scrivere «un po’ caldo»
- Logica fuzzy e probabilità: perché «un po’ caldo» non significa «probabilmente caldo»
- Logica fuzzy: t-norme, implicazioni e regole SE–ALLORA
- Controllo fuzzy: Mamdani e Sugeno, dal ventilatore al pendolo inverso su un carrello
- Clustering fuzzy: l’algoritmo fuzzy c-means e i punti tra i cluster
- Sistemi neuro-fuzzy (ANFIS): regole fuzzy che imparano dai dati
- Catene di Markov: matrice di transizione, distribuzione stazionaria e PageRank
- Modelli di Markov nascosti: l’algoritmo di Viterbi e il casinò disonesto
- Reti bayesiane: perché l’erba è bagnata e che cosa significa per l’irrigatore
- Filtro di Kalman: come stimare da misure imprecise dove si trova un’auto e a che velocità va
- Tipi di apprendimento automatico: supervisionato, non supervisionato, semi-supervisionato e altri
- Prompt, contesto e RAG: come dare a un modello le informazioni giuste
- Autoencoder variazionali (VAE): immagini nuove da uno spazio latente continuo
- Reti generative avversarie (GAN): il falsario contro il detective
- Modelli di diffusione: come il rumore puro diventa un’immagine
- Processi decisionali di Markov: stati, azioni, ricompense ed equazioni di Bellman
- Q-learning e DQN: imparare il valore delle azioni, dalla tabella alla rete neurale
- Policy gradient e PPO: imparare direttamente la politica
- Come funziona un agente IA: un modello linguistico in un ciclo con strumenti
- Programmare con agenti IA: incarico, test e controllo
- Valutazione e sicurezza dei sistemi di IA: come misurare e come sorvegliare
Programma completo della serie 52 di 83 argomenti pubblicati
- Come formulare un compito come la ricerca di un percorso in un grafo degli stati.
- In che cosa si differenziano BFS, DFS, UCS, la ricerca greedy e A* — e perché in fondo è una sola riga di codice.
- Che cos’è un’euristica, quando è ammissibile e perché con essa A* trova il percorso più economico con il minimo lavoro.
Utile sapere prima: Mappa dell’intelligenza artificiale: che cos’è ciascuna cosa e come si incastra tutto, Come leggere la notazione matematica: simboli, indici, sommatorie e quantificatori
Quando oggi si parla di intelligenza artificiale, la maggior parte delle persone pensa alle reti neurali e ai grandi modelli linguistici. Eppure, molto prima che le macchine imparassero dai dati, sapevano già cercare. La navigazione su una mappa, una mossa a scacchi, il piano di un robot in un magazzino — tutto questo è la ricerca di un percorso in uno spazio di possibilità. La ricerca nello spazio degli stati è il primo capitolo di ogni manuale di intelligenza artificiale, e le sue idee le ritroverai anche nell’apprendimento automatico: euristiche, ottimizzazione, ricerca della soluzione migliore.
Qui trovi ogni concetto due volte: prima una definizione matematica precisa, subito sotto la stessa cosa in parole semplici. Tutte le animazioni sono l’esecuzione reale dell’algoritmo — niente è disegnato a mano.
1. Che cos’è lo spazio degli stati
Prima di cercare qualsiasi cosa, dobbiamo descrivere il problema in modo che un computer lo capisca. A questo serve il problema di ricerca:
Definizione — problema di ricerca
Un problema di ricerca è una sestupla P = (S, s0, A, T, G, c), dove
- S è l’insieme degli stati,
- s0 ∈ S è lo stato iniziale,
- A(s) è l’insieme delle azioni applicabili nello stato s,
- T : S × A → S è la funzione di transizione; lo stato s′ = T(s, a) è un successore dello stato s,
- G ⊆ S è l’insieme degli stati obiettivo,
- c : S × A → ℝ+ è il costo di un’azione, sempre positivo.
Uno stato è un’«istantanea del mondo» in un dato momento — dove si trova l’omino nel labirinto, come sono disposte le tessere del rompicapo. Un’azione è una mossa che puoi fare. La funzione di transizione dice dove ti porta quella mossa. L’obiettivo è dove vuoi arrivare, e il costo dice quanto ti costa ogni mossa — tempo, carburante, passi.
Definizione — percorso, soluzione, ottimo
Un percorso di lunghezza k è una sequenza s0, a1, s1, …, ak, sk, dove si = T(si−1, ai).
Il costo del percorso è la somma dei costi delle azioni: g = ∑i=1k c(si−1, ai).
Una soluzione è un percorso che termina in un obiettivo: sk ∈ G. Una soluzione ottima è una soluzione di costo minimo; tale costo si indica con C*.
Un percorso è un elenco di mosse una dopo l’altra. Una soluzione è un percorso che arriva all’obiettivo. La soluzione ottima è la più economica di tutte — ed è proprio quella che di solito vogliamo trovare.
Gli stati collegati dalle azioni formano un grafo orientato: gli stati sono i vertici, le azioni archi con peso c. Cercare una soluzione significa quindi trovare nel grafo un percorso da s0 a uno dei vertici di G. L’esempio classico è il gioco dell’8: tessere da 1 a 8 in una cornice 3 × 3 e una casella vuota. Uno stato è la disposizione delle tessere, un’azione lo spostamento della casella vuota e l’obiettivo le tessere in ordine:
Sembra innocuo, ma il gioco dell’8 ha 181 440 stati raggiungibili e il suo fratello maggiore, il gioco del 15, più di diecimila miliardi. Per questo il grafo non lo costruiamo mai tutto in anticipo. L’algoritmo lo scopre man mano — dalla funzione di transizione calcola soltanto i successori dello stato in cui si trova.
2. Il problema: attraversare un labirinto
Vedremo tutti gli algoritmi sullo stesso problema. Abbiamo un labirinto di 9 × 14 caselle e vogliamo andare dalla partenza all’arrivo spendendo il meno possibile. Prima di tutto, cosa significa ogni casella:
- Casella libera — entrarci costa 1
- Fango — si può attraversare, ma entrarci costa 5
- Muro — non ci si può entrare
- S Partenza (start) — lo stato iniziale, la casella (0, 0)
- G Arrivo (goal) — la casella (8, 13)
- Casella esplorata — l’algoritmo l’ha espansa
- Percorso trovato — il risultato dell’algoritmo
Formulazione formale
Scriviamo una casella come coppia (r, s) — riga r ∈ {0, …, 8}, colonna s ∈ {0, …, 13}. Indichiamo con Z l’insieme dei muri e con B l’insieme delle caselle di fango.
- Stati: S = {(r, s) : (r, s) ∉ Z} — tutte le caselle tranne i muri.
- Stato iniziale: s0 = (0, 0). Obiettivo: G = {(8, 13)}.
- Azioni: A = {↑, →, ↓, ←}. Un’azione è applicabile in uno stato solo se porta a una casella dentro la griglia che non sia un muro.
- Transizione: T((r, s), →) = (r, s + 1), e analogamente per le altre direzioni.
- Costo: c(x, a) = 5 se T(x, a) ∈ B, altrimenti 1 — si paga per la casella in cui si entra.
- Compito: trovare una soluzione di costo minimo C*.
L’omino sta in alto a sinistra e vuole arrivare in basso a destra. Si muove solo su, giù, a sinistra e a destra, non passa attraverso i muri e ogni passo gli costa 1 — tranne un passo nel fango, che costa 5. Cerchiamo il percorso che gli costerà meno.
Il fango è messo apposta sul tragitto più diretto. Vedremo che non tutti gli algoritmi se ne accorgono: alcuni contano solo i passi, altri il costo reale.
3. Lo scheletro comune a tutti gli algoritmi
Tutti gli algoritmi dell’articolo hanno lo stesso scheletro. Si differenziano solo per una decisione.
Definizione — frontiera, stati chiusi, espansione
La frontiera (frontier) F ⊆ S è l’insieme degli stati che l’algoritmo ha già scoperto ma non ancora espanso.
Gli stati chiusi E ⊆ S sono gli stati già espansi.
Espandere uno stato s significa calcolare tutti i suoi successori {T(s, a) : a ∈ A(s)} e aggiungere a F quelli che non sono in E.
La frontiera è l’elenco dei posti dove sai che porta un percorso, ma dove non sei ancora andato a guardare. Gli stati chiusi sono i posti dove sei già stato — grazie a loro non giri in tondo. Espandere uno stato significa mettersi lì e guardarsi intorno per vedere dove si può andare dopo.
frontier = [start]
closed = {}
finché frontier non è vuota:
state = choose_from(frontier) # ← è qui che gli algoritmi si differenziano
se state è l’obiettivo: fatto
aggiungi state a closed
per ogni successore di state:
se non è ancora chiuso: aggiungilo a frontier
Tutta la differenza tra gli algoritmi sta in una sola riga: quale stato della frontiera scegliamo come prossimo. Una coda, una pila o una coda di priorità in base a un certo numero. Tutto il resto si regge su questo.
4. Ricerca in ampiezza (BFS)
Definizione — BFS
La frontiera F è una coda (FIFO): si espande lo stato entrato in F per primo.
Conseguenza: gli stati vengono espansi in ordine di profondità d(s) — il numero di azioni da s0. Per questo BFS trova la soluzione con il minor numero di azioni.
Se b è il fattore di ramificazione (il numero massimo di successori di uno stato) e d la profondità della soluzione meno profonda, BFS genera al massimo b + b2 + … + bd = O(bd) stati e ne tiene altrettanti in memoria.
Chi arriva primo viene servito per primo — come la fila in un negozio. Prima esplori tutto ciò che è a un passo dalla partenza, poi tutto ciò che è a due passi, poi a tre. Si propaga come un’onda sull’acqua, ed è per questo che il primo percorso che arriva all’obiettivo ha il minor numero di passi. Il problema: devi ricordarti tutta l’onda, e cresce terribilmente in fretta.
Nel labirinto l’onda si vede bene — si allarga in tutte le direzioni finché non incontra l’arrivo:
Nota però che il percorso passa dritto nel fango. BFS conta le azioni, non il costo — per lui un passo nel fango vale quanto un passo sull’asciutto. Il percorso più corto è risultato di 25 passi, ma con costo 37. BFS è ottimo solo quando tutte le azioni hanno lo stesso costo.
from collections import deque
def bfs(start, goal, successors):
frontier = deque([start])
parent = {start: None}
while frontier:
state = frontier.popleft() # coda: il primo a entrare è il primo a uscire
if state == goal:
return path(parent, goal)
for nxt in successors(state):
if nxt not in parent:
parent[nxt] = state
frontier.append(nxt)
return None
def path(parent, state):
result = []
while state is not None:
result.append(state)
state = parent[state]
return result[::-1]
5. Ricerca in profondità (DFS)
Definizione — DFS
La frontiera F è una pila (LIFO): si espande lo stato entrato in F per ultimo.
Se m è la profondità massima dello spazio degli stati, DFS tiene in memoria solo il ramo corrente e i suoi fratelli: O(b · m) stati. Il tempo nel caso peggiore è O(bm).
DFS non è ottimo e in uno spazio infinito non è nemmeno completo — può scendere all’infinito.
È come muoversi in un labirinto tenendo sempre la mano destra sul muro: scegli un corridoio e lo segui fino in fondo. Solo in un vicolo cieco torni all’ultima diramazione e ne provi un’altra. Serve poca memoria — ricordi solo il percorso che stai facendo. Ma il percorso più corto lo troverai solo per caso.
Nell’albero si vede dall’ordine: A, B, D, H, I — e solo dopo E. Nel labirinto:
Qui DFS è stato fortunato ed è arrivato all’obiettivo senza esplorare molte caselle. Il percorso però è inutilmente lungo — 31 passi invece di 25 e costo 59.
Definizione — approfondimento iterativo (IDDFS)
Per ℓ = 0, 1, 2, … esegui un DFS che non espande stati più profondi di ℓ. Fermati alla prima soluzione trovata.
Gli stati a profondità i vengono generati (d − i + 1) volte, in totale ∑i=1d (d − i + 1) · bi = O(bd) — come BFS, ma con una memoria di soli O(b · d).
Avvii DFS, ma gli vieti di andare più in profondità di un passo. Non ha trovato niente? Allora due passi, poi tre… I livelli superiori li ripercorri più e più volte, ma la maggior parte degli stati sta sempre nel livello più profondo, quindi la ripetizione non costa quasi nulla. Ottieni il percorso più corto come con BFS e la poca memoria di DFS.
6. Ricerca a costo uniforme (UCS)
Definizione — UCS
Per uno stato n, indichiamo con g(n) il costo del percorso più economico trovato finora da s0 a n.
La frontiera è una coda di priorità ordinata per g: si espande lo stato con il g(n) minimo.
Se tutti i costi delle azioni sono ≥ ε > 0, UCS espande gli stati in ordine non decrescente di g, ed è quindi completo e ottimo. Il numero di stati espansi è O(b1 + ⌊C*/ε⌋).
Invece dei passi conti i soldi. Riparti sempre dal posto che finora hai raggiunto spendendo meno. Quando arrivi all’obiettivo, sai con certezza che non si può spendere meno — tutto ciò che costava meno l’hai già esplorato. Su tutti i grafi con costi positivi lo fa il noto algoritmo di Dijkstra; UCS ne è la versione che si ferma all’obiettivo.
UCS ha aggirato il fango. Ha trovato un percorso con lo stesso numero di passi di BFS, ma con costo 25 invece di 37 — ed è garantito il più economico, cioè C* = 25. Il prezzo è che cerca alla cieca in tutte le direzioni: non sa dove si trovi l’obiettivo, quindi si allarga anche nella direzione opposta.
7. Euristica: stimare la strada verso l’obiettivo
Gli algoritmi visti finora sono non informati — dell’obiettivo sanno solo che lo riconosceranno quando ci metteranno piede. Gli algoritmi informati ricevono in più una stima di quanto manca.
Definizione — euristica
Indichiamo con h*(n) il costo del percorso ottimo dallo stato n all’obiettivo più vicino. Un’euristica è una funzione h : S → ℝ≥0 che stima h*(n), con h(n) = 0 per n ∈ G.
h è ammissibile (admissible) se non sovrastima mai: ∀n ∈ S : h(n) ≤ h*(n).
h è consistente se per ogni stato n, azione a e successore n′ = T(n, a) vale h(n) ≤ c(n, a) + h(n′). Ogni euristica consistente è ammissibile.
Un’euristica è una stima ragionata — come quando in una città sconosciuta sai che la stazione è «da qualche parte a nord». Ammissibile significa ottimista: non dice mai che è più lontano di quanto sia in realtà. Consistente significa che la stima non si contraddice dopo un passo — non può migliorare più di quanto ti è costato quel passo.
Nel nostro labirinto viene naturale la distanza di Manhattan — il numero di caselle in orizzontale più in verticale, come quando cammini per le strade ad angolo retto di Manhattan:
h((r, s)) = |r − rG| + |s − sG| = |r − 8| + |s − 13|
È consistente (e quindi ammissibile): un’azione cambia h al massimo di 1 e costa almeno 1.
Conti di quante caselle l’obiettivo è più in basso e di quante più a destra, e le sommi. Muri e fango possono solo allungare o rincarare il percorso, mai accorciarlo — quindi la stima è sempre ottimista, esattamente come deve essere.
8. Ricerca greedy
Definizione — ricerca greedy
La frontiera è una coda di priorità secondo f(n) = h(n): si espande lo stato che secondo la stima è più vicino all’obiettivo. Il costo accumulato g(n) non viene considerato.
La ricerca greedy non è ottima; con il controllo degli stati ripetuti è completa in uno spazio finito.
Vai sempre dove sembra più vicino all’obiettivo, e non ti importa affatto di quanto ti sia costato il percorso finora. Punta in fretta nella direzione giusta, ma ci si lascia ingannare facilmente — per esempio da un fango che sta «nella direzione giusta».
La ricerca greedy esplora poche caselle, ma va dove sembra più vicino e finisce su un percorso lungo e costoso — 31 passi, costo 59.
9. A*: il meglio dei due mondi
Definizione — A*
La frontiera è una coda di priorità secondo f(n) = g(n) + h(n) — la stima del costo dell’intero percorso dalla partenza all’obiettivo passando per n.
Teorema. Se h è consistente, i valori di f lungo ogni percorso non decrescono, A* espande gli stati in ordine non decrescente di f e il primo stato obiettivo espanso si trova su un percorso ottimo di costo C*. Nel farlo espande ogni stato con f(n) < C* e nessuno con f(n) > C*.
Per h ≡ 0, A* coincide con UCS; senza g sarebbe la ricerca greedy.
Per ogni posto, A* somma quanto hai già pagato e quanto, secondo la stima, pagherai ancora. Grazie alla prima parte non si lascia attirare da una scorciatoia costosa; grazie alla seconda non perde tempo su percorsi che si allontanano dall’obiettivo. Se la stima è ottimista, trova sempre il percorso più economico — ed esplora solo ciò che deve esplorare.
Stesso costo 25 di UCS, ma A* ha esplorato 37 caselle invece di 49. In un piccolo labirinto è una differenza di poche caselle; su una mappa con milioni di incroci è la differenza tra una risposta immediata e un minuto di attesa. Per questo A* (e le sue varianti) lo trovi nei navigatori, nei videogiochi e nella pianificazione del movimento dei robot.
import heapq
def a_star(start, goal, successors, h):
frontier = [(h(start), 0, start)] # (f, g, stato)
g = {start: 0}
parent = {start: None}
while frontier:
_, cost, state = heapq.heappop(frontier)
if state == goal:
return path(parent, goal), cost
if cost > g[state]:
continue # voce obsoleta, ne conosciamo già una più economica
for nxt, step in successors(state):
new = cost + step
if new < g.get(nxt, float("inf")):
g[nxt] = new
parent[nxt] = state
heapq.heappush(frontier, (new + h(nxt), new, nxt))
return None
10. Confronto tra gli algoritmi
Definizione — proprietà di un algoritmo
Un algoritmo è completo se, per ogni problema che ha soluzione, restituisce una soluzione.
Un algoritmo è ottimo se la soluzione restituita ha costo C*.
b — fattore di ramificazione, d — profondità della soluzione meno profonda, m — profondità massima dello spazio. O(·) indica come crescono il tempo o la memoria con questi parametri.
Completo = se il percorso esiste, lo trova. Ottimo = trova il più economico. O(bd) significa «con ogni passo in più in profondità il lavoro si moltiplica» — per questo la memoria conta così tanto.
| Algoritmo | Completo | Ottimo | Tempo | Memoria | Nel nostro labirinto |
|---|---|---|---|---|---|
| BFS | sì | a costi uguali | O(bd) | O(bd) | 75 caselle, costo 37 |
| DFS | no* | no | O(bm) | O(b·m) | 32 caselle, costo 59 |
| IDDFS | sì | a costi uguali | O(bd) | O(b·d) | — |
| UCS | sì | sì | O(b1+⌊C*/ε⌋) | O(b1+⌊C*/ε⌋) | 49 caselle, costo 25 |
| Greedy | no* | no | O(bm) | O(bm) | 33 caselle, costo 59 |
| A* | sì | sì** | dipende dall’euristica | dipende dall’euristica | 37 caselle, costo 25 |
* In uno spazio finito con il controllo degli stati ripetuti trovano sempre una soluzione. ** Con un’euristica consistente.
11. Ricerca locale: quando il percorso non conta
In alcuni problemi il percorso non ci interessa — vogliamo solo un buon stato finale. Un orario scolastico, la disposizione dei componenti su una scheda, la regolazione dei parametri.
Definizione — ricerca locale
Sono date una funzione obiettivo f : S → ℝ e un intorno N(s) ⊆ S di ogni stato. Cerchiamo s* = argmaxs ∈ S f(s).
Uno stato s è un massimo locale se f(s) ≥ f(s′) per ogni s′ ∈ N(s), e un massimo globale se ciò vale per ogni s′ ∈ S.
Hill climbing: ripeti s ← argmaxs′ ∈ N(s) f(s′) finché f cresce.
La funzione obiettivo dice quanto è buona una soluzione — più è alta, meglio è. L’intorno sono le soluzioni che ottieni con una piccola modifica. Lo scalatore sta su una collina nella nebbia, vede solo un po’ intorno a sé e va sempre dove si sale. Quando tutto intorno scende, si ferma — anche se poco lontano c’è una montagna molto più alta.
Per non restare bloccati si usano il riavvio casuale (far partire lo scalatore da punti diversi), gli algoritmi genetici, che incrociano e fanno mutare un’intera popolazione di soluzioni, oppure la ricottura simulata:
Ricottura simulata
Scegli un vicino casuale s′ ∈ N(s) e calcola Δ = f(s′) − f(s). Se Δ > 0, passa a s′; altrimenti passaci con probabilità eΔ/T, dove la «temperatura» T scende gradualmente verso zero.
All’inizio lo scalatore si concede ogni tanto di andare in discesa — così passa da una vetta piccola a una più grande. Più a lungo cerca, più diventa prudente, finché alla fine sale soltanto.
Ed è qui che il cerchio si chiude con l’apprendimento automatico. Addestrare una rete neurale è una ricerca locale nello spazio dei suoi pesi θ: la discesa del gradiente ripete θ ← θ − η ∇L(θ) — un passo di ampiezza η nella direzione in cui la funzione di errore L scende più rapidamente. È uno scalatore a testa in giù che, invece della vetta più alta, cerca la valle più bassa — e si scontra con gli stessi problemi: minimi locali e plateau.
12. Riepilogo
- Un problema di ricerca è una sestupla (S, s0, A, T, G, c). Una soluzione è un percorso nel grafo degli stati, la soluzione ottima quella più economica.
- Gli algoritmi differiscono solo per quale stato scelgono dalla frontiera: coda (BFS), pila (DFS), g minimo (UCS), h minimo (greedy), g + h minimo (A*).
- BFS trova il minor numero di passi, UCS il costo più basso, A* il costo più basso esplorando il meno possibile — purché abbia un’euristica consistente.
- Quando non conta il percorso ma il risultato, entra in gioco la ricerca locale — e da lì è solo un passo verso la discesa del gradiente e l’apprendimento automatico.
Vuoi mettere le mani sugli algoritmi? Fai scrivere a un agente IA la tua visualizzazione — e poi cerca nel codice quell’unica riga in cui si differenziano.
La vedi diversamente? Scrivi a support@coden-app.com.