You are on page 1of 79

Metoda grananja i

ogranienja
Metoda grananja i ogranienja

Ova je metoda nadgradnja metode pretraivanja


s vraanjem.
Metoda grananja i ogranienja

LCS pretraivanje
Ne koristi se ni BFS ni DFS ve se svakom stanju definira
isplativost i razvija se ona grana stabla koja ima najveu
procjenjenu isplativost.
Prema minimizacijskoj varijanti, ovo se pretraivanje
naziva pretraivanje najmanje cijene (least cost)
Funkcija procjene isplativosti je najee brza heuristika
koja daje suboptimalno rjeenje.
Metoda grananja i ogranienja

Za uvanje stanja koje se trenutno razmatraju


koristi se ATP koji se naziva prioritetni red.
Kod prioritetnog reda imamo sljedee operacije:
Dodavanje novog elementa u red
itanje elementa s najveim prioritetom u redu
Brisanje elementa s najveim prioritetom u redu.
Metoda grananja i ogranienja

Implementacije prioritetnog reda:


Pomou sortirane liste (slino kao insertion sort)
Pomou hrpe (slino kao heap sort)
Pomou binarnog stabla pretraivanja
Problem "konjiev skok"

Na ahovsku je plou nn polja postavljen


skaka. Cilj je da skaka proe svako polje ploe
u tono nn-1 poteza
Treba pronai jedno takvo rjeenje.
Problem "konjiev skok"

Algoritam temeljimo na metodi pretraivanja s


vraanjem i to na BFS pretraivanju.
Skaka u svakom trenutku moe napraviti
najvie 8 razliitih poteza.
Problem "konjiev skok"

Algoritam temeljimo na metodi pretraivanja s


vraanjem i to na BFS pretraivanju.
Skaka u svakom trenutku moe napraviti
najvie 8 razliitih poteza.
Problem "konjiev skok"

Algoritam temeljimo na metodi pretraivanja s


vraanjem i to na BFS pretraivanju.
Skaka u svakom trenutku moe napraviti
najvie 8 razliitih poteza.
Problem "konjiev skok"

Algoritam temeljimo na metodi pretraivanja s


vraanjem i to na BFS pretraivanju.
Skaka u svakom trenutku moe napraviti
najvie 8 razliitih poteza.
Problem "konjiev skok"

Algoritam temeljimo na metodi pretraivanja s


vraanjem i to na BFS pretraivanju.
Skaka u svakom trenutku moe napraviti
najvie 8 razliitih poteza.
Problem "konjiev skok"

Algoritam temeljimo na metodi pretraivanja s


vraanjem i to na BFS pretraivanju.
Skaka u svakom trenutku moe napraviti
najvie 8 razliitih poteza.
Problem "konjiev skok"

Algoritam temeljimo na metodi pretraivanja s


vraanjem i to na BFS pretraivanju.
Skaka u svakom trenutku moe napraviti
najvie 8 razliitih poteza.
Problem "konjiev skok"

Algoritam temeljimo na metodi pretraivanja s


vraanjem i to na BFS pretraivanju.
Skaka u svakom trenutku moe napraviti
najvie 8 razliitih poteza.
Problem "konjiev skok"

Algoritam temeljimo na metodi pretraivanja s


vraanjem i to na BFS pretraivanju.
Skaka u svakom trenutku moe napraviti
najvie 8 razliitih poteza.
Problem "konjiev skok"

Oznaimo li polja na ploi parovima (0,0), ...


(n-1,n-1), skaka e sa polja (a,b) moi skoiti na
polja (a-2,b-1), (a-1,b-2), (a+2,b-1), (a+1,b-2),
(a-2,b+1), (a-1,b+2), (a+1,b+2) i (a+2,b+1).
Problem "konjiev skok"

U konkretnoj situaciji za svaki ovaj potez moramo


provjeriti je li on mogu, tj. da li smo njegovim
izvoenjem jo uvijek ostali na ploi.
Ako smo na poziciji (a,b) i elimo se pomaknuti
na poziciju (a1,b1), treba provjeriti je li 0a1n-1 i
0b1n-1.
Problem "konjiev skok"

Kako bismo pojednostavili programiranje mogue


emo poteze zapisati u polje dimenzija 82 u
koje emo zapisati pomak po jednoj i drugoj
dimenziji ploe.
Drugim rijeima, polje emo definirati kao
int potezi[8][2]={{-2,-1},{-1,-2},{1,-2},{2,-1},
{-2,1},{-1,2},{1,2},{2,1}};
Problem "konjiev skok"

Rjeenje emo uvati u dvodimenzionalnom


polju dimenzija nn.
Polje rjeenja emo inicijalno popuniti nulama, te
emo u element polja zapisivati broj poteza u
kojem je skaka posjetio to polje ploe.
Problem "konjiev skok"

Kada skaka ide s polja (a,b) na neko polje s


pomakom potezi[i][0], potezi[i][1], tada treb
provjeriti je li
a+potezi[i][0]>=0
a+potezi[i][0]<n
b+potezi[i][1]>=0
b+potezi[i][1]<n
Problem "konjiev skok"

Kada doemo do poteza od brojem nn-1, onda


znai da smo dobili rjeenje, te ga treba ispisati i
prekinuti program.
U suprotnom treba napraviti zapisati taj potez na
plou te napraviti rekurzivni poziv
Poslije rekurzivnog poziva, dano polje ploe
treba vratiti na 0, kako bismo mogli izvesti neki
drugi mogui potez.
Problem "konjiev skok"

void Knight (int n, int a, int b, int


p[20][20],
int pot) {
int potezi[8][2]={{-2,-1},{-1,-2},{1,-
2},
{2,-1},{-2,1},{-1,2},
{1,2},
{2,1}};
if (pot<=n*n)
for (int i=0; i<8; i++) {
Problem "konjiev skok"

if ((a+potezi[i][0]>=0) &&
(a+potezi[i][0]<n)
&& (b+potezi[i][1]>=0) &&
(b+potezi[i][1]<n)
&& (p[a+potezi[i][0]][b+potezi[i][1]]
==0)) {
p[a+potezi[i][0]][b+potezi[i][1]]=pot;
Knight(n,a+potezi[i][0],
b+potezi[i][1], p, pot+1);
p[a+potezi[i][0]][b+potezi[i][1]]=0;
}
}
Problem "konjiev skok"

else {
for (int i=0; i<n; i++) {
for (int j=0;j<n; j++)
cout << p[i][j] << "\t";
cout << endl;
}
system("pause");
exit(0);
}
}
Problem "konjiev skok"

Sloenost?

TMaxKnight(n)=O(8n)
Problem "konjiev skok"

Primijetimo da ovaj algoritam radi brzo do ploe


dimenzije 77, dok je za vee n-ove vrlo spor.
Moemo li to postii LCS pretraivanjem stabla
prostora stanja problema?
Kako definirati LCS?
Radit emo najprije poteze koji poslije kojih emo
imati najmanje moguih grananja u sljedeem
potezu.
Problem "konjiev skok"

U svakom emo rekurzivnom koraku za svaki


mogui sljedei potez prebrojavati koliko ima
moguih poteza nakon njega.
Problem "konjiev skok"
int Check(int n,int a, int b, int p[40][40]) {
int potezi[8][2]={{-2,-1},{-1,-2},{1,-2},
{2,-1},{-2,1},{-1,2},{1,2},
{2,1}};
int pp=0;
for (int i=0; i<7; i++)
if ((a+potezi[i][0]>=0) &&
(a+potezi[i][0]<n)
&& (b+potezi[i][1]>=0) &&
(b+potezi[i][1]<n)
&& (p[a+potezi[i][0]][b+potezi[i][1]]
==0)) pp++;
return pp;
}
Problem "konjiev skok"

Prioritetni red koji emo koristiti bit e prioritetni


red implementiran pomou sortiranog polja.
Problem "konjiev skok"

void Knight (int n, int a, int b, int p[40][40],


int pot) {
int aa[8][3], nn=0;
int potezi[8][2]={{-2,-1},{-1,-2},{1,-2},
{2,-1},{-2,1},{-1,2},{1,2},
{2,1}};
if (pot<=n*n) {
for (int i=0; i<8; i++) {
Problem "konjiev skok"

if ((a+potezi[i][0]>=0) &&
(a+potezi[i][0]<n)
&& (b+potezi[i][1]>=0) &&
(b+potezi[i][1]<n)
&& (p[a+potezi[i][0]][b+potezi[i][1]]
==0)) {
int c=Check(n,a+potezi[i][0],
b+potezi[i][1], p);
int j=nn++;
Problem "konjiev skok"

while (j>0 && aa[j-1][0]>c) {


aa[j][0]=aa[j-1][0];
aa[j][1]=aa[j-1][1];
aa[j][2]=aa[j-1][2];
j--;
}
aa[j][0]=c;
aa[j][1]=a+potezi[i][0];
aa[j][2]=b+potezi[i][1];
}
}
Problem "konjiev skok"

for (int i=0;i<nn;i++) {


p[aa[i][1]][aa[i][2]]=pot;
Knight(n,aa[i][1],aa[i][2],p,pot+1);
p[aa[i][1]][aa[i][2]]=0;
}
}
Problem "konjiev skok"

else {
for (int i=0; i<n; i++) {
for (int j=0;j<n; j++)
cout << p[i][j] << "\t";
cout << endl;
}
system("pause");
exit(0);
}
}
Metoda grananja i ogranienja

Rezanje grana stabla


Definira se funkcija ogranienja koja odreuje
neperspektivne grane.
Funkcija ogranienja mora odrediti grane koje sigurno
nee dovesti do rjeenja.
Kako bi ova funkcija mogla odreivati neperspektivne
grane, potrebno je imati neko rjeenje. Zato se na
poetku brzom heuristikom trai inicijalno rjeenje.
0-1 ranac rjeenje temeljeno na
metodi pretraivanja s vraanjem
void Knapsack01(int n, int i, double V, double W,
double P, double e[][2], long sol) {
if (i<n-1) {
Knapsack01(n, i+1, V, W, P, e, sol);
if (W+e[i][1]<=V)
Knapsack01(n, i+1, V, W+e[i][1],
P+e[i][0], e,
int(sol+pow(2.,i)));
}
0-1 ranac rjeenje temeljeno na
metodi pretraivanja s vraanjem
else {
if (W+e[i][1]<=V)
if (((found) && (P+e[i][0]>Pmax)) ||
!(found)) {
Pmax=P+e[i][0];
maxsol = int(sol+pow(2.,i));
}
0-1 ranac rjeenje temeljeno na
metodi pretraivanja s vraanjem
else
if (W<=V)
if (((found) && (P>Pmax)) ||
!(found)) {
Pmax=P+e[i][0];
maxsol=sol;
}
}
}
0-1 ranac

Funkcija procjene isplativosti:


U svakom se stanju za ostatak izbora koristi pohlepni
algoritam kako bi se procijenilo koja je grana
najperspektivnija
Funkcija odreivanja neperspektivnih grana
Uzeti cijene svih preostalih stvari, bez obzira na
obujam.
Koristiti algoritam za realni ranac temeljen na metodi
pohlepe.
0-1 ranac

Kao funkciju procjene isplativosti koristit emo


algoritam pohlepni algoritam koji smo koristili za
realni ranac.
Problem realnog ranca rjeenje
temeljeno na metodi pohlepe
void RealKnapsack(int n, double V, double
e[][2], double r[]) {
int i;
rel *p = new rel [n];
for (int i=0;i<n;i++) {
p[i].p=e[i][0]/e[i][1];
p[i].index=i;
}
heapsort(n,p-1);
Problem realnog ranca rjeenje
temeljeno na metodi pohlepe
i=0;
while (V>e[p[i].index][1]) {
r[p[i].index]=1;
V-=e[p[i++].index][1];
}
r[p[i++].index]=V/e[p[i].index][1];
for (int j=i;j<n;j++) r[p[j].index]=0;
delete [] p;
}
0-1 ranac

Inicijalno e se rjeenje traiti pohlepnim algoritmom, koji


e uzimati stvari prema relativnoj vrijednosti
Ovdje se ne koristi algoritam za realni ranac, ve
pohlepni algoritam za 0-1 ranac.
Za razliku od realnog ranca, ovdje ne moemo stati kada
neka stvar vie ne stane u ranac, ve moramo ii do kraja
i pokuati u ranac staviti svaku stvar s popisa
0-1 ranac

void InitSol(int n, double V, double e[][2], rel p[],


long *sol, double *P) {
*sol=0;
double v=V;
*P=0;
for (int i=0; i<n; i++)
if (e[p[i].index][1]<=v) {
(*P)+=e[p[i].index][0];
v-=e[p[i].index][1];
(*sol)+=int(pow(2.,p[i].index));
}
}
0-1 ranac - pseudokod

Sortiraj(e,n)
Nai inicijalno rjeenje
BBKnap(e,n)
0-1 ranac

Sortiranje
Koristit e se ranije razvijeni heapsort algoritam
0-1 ranac

Odreivanje neperspektivnih grana


Neperspektivnim emo smatrati one grane koje nikako
daljnjim razvojem ne mogu nai rjeenje koje je bolje od
ve naenog.
Da bismo odredili je li grana neperspektivna, moramo
nai gornju meu rjeenja koje ona moe dati.
Gornju emo meu odreivati uzimanjem svih preostalih
stvari, bez obzira na obujam.
0-1 ranac

Odreivanje neperspektivnih grana


Napravimo neki izbor, neemo morati ponovo
raunati funkciju perspektivnosti, ve emo raditi
na sljedei nain:
Ako je stavljeni xi=1, onda gornja mea ostaje ista
Ako je stavljeni xi=0, onda se gornja mea smanjuje
za cijenu i-te stvari
0-1 ranac

Funkcija isplativosti
Koristit emo pohlepni algoritam za stvari i, ..., n i
preostali obujam ranca.
Mogu se koristiti sljedee ocjene
Algoritam koji smo koristili za inicijalno rjeenje, s time to nije
potrebno vraati rjeenje, ve samo iznos procjene
Algoritam koji smo koristili za problem realnog ranca.
Koristit emo ovaj drugi, jer je on neto bri.
0-1 ranac

double Cost(int i, int n, double V, double e[][2],


rel p[]) {
double P=0;
int j=i;
while (V>e[p[j].index][1]) {
P+=e[p[j].index][0];
V-=e[p[j++].index][1];
}
if (j<n)
P+=e[p[j].index][0]*V/e[p[j].index][1];
return P;
}
0-1 ranac

Struktura podataka
Koristit emo listu u kojoj emo uvati vorove koji se
trenutno razmatraju s podacima koji su nam potrebni
kako bismo procijenili isplativost razvoja neke grane:
Trenutno rjeenje s brojem stvari o kojima je ve donesena
odluka
Procjena isplativosti grane
Procjena perspektivnosti grane
Trenutno ostvarenu cijenu
Preostali obujam
vorovi e biti sortirani silazno prema procjeni isplativosti
0-1 ranac

Struktura podataka
Uvijek emo uzimati prvi element liste, obraditi
ga, te u listu dodati njegovu djecu, na mjesto
gdje spadaju prema procjeni isplativosti (insertion
sort)
Kako veliina ove liste moe jako varirati, koristit
emo implementaciju liste pomou pokazivaa
0-1 ranac

struct elem {
int i;
long sol;
double persp, ispl, P, V;
elem *next;
};
0-1 ranac

Razlikujemo sluaj kada odluujemo o


posljednjoj stvari od sluaja kada stvar o kojoj
odluujemo nije posljednja.
0-1 ranac

Neka stvar o kojoj odluujemo nije posljednja, tj.


neka je i<n-1
Ovisno o tome jesmo li izabrali i-tu stvar u
rjeenje ili ne kreiramo dva nova elementa liste
0-1 ranac

Ako i-ta stvar nije izabrana:


i se poveava za 1
sol se ne mijenja
persp se se umanjuje za pi
ispl se ponovo rauna
P se ne mijenja
V se ne mijenja
0-1 ranac

*n.sol=*el.sol;
*n.i=*el.i+1;
*n.P=*el.P;
*n.V=*el.V;
*n.persp=*el.persp-e[*el.i][0];
*n.ispl=*el.P+Cost(*el.i+1,n,*el.V,e,p);
0-1 ranac

Ako je i-ta stvar izabrana:


i se poveava za 1
u sol se i-ti bit postavlja na 1
persp se ne mijenja
ispl se ponovo rauna
P se uveava za pi
V se umanjuje za wi
0-1 ranac

*n.sol=*el.sol+int(pow(2.,i));
*n.i=*el.i+1;
*n.P=*el.P+e[*el.i][0];
*n.V=*el.V-e[*el.i][1];
*n.persp=*el.persp;
*n.ispl=*el.P+e[i][0]+Cost(*el.i+1,n,*el.V,e,p);
0-1 ranac

Novokreirani vor se mora dodati na svoje


mjesto u listu.
0-1 ranac

while (*l.next!=NULL &&


(*(*l.next).ispl>*n.ispl))
l=*l.next;
*n.next=*l.next;
*l.next=n;
0-1 ranac

Prije nego to ga dodajemo trebamo odluiti o


njegovoj perspektivnosti.
Perspektivnost desnog djeteta jednaka je
perspektivnosti njegova roditelja, dok se samo
perspektivnost lijevog djeteta smanjuje
Dakle, treba provjeriti je li lijevo dijete jo uvijek
perspektivno s obzirom na trenutno najbolje
rjeenje.
0-1 ranac

if (*el.persp-e[*el.i][0]>=P) {
0-1 ranac

S druge strane, desno dijete moe premaiti


zadani obujam, pa treba provjeriti jesmo li
uzimanjem i-te stvari jo unutar definiranih
granica problema.
0-1 ranac

if ((*el).V>=e[(*el).i][1]) {
0-1 ranac

Pogledajmo sada obradu listova stabla, tj. odluivanje o


posljednjoj stvari.
U ovom sluaju prvo provjeravamo stane li posljednja
stvar u ranac te ako stane, onda je stavljamo, a ako ne
onda generiramo rjeenje bez nje.
Nadalje, moramo provjeriti jesmo li tako dobili bolje
rjeenje od ranije dobivenog, te ako jesmo, onda
dobiveno rjeenje postaje trenutno najbolje.
0-1 ranac

if ((e[n-1][1]<=*el.V) && (*el.P+e[n-1][0]>P) {


P=*el.P+e[n-1][0];
sol=*el.sol+int(pow(2.,n-1));
}
if ((e[n-1][1]>*el.V) && (*el.P>P) {
P=*el.P;
sol=*el.sol;
}
0-1 ranac

Primijetimo da se nalaenjem novog, boljeg rjeenja


moe dogoditi da neki elementi koje imamo u listi mogu
postati neperspektivni. Te bi ih trebalo obrisati
To bi, meutim, bila vremenski zahtjevna operacija i
nepotrebno bi poveavala sloenost algoritma.
Umjesto toga emo svaki novi vor koji doe u
razmatranje provjeriti je li on jo uvijek perspektivan te ga
obraivati samo ako jeste.
Ako smo u listi naili na neperspektivan element, znai li
to da smo gotovi?
0-1 ranac

if (*el.persp>P) {

?
0-1 ranac

long Knapsack01 (int n, double V, double e[][2]) {


rel *p = new rel [n];
long sol;
double P;
for (int i=0;i<n;i++) {
p[i].p=e[i][0]/e[i][1];
p[i].index=i;
}
heapsort(n,p-1);
InitSol(n,V,e,p,&sol,&P);
0-1 ranac

elem *list = new elem;


elem *el = new elem;
(*list).next=el;
(*el).next= NULL;
(*el).sol=0;
(*el).i=0;
(*el).P=0;
(*el).V=V;
(*el).persp=0;
0-1 ranac

for (int j=0; j<n; j++) (*el).persp+=e[j][0];


(*el).ispl=Cost(0, n, V, e, p);
while ((*list).next!=NULL) {
el=(*list).next;
(*list).next=(*el).next;
if ((*el).persp>P) {
if ((*el).i<n-1) {
if ((*el).persp-e[(*el).i][0]>=P) {
0-1 ranac

elem *nw = new elem, *l = list;


(*nw).sol=(*el).sol;
(*nw).i=(*el).i+1;
(*nw).P=(*el).P;
(*nw).V=(*el).V;
(*nw).persp=(*el).persp
-e[(*el).i][0];
(*nw).ispl=(*el).P+Cost((*el).i+
1,n,(*el).V,e,p);
0-1 ranac

while ((*l).next!=NULL &&


((*(*l).next).ispl>
(*nw).ispl)) l=(*l).next;
(*nw).next=(*l).next;
(*l).next=nw;
}
0-1 ranac

if ((*el).V>=e[(*el).i][1]) {
elem *nw = new elem, *l = list;
(*nw).sol=
(*el).sol+int(pow(2.,(*el).i));
(*nw).i=(*el).i+1;
(*nw).P=(*el).P+e[(*el).i][0];
(*nw).V=(*el).V-e[(*el).i][1];
(*nw).persp=(*el).persp;
0-1 ranac

(*nw).ispl=(*el).P+e[(*el).i][0]+
Cost((*el).i+1,n,(*el).V,e,p);
while ((*l).next!=NULL &&
((*(*l).next).ispl>(*nw).ispl))
l=(*l).next;
(*nw).next=(*l).next;
(*l).next=nw;
}
}
0-1 ranac

else {
if ((e[n-1][1]<=(*el).V) &&
((*el).P+e[n-1][0]>P)) {
P=(*el).P+e[n-1][0];
sol=(*el).sol+int(pow(2.,n-1));
}
0-1 ranac

if ((e[n-1][1]>(*el).V) &&
((*el).P>P)) {
P=(*el).P;
sol=(*el).sol;
}
}
}
delete el;
}
delete [] p;
return sol;
}
0-1 ranac

Sloenost?

TmacxBBKnap(n)=O(n2n)

You might also like