background image

Algorytmy z powrotami 

 

Algorytmy z powrotami są wykorzystywane do rozwiązywania 
problemów, w których z określonego zbioru jest wybierana sekwencja 
obiektów tak, aby spełniała ona określone kryteria

 
Klasycznym przykładem jest rozwiązanie problemu n-królowych. 
Zadaniem jest ustawienie n-królowych na szachownicy 

×

  n w taki 

sposób, aby się wzajemnie nie szachowały.  
Sekwencją w tym problemie jest n pozycji, na których są umieszczone 
królowe, zbiorem dla każdego wyboru jest n

możliwych pól na 

szachownicy. Kryterium jest takie, że królowe nie mogą się 
wzajemnie szachować. 
 
Algorytmy z powrotami są zmodyfikowanym przeszukiwaniem 
drzewa ( z korzeniem ) w głąb.  Na  początku  odwiedzamy  korzeń, a 
poźniej po przejściu do węzła przeglądane są wszystkie węzły 
potomne.  
 
Generalnie przeszukiwanie nie wymaga określonego porządku 
odwiedzania węzłów, ale wygodniej jest gdy przeszukiwane są węzły 
od lewej do prawej. 
 
Przykład przeszukiwania drzewa w głąb z węzłami ponumerowanymi 
w kolejności ich odwiedzania: 

 

background image

Węzły są ponumerowane w kolejności ich odwiedzania. Jak widać 
podczas wyszukiwania w głąb przechodzi się po ścieżce tak głęboko, 
jak jest to możliwe, aż do osiągnięcia  ślepego zaułka. Następnie 
wracamy do węzła z niodwiedzonymi węzłami potomnymi i znów 
przechodzimy w głąb tak daleko, jak jest to możliwe. 
 
Rozważmy ustawienie 4 królowych na szachownicy 4 

× 4. 

 
Problem można rozwiązać przez ustawienie królowych w kolejnych 
wierszach i sprawdzanie, która kombinacja kolumn daje prawidłowe 
rozwiązanie. Daje to 4 

× 4 × 4 × 4 = 256 potencjalnych rozwiazań. 

 
Można tworzyć potencjalne rozwiązania przez tworzenie drzewa: 
w węzłach drzewa z poziomu 1 będą zapisane kolumny wybrane dla 
pierwszej królowej,  w węzłach poziomu 2 wybrane kolumny dla 
drugiej królowej, etc.    
 
Ścieżka od węzła głównego do liścia jest potencjalnym rozwiazaniem.  
Liść  to jest węzeł bez węzłów potomnych.  Drzewo takie nazywamy 
drzewem przestrzeni stanów
 
Fragment drzewa przestrzeni stanów pokazano poniżej: 

 

background image

Całe drzewo ma 256 liści, po jednym dla każdego potencjalnego 
rozwiązania. W każdym węźle przechowywana jest para liczb <i,j>, 
oznaczająca, że królowa z wiersza i jest umieszczona w kolumnie j
 
Aby określić rozwiązanie, węzły są odwiedzane zgodnie z metodą 
przeszukiwania w głąb, w którym węzły pochodne są odwiedzane od 
strony lewej do prawej. 
 
Pierwsze sprawdzane scieżki to: 
    [<1.1><2.1><3.1><4.1>] 
    [<1.1><2.1><3.1><4.2>] 
    [<1.1><2.1><3.1><4.3>] 
    [<1.1><2.1><3.1><4.4>] 
    [<1.1><2.1><3.2><4.1>] 
 

Algorytm z powrotami jest algorytmem, w którym po zorientowaniu 
się,  że węzeł prowadzi do ślepego zaułka, wracamy do węzła 
nadrzędnego i kontynuujemy wyszukiwanie od następnego węzła.   

 
Węzeł nazywamy nieobiecującym,  gdy w czasie jego odwiedzania 
można określić,  że nie może on doprowadzić do rozwiązania 
(przykład poniżej). 

 

W przeciwnym razie węzeł jest nazywany obiecującym. 
 
Algorytm z powrotami polega na wykonywaniu przeszukiwania w 
głąb drzewa przestrzeni stanów, aby sprawdzić czy węzeł jest 
obiecujący, czy nie. Jeżeli węzeł nie jest obiecujący, wracamy do 
węzła nadrzędnego. 

background image

Ogólny algorytm z powrotami: 
 
void checknode (node v) 

  node u; 

 

  if (promising(v)) 
     if(istnieje rozwiązanie dla v) 

        drukuj rozwiązanie; 

     else 

        for(każdy węzeł pochodny u węzła v) 

            checknode(u); 


 
Algorytm z powrotami jest identyczny jak przeszukiwanie w głąb, 
poza tym, że węzły pochodne są odwiedzane tylko w przypadku, gdy 
węzeł macierzysty jest obiecujący i nie znaleziono w nim rozwiązania 
 
Dla problemu n-królowych funkcja promising  zwraca  false  , jeżeli 
węzeł i dowolny z jego przodków oznaczają umieszczenie królowej w 
tej samej kolumnie lub przekątnej (oznaczenie x na rys.). 

 

 

background image

 

 
 
Algorytm z powrotami sprawdza 27  węzłów w celu odszukania 
rozwiązania, bez zastosowania tego algorytmu trzeba sprawdzić  155 
wezłów w celu odszukania tego samego rozwiązania. 
 
Nieefektywność w ogólnym algorytmie z powrotami (procedura 
checknode) wynika z faktu, że sprawdzamy czy węzeł jest obiecujący, 
po przekazaniu go do procedury. Rekordy aktywacji wezłów 
nieobiecujących są niepotrzebnie odkładane na stos rekordów 
aktywacji.  
 
 
Algorytm ze sprawdzeniem czy wezeł jest obiecujący, przed 
wywołaniem rekurencyjnym, wyglądałby następujaco: 
 
 
 

background image

 
void expand (node v) 

  node u; 

 

  for (każdy węzeł pochodny u węzła v) 

       if (promising(u)) 

           if (istnieje rozwiazanie dla u) 

               drukuj rozwiazanie; 

           else 

               expand(u); 

 
Wersja poprzednia jest łatwiejsza do zrozumienia, gdyż wszystkie 
operacje są wykonywane w checknode  tzn. : sprawdzanie czy węzeł 
jest obiecujący; jeżeli jest obiecujący to czy zawiera rozwiązanie; 
drukowanie rozwiązania. 
 
 

Problem n-królowych 

 
 
Funkcja sprawdzająca, czy węzeł jest obiecujący, musi sprawdzać, czy 
dwie królowe są w tej samej kolumnie lub na tej samej przekątnej. 
 
Sprawdzenie kolumny to:            
                                               col(i) = col(k)   
 
gdzie col(i) jest kolumną w której jest umieszczona królowa z i-tego 
wiersza. 
 
Sprawdzenie przekątnej to: 
 
            col(i) - col(k) =  k      lub       col(i) - col(k) =  i 
 
 
 
 

background image

Przykładowo: 

 

 

col(6) – col(3) = 4-1 = 3 = 6-3 

col(6) – col(2) = 4-8 = -4 = 2-6 

 
 

Algorytm z powrotami dla problemu n-królowych 
 
Problem: umieść  n królowych na szachownicy w taki sposób, żeby 
żadne dwie królowe nie znalazły się w tym samym wierszu, tej samej 
kolumnie oraz na tych samych przekątnych. 
 
Dane wejściowe: dodatnia liczba całkowita n
 
Wynik: wszystkie możliwe sposoby na umieszczenie n królowych na 
szachownicy  n

×

n tak, aby się wzajemnie nie szachowały. Każdy 

wynik cząstkowy składa się z tablicy liczb całkowitych  col
indeksowanych od 1 do n, gdzie col(i) jest kolumną, w której 
umieszczona została królowa z wiersza i
 
 
 

background image

void queens (index i) 

 index j; 

 

 if (promising(i) ) 

    if(i= = n) 

     cout<< od col[i] do col[n]; 

    else 
     for(j=1;j<=n;j++){//Sprawdzenie czy królow 

         col[i+1] = j; //w i+1-tym wierszu moze 

         queens(i+1);  //byc ustawiona w kazdej 

                       //z n kolumn 

            } 

}  

 

bool promising (index i) 

   index k; 

   bool switch; 

 
   k=1; 

   switch = true;    //Sprawdź czy jakas     

                     //krolowa szachuje królową 

  while(k<i && switch){ //w i-tym wierszu 

  if(col[i]==col[k] || abs(col[i]-col[k])==i-k) 

     switch = false; 

  k++; 

  } 

return switch; 


 
Algorytm powyższy tworzy wszystkie rozwiązania problemu n-
królowych. Przerobienie programu tak, aby zatrzymywał się po 
znalezieniu pierwszego rozwiazania, jest proste. 
 
W analizie algorytmu należy określić ilość sprawdzonych węzłów 
jako funkcję wartości n, czyli liczby królowych. Górną granicę liczby 
węzłów w drzewie przestrzeni stanów można dośc łatwo policzyć.  

background image

Drzewo zawiera 1 węzeł na poziomie 0, n węzłów na poziomie 1, n

węzłów na poziomie 2 oraz n

n

 na poziomie n

Całkowita liczba węzłów wynosi 
 
                1+n+n

2

+n

3

+…+n

n

 = (n

n+1

 – 1) / (n – 1) 

 
Przykladowo, dla n=8 mamy 
 
                         (8

8+1

 – 1) / (8 – 1) = 19 173 961 węzłów 

 
Analiza ta jest nie w pełni użyteczna bo zadaniem algorytmu z 
powrotami jest uniknięcie sprawdzania wielu z tych węzłów. 
 
Można również określić górną granicę ilości węzłów obiecujących 
(dla  n=8). Pierwsza królowa może być umieszczona w dowolnej z 
ośmiu kolumn, druga może być umieszczona w jednej z siedmiu 
kolumn. Po ustawieniu drugiej królowej dla trzeciej zostanie do 
wyboru sześć kolumn. Dlatego mamy co najwyżej: 
 
              1 + 8 + 8

×7 + 8×7×6 + 8×7×6×5 + … + 8! = 109 601 

obiecujących węzłów. 
 
Ogólnie dla dowolnego n mamy co nawyżej  
    
            1 + n(n-1) + n(n-1)(n-2) + … + n!   obiecujących węzłów. 
 
Analiza ta nie jest pełna, gdyż po pierwsze nie bierze pod uwagę 
sprawdzania przekątnych, po drugie całkowita liczba odwiedzanych 
węzłów zawiera zarówno węzły obiecujące, jak i nieobiecujące. 
 
Najprostszą metodą byloby uruchomienie programu na komputerze i 
zliczanie odwiedzanych węzłów. 
 
 
 
 
 

background image

 
 

  n     Algorytm A            Algorytm B                       Algorytm z powrotami                  
________________________________________________________________ 
       Liczba węzłów     Liczba potencjalnych      Liczba węzłów         Liczba 
        sprawdzanych          rozwiązań n!                 sprawdzanych      znalezionych       
       (bez powrotów)       (rozne kolumny)            (z powrotami)     węzłów obiec.   
________________________________________________________________ 
 4             341                                24                               61                          17 
 
 8         19 173 961                   40 320                       15 721                      2057 
 
12          9.73

×10

12

                   4.79

×10

8

                    1.01

×10

7

              8.56

×10

5

 

 
14         1.20

×10

16

                    8.72

×10

10

                   3.78

×10

8

              2.74

×10

7

 

 
 

Oczywiście, uruchamianie algorytmu w celu określenia jego 
efektywności nie jest faktyczną analizą. Zadaniem analizy jest 
określenie jak efektywny jest algorytm, jeszcze przed jego 
uruchomieniem.  
 
Co można zrobić w takiej sytuacji ? 
 
 

Algorytmy Monte-Carlo 

 

    Drzewa  przestrzeni  stanów dla algorytmów z powrotami mają 
wykładniczo lub szybciej roznąca liczbę węzłów. Warto zauważyć, że 
jeśli mamy dwa przypadki z taką samą wartością n, jeden z nich może 
wymagać sprawdzenia kilku węzłów, natomiast inne wymagają 
sprawdzenia całego drzewa przestrzeni stanów. 
Jeżeli oszacujemy, jak efektywny jest dany algorytm z powrotami dla 
danego przypadku, możemy zdecydować, czy zastosowanie go jest 
sensowne. 
 
Algorytm Monte-Carlo to algorytm probabilistyczny.  Jest  to  taki 
algorytm, w którym następna wykonywana instrukcja jest czasami 
określana w sposób losowy, zgodnie z pewnym rozkładem losowym. 

background image

Algorytm deterministyczny to taki, w którym przedstawiony 
przypadek nie może mieć miejsca.  
Algorytm Monte-Carlo pozwala oszacować spodziewaną wartość 
zmiennej losowej, zdefiniowanej w przestrzeni próbek, na podstawie 
średniej wartości losowych próbek z tej przestrzeni. Nie ma 
gwarancji,  że to oszacowanie jest bliskie właściwej wartości 
oczekiwanej, ale prawdopodobieństwo, że jest bliskie, zwiększa się ze 
wzrostem czasu działania algorytmu (ilosci uruchomień algorytmu). 
 
Jak wykorzystać algorytm Monte-Carlo do oszacowania efektywności 
algorytmu z powrotami ? 
 
Generujemy w drzewie „typową  ścieżkę”, składajacą się z węzłów, 
które powinny być sprawdzone w danym przypadku, a nastepnie 
szacujemy liczbę  węzłów, odgałęziających się od tej ścieżki. 
Oszacowanie to daje w wyniku szacunkową liczbę  węzłów, które 
należy sprawdzić w celu znalezienia wszystkich rozwiązań. Inaczej 
mówiąc, jest to szacunkowa liczba węzłów w przeciętnym drzewie 
stanów. 
 
Muszą być spełnione dwa warunki: 

•  we wszystkich węzłach na tym samym poziomie drzewa 

przestrzeni stanów powinna być  używana ta sama funkcja 
określająca, czy węzeł jest obiecujący 

•  węzły na tym samym poziomie w drzewie przestrzeni stanów 

muszą mieć taka samą liczbę potomków. 

 
Algorytm dla n-królowych spełnia te warunki. 
 
Technika Monte-Carlo wymaga losowego generowania obiecującego 
potomka węzła, zgodnie z rozkładem normalnym, czyli generowania 
liczb losowych.  
 
Sposób realizacji: 

•  niech m

0 

będzie liczbą obiecujących potomków korzenia 

•  losowo generujemy obiecujący węzeł pochodny na poziomie 1. 

Niech m

1 

będzie liczbą obiecujących potomków tego węzła. 

background image

•  losowo wygeneruj obiecujący węzeł dla węzła uzyskanego w 

poprzednim kroku. Niech m

2

  będzie liczbą obiecujących 

potomków tego węzła. 
…… 

•  Losowo wygeneruj obiecujący węzeł dla węzła uzyskanego w 

poprzednim kroku. Niech m

i

  będzie liczbą obiecujących 

potomków tego węzła. 

 
Proces jest kontynuowany, dopóki nie zostaną znalezione żadne 
obiecujące węzły potomne. m

i

  jest szacunkową  średnią liczbą 

obiecujących węzłów na poziomie i.  
Niech  t

i

 = całkowita liczba potomków węzła na poziomie i 

 
Wszystkie  t

i

  węzłów zostaje sprawdzone i tylko m

i

   obiecujacych 

węzłów potomnych ma sprawdzone węzły potomne. Szacunkowa 
liczba węzłów sprawdzonych przez algorytm z powrotami w celu 
wyszukania wszystkich rozwiązań wynosi 
 
                1+ t

0

 + m

t

1

 + m

0

m

1

t

2

 + m

0

m

1

…m

i-1

t

i

 + .. 

. 

Ogólny algorytm obliczający tą średnią może wygladać następująco 
mprod m

0

m

1

…m

i-1 

) . 

 
 
Szacowanie Monte-Carlo 
 
Problem: oszacuj efektywność algorytmu z powrotami, korzystając z 
algorytmu Monte Carlo. 
 
Dane wejściowe: problem rozwiazywany przez algorytm z 
powrotami. 
 
Wynik: szacunkowa liczba węzłów w przyciętym drzewie przestrzeni 
stanów generowanych przez algorytm, który jest liczbą węzłów, jaką 
musi sprawdzić algorytm w celu znalezienia wszystkich rozwiązań 
danego przypadku. 
 

background image

 
 
 
int estimate () 

 node v; 

 int m,mprod,t,numnodes; 

 

v = korzeń drzewa stanów; 

numnodes = 1; 

m=1; 

mprod=1; 
while

 (m!=0) { 

t=liczba potomków v; 

mprod=mprod*m; 

numnodes=numnodes+mprod*t; 

m=liczba obiecujacych potomków v; 
if

 (m!=0) 

   v=losowo wybrany obiecujacy potomek v; 

return numnodes; 


 
Dla algorytmu problemu n-królowych może to wyglądać. 
 
________________________________________________________
Oszacowanie metodą Monte Carlo dla algorytmu z powrotami –
problem n
-królowych 
 
Problem:
 oszacowanie efektywności algorytmu 
 
Dane wejściowe: dodatnia wartość całkowita n 
 
Wynik: szacunkowa liczba węzłów w przyciętym drzewie przestrzeni 
stanów, generowanym przez algorytm – liczba węzłów, jakie muszą 
zostać sprawdzone przez algorytm przed wyszukaniem wszystkich 
sposobów na ustawienie n królowych na szachownicy n

×

  n tak, aby 

się wzajemnie nie szachowały. 

background image

 
int estimate_n_queens (int n) 

  index i,j,col[1..n]; 

  int m,mprod,numnodes; 

  set_of_index prom_children; 

 

  i=0; 

  numnodes=1; 

  m=1; 

  mprod=1; 

  while (m!=0 && i!=n) { 

    mprod=mprod*m; 

    numnodes=numnodes+mprod*n;//Liczba wezłów t 
    i++;                      // wynosi 

    m=0; 
    prom_children=∅;    //Inicjalizacja zbioru 

    for(j=1;j<=n;j++){  //obiecujacych potomkow 

       col[i]=j;        //pustym zbiorem 

       if(promising(i)){  //Okreslenie obiecuj. 

          m++;            //potomkow. 
          prom_children=prom_children ∪ {j}; 

        } 

    } 
    if (m!=0){ 

       j= losowy wybór z prom_children; 

       col[i]=j; 

    } 

  } 

  return numnodes; 


 
Algorytm Monte Carlo można uruchomić wielokrotnie i jako 
właściwą wartość wykorzystać  średnią z otrzymanych wyników. 
Trzeba zauwazyć,  że choć prawdopobieństwo uzyskania dobrego 
oszacowania jest wysokie przy wielokrotnym uruchomieniu to nigdy 
nie mamy gwarancji, że jest to dobre oszacowanie. 
 

background image

Oszacowanie uzyskiwane dla dowolnego przypadku zastosowania 
metody Monte Carlo jest prawdziwe tylko dla tego pojedynczego 
przypadku. Zdarza się, że gdy mamy dwa różne przypadki dla takiej 
samej wartości  n, jeden może wymagać sprawdzenia niewielkiej 
liczby węzłów, natomiast drugi przejrzenia całego drzewa przestrzeni 
stanów.