Qual è la differenza tra backtracking e deep first search?
Qual è la differenza tra backtracking e deep first search?
Risposte:
Il backtracking è un algoritmo più generico.
La ricerca in profondità è una forma specifica di backtracking correlata alla ricerca di strutture ad albero. Da Wikipedia:
Si parte dalla radice (selezionando un nodo come radice nel caso del grafico) ed esplora il più possibile lungo ogni ramo prima di tornare indietro.
Utilizza il backtracking come parte dei suoi mezzi per lavorare con un albero, ma è limitato a una struttura ad albero.
Il backtracking, tuttavia, può essere utilizzato su qualsiasi tipo di struttura in cui è possibile eliminare parti del dominio, indipendentemente dal fatto che si tratti o meno di un albero logico. L'esempio Wiki utilizza una scacchiera e un problema specifico: puoi guardare una mossa specifica ed eliminarla, quindi tornare indietro alla prossima mossa possibile, eliminarla, ecc.
Penso che questa risposta a un'altra domanda correlata offra ulteriori approfondimenti.
Per me, la differenza tra backtracking e DFS è che il backtracking gestisce un albero implicito e DFS si occupa di uno esplicito. Sembra banale, ma significa molto. Quando lo spazio di ricerca di un problema viene visitato tornando indietro, l'albero implicito viene attraversato e sfoltito al centro di esso. Tuttavia, per DFS, l'albero / grafico di cui si occupa è costruito esplicitamente e casi inaccettabili sono già stati eliminati, ovvero eliminati, prima che venga eseguita qualsiasi ricerca.
Quindi, il backtracking è DFS per la struttura ad albero implicita, mentre DFS è il backtracking senza eliminazione.
Il backtracking viene in genere implementato come DFS più l'eliminazione della ricerca. Attraversi la profondità dell'albero di ricerca costruendo prima soluzioni parziali lungo il percorso. DFS a forza bruta può costruire tutti i risultati della ricerca, anche quelli che non hanno senso praticamente. Questo può anche essere molto inefficiente per costruire tutte le soluzioni (n! O 2 ^ n). Quindi, in realtà, come si fa DFS, è necessario eliminare anche soluzioni parziali, che non hanno senso nel contesto dell'attività effettiva, e concentrarsi sulle soluzioni parziali, che possono portare a soluzioni ottimali valide. Questa è l'attuale tecnica di backtracking: scarti le soluzioni parziali il prima possibile, fai un passo indietro e provi a trovare di nuovo l'ottimo locale.
Niente si ferma per attraversare l'albero dello spazio di ricerca utilizzando BFS ed eseguire la strategia di backtracking lungo il percorso, ma in pratica non ha senso perché dovresti memorizzare lo stato di ricerca strato per strato nella coda e la larghezza dell'albero cresce esponenzialmente all'altezza, quindi sprecheremmo molto spazio molto rapidamente. Ecco perché gli alberi vengono in genere attraversati utilizzando DFS. In questo caso lo stato di ricerca è memorizzato nello stack (stack di chiamate o struttura esplicita) e non può superare l'altezza dell'albero.
Di solito, una ricerca approfondita è un modo per iterare attraverso un vero grafo / struttura ad albero alla ricerca di un valore, mentre il backtracking sta iterando attraverso uno spazio problematico alla ricerca di una soluzione. Il backtracking è un algoritmo più generale che non è necessariamente correlato nemmeno agli alberi.
Direi che DFS è la forma speciale di backtracking; il backtracking è la forma generale di DFS.
Se estendiamo DFS a problemi generali, possiamo chiamarlo backtracking. Se usiamo il backtracking per problemi relativi ad albero / grafico, possiamo chiamarlo DFS.
Portano la stessa idea nell'aspetto algoritmico.
Secondo Donald Knuth, è lo stesso. Ecco il link nel suo articolo sull'algoritmo Dancing Links, che viene utilizzato per risolvere problemi "non ad albero" come N-queens e Sudoku solver.
IMHO, la maggior parte delle risposte sono in gran parte imprecise e / o senza alcun riferimento da verificare. Quindi lasciatemi condividere una spiegazione molto chiara con un riferimento .
Innanzitutto, DFS è un algoritmo generale di attraversamento (e ricerca) del grafo. Quindi può essere applicato a qualsiasi grafico (o anche foresta). Tree è un tipo speciale di Graph, quindi DFS funziona anche per tree. In sostanza, smettiamola di dire che funziona solo per un albero o simili.
Basato su [1], Backtracking è un tipo speciale di DFS utilizzato principalmente per il risparmio di spazio (memoria). La distinzione che sto per menzionare può sembrare confusa poiché in algoritmi Graph di questo tipo siamo così abituati ad avere rappresentazioni di liste di adiacenza e ad usare pattern iterativi per visitare tutti i vicini immediati ( per albero sono i figli immediati ) di un nodo , spesso ignoriamo che una cattiva implementazione di get_all_immediate_neighbors può causare una differenza nell'utilizzo della memoria dell'algoritmo sottostante.
Inoltre, se un nodo del grafo ha un fattore di ramificazione b e un diametro h ( per un albero questa è l'altezza dell'albero ), se memorizziamo tutti i vicini immediati ad ogni passo della visita di un nodo, i requisiti di memoria saranno big-O (bh) . Tuttavia, se prendiamo solo un singolo vicino (immediato) alla volta e lo espandiamo, la complessità della memoria si riduce a big-O (h) . Mentre il primo tipo di implementazione viene definito DFS , il secondo tipo è denominato Backtracking .
Ora vedi, se stai lavorando con linguaggi di alto livello, molto probabilmente stai effettivamente usando Backtracking sotto forma di DFS. Inoltre, tenere traccia dei nodi visitati per un insieme di problemi molto ampio potrebbe richiedere molto memoria; richiedendo un'attenta progettazione di get_all_immediate_neighbors (o algoritmi in grado di gestire la rivisitazione di un nodo senza entrare in un ciclo infinito).
[1] Stuart Russell e Peter Norvig, Artificial Intelligence: A Modern Approach, 3rd Ed
La profondità prima è un algoritmo per attraversare o cercare un albero. Vedi qui . Backtracking è un termine molto più ampio che viene utilizzato ovunque si formi un candidato alla soluzione e in seguito scartato tornando a uno stato precedente. Vedi qui . La prima ricerca in profondità utilizza il backtracking per cercare prima un ramo (candidato alla soluzione) e, se non riesce, cerca negli altri rami.
DFS descrive il modo in cui si desidera esplorare o attraversare un grafico. Si concentra sul concetto di andare il più in profondità possibile data la scelta.
Il backtracking, sebbene di solito implementato tramite DFS, si concentra maggiormente sul concetto di eliminazione dei sottospazi di ricerca poco promettenti il prima possibile.
In una ricerca approfondita , inizi dalla radice dell'albero e poi esplori il più lontano possibile lungo ogni ramo, quindi torni indietro a ciascun nodo genitore successivo e attraversi i suoi figli
Backtracking è un termine generalizzato per iniziare alla fine di un obiettivo e spostarsi gradualmente all'indietro, costruendo gradualmente una soluzione.
IMO, su qualsiasi nodo specifico del backtracking, provi prima a approfondire la ramificazione in ciascuno dei suoi figli, ma prima di diramare in qualsiasi nodo figlio, devi "cancellare" lo stato del figlio precedente (questo passaggio equivale a tornare indietro camminare fino al nodo padre). In altre parole, ogni stato dei fratelli non dovrebbe influenzarsi a vicenda.
Al contrario, durante il normale algoritmo DFS, di solito non hai questo vincolo, non è necessario cancellare (back track) lo stato dei fratelli precedenti per costruire il nodo di pari livello successivo.
Idea - Inizia da un punto qualsiasi, controlla se è l'endpoint desiderato, se sì, allora abbiamo trovato una soluzione altrimenti va a tutte le possibili posizioni successive e se non possiamo andare oltre, torna alla posizione precedente e cerca altre alternative che contrassegnano quella corrente il percorso non ci condurrà alla soluzione.
Ora backtracking e DFS sono 2 nomi diversi dati alla stessa idea applicata a 2 diversi tipi di dati astratti.
Se l'idea è applicata alla struttura dati della matrice, la chiamiamo backtracking.
Se la stessa idea viene applicata all'albero o al grafico, la chiamiamo DFS.
Il cliché qui è che una matrice potrebbe essere convertita in un grafico e un grafico potrebbe essere trasformato in una matrice. Quindi, applichiamo effettivamente l'idea. Se su un grafico lo chiamiamo DFS e se su una matrice lo chiamiamo backtracking.
L'idea in entrambi gli algoritmi è la stessa.
Il backtracking è solo una prima ricerca approfondita con condizioni di terminazione specifiche.
Considera l'idea di attraversare un labirinto in cui per ogni passo prendi una decisione, quella decisione è una chiamata allo stack di chiamate (che conduce la tua prima ricerca approfondita) ... se raggiungi la fine, puoi tornare sul tuo percorso. Tuttavia, se raggiungi un deadend, vuoi tornare da una certa decisione, in sostanza tornando da una funzione sul tuo stack di chiamate.
Quindi quando penso a tornare indietro mi preoccupo
Lo spiego nel mio video sul backtracking qui .
Di seguito è riportata un'analisi del codice di backtracking. In questo codice di backtracking voglio tutte le combinazioni che risulteranno in una certa somma o obiettivo. Pertanto, ho 3 decisioni che effettuano chiamate al mio stack di chiamate, ad ogni decisione posso scegliere un numero come parte del mio percorso per raggiungere il numero di destinazione, saltare quel numero o selezionarlo e selezionarlo di nuovo. E poi se raggiungo una condizione di risoluzione, il mio passo indietro è solo per tornare . Il ritorno è il passaggio di backtracking perché esce da quella chiamata nello stack di chiamate.
class Solution:
"""
Approach: Backtracking
State
-candidates
-index
-target
Decisions
-pick one --> call func changing state: index + 1, target - candidates[index], path + [candidates[index]]
-pick one again --> call func changing state: index, target - candidates[index], path + [candidates[index]]
-skip one --> call func changing state: index + 1, target, path
Base Cases (Termination Conditions)
-if target == 0 and path not in ret
append path to ret
-if target < 0:
return # backtrack
"""
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
"""
@desc find all unique combos summing to target
@args
@arg1 candidates, list of ints
@arg2 target, an int
@ret ret, list of lists
"""
if not candidates or min(candidates) > target: return []
ret = []
self.dfs(candidates, 0, target, [], ret)
return ret
def dfs(self, nums, index, target, path, ret):
if target == 0 and path not in ret:
ret.append(path)
return #backtracking
elif target < 0 or index >= len(nums):
return #backtracking
# for i in range(index, len(nums)):
# self.dfs(nums, i, target-nums[i], path+[nums[i]], ret)
pick_one = self.dfs(nums, index + 1, target - nums[index], path + [nums[index]], ret)
pick_one_again = self.dfs(nums, index, target - nums[index], path + [nums[index]], ret)
skip_one = self.dfs(nums, index + 1, target, path, ret)