Visszalépéses keresés (algoritmus)
A visszalépéses keresés (Backtracking) olyan esetekben használható, amikor a keresési tér fastruktúraként képzelhető el, amiben a gyökérből kiindulva egy csúcsot keresünk. Számtalan ilyen probléma található, a labirintusokban történő útkeresés, bizonyos mintaillesztési feladatok, a könyvtárszerkezetben történő keresés, egyes logikai játékok (pl. Rubik kocka) kirakása is vizzavezethető erre a problémára.
Az algoritmus lényege, hogy a kezdőpontból kiindulva megtesz egy utat, és ha valahol az derül ki, hogy már nem juthat el a célig, akkor visszalép egy korábbi döntési ponthoz, és ott más utat választ.
Az alap algoritmus kódja C++ nyelven:
// igazat ad, ha az útvonal nem a célba vezet
// ilyenkor step egy rossz lépés sorszáma
bool erroneous( const Path & path, int & step );
// módosítja az útvonalat úgy, hogy
// a step-edik lépésben (vagy az előtt) más
// irányt választ. Hamissal tér vissza, ha ez lehetetlen
bool increase( Path & path, int step );
// visszalépéses keresés
bool backtrack( Path & path ) {
int i;
while( erroneous( path, i ) {
if ( ! increase( path, i ))
return false;
}
return true;
}
Ha az útvonalak valamiképpen sorbarendezhetők, és tudjuk biztosítani, hogy az increase függvény későbbi utat generáljon minden esetben, akkor (véges problématérben) az algoritmus biztosan véget ér.
Ha az erroneous függvény ezen felül mindig a legutolsó rossz döntés sorszámát adja vissza, és az increase nem hagy ki utakat, akkor az algoritmus megtalálja az első jó megoldást.
Ha az út érvényessége és a következő lépés meghatározásához elég a pillanatnyi helyet ismerni, akkor az útvonal ábrázolható veremmel. Bizonyos feladatok könnyebben kezelhetők, ha ilyenkor a programnyelv beépített vermét használjuk, és egy rekurzívan hívott függvénynek paraméterként csak az útvonal utolsó elemét adjuk át.
Pl. Visszalépéses keresés bináris fában:
#include <string>
using namespace std;
// igazat ad, ha n célpont
bool isgoal( const Node & n );
// visszalépéses keresés bináris fában
string bintreesearch( Node * n ) {
string res="";
if ( !n ) return res;
if ( isgoal( *n ) ) return "goal";
res = bintreesearch( n->left );
if ( res != "" ) return res.insert( 0, "left " );
res = bintreesearch( n->right );
if ( res != "" ) return res.insert( 0, "right " );
return "";
}
A rekurzív megoldás átláthatóbbá teheti a kódot, de figyelni kell arra, hogy a programnyelv verme sem végtelen. Ha az utak hossza túlságosan nagy, az ilyenkor a program megszakadásához vezethet.
Ha a probléma nem fastruktúrával, hanem kört is tartalmazó gráffal írható le, akkor az érintett helyek halmazát is tárolni kell keresés közben. Erre szolgálnak a gráfkereső algoritmusok.