51
2007 Prirodno-matematički fakultet u Nišu Ivan Stanković [STRUKTURE I BAZE PODATAKA - NELINEARNE STRUKTURE]

Strukture i baze podataka - Nelinearne strukture strukture.pdf · 3 STABLA 1. NELINEARNE STRUKTURE PODATAKA Najznačajnije nelinearne strukture podataka su stabla i grafovi

Embed Size (px)

Citation preview

2007

Prirodno-matematički fakultet u Nišu Ivan Stanković

[STRUKTURE I BAZE PODATAKA - NELINEARNE STRUKTURE]

DEFINICIJE I KONCEPTI 2

3 STABLA

1. NELINEARNE STRUKTURE PODATAKA Najznačajnije nelinearne strukture podataka su stabla i grafovi.

1.1. STABLA

1.1.1. DEFINICIJE I KONCEPTI

Graf G=(V,E) se sastoji od nepraznog skupa čvorova G i skupa E koji je skup grana grafa.

Stablo je acikličan, orijentisan graf koji ima jedan čvor koji se zove koren (root) sa ulaznim

stepenom 0 dok svi drugi čvorovi imaju ulazni stepen 1. Ako izbrišemo koren i njegove grane dobijamo

skup disjunktnih stabala. Svaki čvor kojiima izlazni stepen 0 naziva se terminalni čvor ili list, dok se svi

drugi čvorovi nazivaju čvorovi grananja (brunch nodes).

• Nivo čvora je njegova dužina od korena, d.

• Dubina (visina) stabla je maksimalna vrednost nivoa nekog čvora u stablu.

• Za stablo se kaže da je n-arno (reda n) ako svaki čvor ima najviše n podčvorova.

• Za stablo se kaže da je puno ako se svi listovi nalaze na istom rastojanju od korena, tj. ako

od korena do svakog lista odgovara put dužine h-1.

• Za stablo se kaže da je kompletno ako svi njegovi čvorovi koji ne predstavljaju listove imaju

svih n odlaznih potega.

• Broj čvorova kompletnog punog stabla iznosi

C = n0 + n1 + n3 + ... + nh = ∑ 𝑛𝑛𝑗𝑗ℎ𝑗𝑗=0 =

(𝑛𝑛ℎ+1−1)(𝑛𝑛−1)

• Kapacitet čvora k predstavlja broj elemenata koji se može smestiti u čvor.

• Za stablo se kaže da je balansirano ako za svaki čvor važi da se broj čvorova u svakom

njegovom podstablu ne razlikuje za više od 1.

OPERACIJE NA BINARNIM STABLIMA 4

• Za stablo reda n čiji su svi čvorovi na nivoima od 1 do h-1 kompletni, kaže se da je

optimalno balansirano.

1.1.2. OPERACIJE NA BINARNIM STABLIMA

Neke od operacija nad binarnim stablom su: prolaz, umetanje, brisanje, pretraživanje i kopiranje.

/* rad sa binarnim stablom - implementacija funkcija koje vrse specificnu obradu nad cvorovima binarnog stabla */ #include <stdio.h> #include <stdlib.h> typedef struct cvor { int broj; struct cvor *levo, *desno; } Cvor; typedef Cvor *Stablo; Stablo stvori (void); /* Stavaranje praznog stabla. */ int vel (Stablo koren); /* Broj cvorova u stablu. */ int zbir (Stablo koren); /* Zbir brojeva u stablu. */ void pisi_kld (Stablo koren); /* Prefiksno ispisivanje. */ void pisi_lkd (Stablo koren); /* Infiksno ispisivanje. */ void pisi_ldk (Stablo koren); /* Postfiksno ispisivanje. */ void crtaj (Stablo koren, int nivo); /* Graficki prikaz stabla. */ int pojav (Stablo koren, int b); /* Broj pojavljivanja u stablu. */ int min_u (Stablo koren); /* Najmanji u uredjenom stablu. */ int max_u (Stablo koren); /* Najveci u uredjenom stablu. */ int min_n (Stablo koren); /* Najmanji u neuredjenom stablu. */ int max_n (Stablo koren); /* Najveci u neuredjenom stablu. */ int uredjeno (Stablo koren); /* Da li je stablo uredjeno? */ Cvor *nadji_u (Stablo koren, int b); /* Trazenje u uredjenom stablu. */ Cvor *nadji_n (Stablo koren, int b); /* Trazenje u neuredjenom stablu. */ Stablo dodaj_u (Stablo koren, int b); /* Dodavanje u uredjeno stablo. */ Stablo dodaj_n (Stablo koren, int b); /* Dodavanje u neuredjeno stablo. */ Stablo citaj_u (int n); /* Citanje uredjenog stabla. */

5 STABLA

Stablo citaj_n (int n); /* Citanje neuredjenog stabla. */ Stablo brisi (Stablo koren); /* Brisanje celog stabla. */ Stablo izost_u (Stablo koren, int b); /* Izost. iz uredjenog stabla. */ Stablo izost_n (Stablo koren, int b); /* Izost. iz neuredjenog stabla. */ Stablo balans_u (Stablo koren); /* Balansiranje uredjenog stabla. */ Stablo balans_n (Stablo koren); /* Balansiranje neuredjenog satbla.*/ int moze (Stablo koren); /* Da li moze uredjena radnja? */ Stablo radi (Stablo (*f)(Stablo,int), Stablo koren); /* Primena operacije na stablo za svaki procitani broj */ void main () { Stablo koren = stvori (); //stablo int kraj = 0, broj, n; //indikator kraja rada, element u cvoru stabla, duzina char izbor[2]; //izbor korisnika sa menija opcija //obrada menija opcija koje se prikazuju korisniku while (!kraj) { printf ("\nDodavanje brojeva: a) uredjeno b) neuredjeno\n" "Izostavljanje brojeva: c) uredjeno d) neuredjeno\n" "Citanje stabla: e) uredjeno f) neuredjeno\n" "Najmanji element: g) uredjeno h) neuredjeno\n" "Najveci element: i) uredjeno j) neuredjeno\n" "Pretrazivanje: k) uredjeno l) neuredjeno\n" "Balansiranje: m) uredjeno n) neuredjeno\n" "Pisanje stabla: p) koren-levo-desno\n" " q) levo-koren-desno (uredjeno)\n" " r) levo-desno-kren\n" " s) crtanje\n" "1. Velicina stabla 2. Zbir elemenata\n" "3. Broj pojavljivanja 4. Praznjenje stabla\n" " 0. Zavrsetak rada\n\n" "Vas izbor? " ); scanf ("%s", &izbor); switch (izbor[0]) { case 'a': case 'A': /* Dodavanje brojeva u uredjeno stablo: */ if (moze (koren)) koren = radi (dodaj_u, koren); break; case 'b': case 'B': /* Dodavanje brojeva u neuredjeno stablo: */ koren = radi (dodaj_n, koren); break; case 'c': case 'C': /* Izostavljanje brojeva iz uredjenog stabla: */ if (moze (koren)) koren = radi (izost_u, koren); break; case 'd': case 'D': /* Izostavljanje brojeva iz neuredjenog stabla: */ koren = radi (izost_n, koren); break; case 'e': case 'E': /* Citanje uredjenog stabla: */ printf ("Duzina? "); scanf ("%d", &n); printf ("Brojevi? "); koren = brisi (koren); koren = citaj_u (n); break; case 'f': case 'F': /* Citanje neuredjenog stabla: */

OPERACIJE NA BINARNIM STABLIMA 6

printf ("Duzina? "); scanf ("%d", &n); printf ("Brojevi? "); koren = brisi (koren); koren = citaj_n (n); break; case 'g': case 'G': case 'h': case 'H': case 'i': case 'I': case 'j': case 'J': if (koren) switch (izbor[0]) { case 'g': case 'G': /* Najmanji element uredjenog stabla: */ if (moze (koren)) printf ("min= %d\n", min_u (koren)); break; case 'h': case 'H': /* Najmanji element neuredjenog stabla: */ printf ("min= %d\n", min_n (koren)); break; case 'i': case 'I': /* Najveci element uredjenog stabla: */ if (moze (koren)) printf ("max= %d\n", max_u (koren)); break; case 'j': case 'J': /* Najveci element neuredjenog stabla: */ printf ("max= %d\n", max_n (koren)); break; } else printf ("*** Stablo je parzno! ***\a\n"); break; case 'k': case 'K': /* Broj pojavljivanja u uredjenom stablu: */ if (moze (koren)) { printf ("Broj? "); scanf ("%d", &broj); printf ("Broj se%s nalazi u stablu.\n", (nadji_u (koren, broj) != NULL ? "" : " NE")); } break; case 'l': case 'L': /* Broj pojavljivanja u neuredjenom stablu: */ printf ("Broj? "); scanf ("%d", &broj); printf ("Broj se%s nalazi u stablu.\n", (nadji_n (koren, broj) != NULL ? "" : " NE")); break; case 'm': case 'M': /* Balansiranje uredjenog stabla: */ if (moze (koren)) koren = balans_u (koren); break; case 'n': case 'N': /* Balansiranje neuredjenog stabla: */ koren = balans_n (koren); break; case 'p': case 'P': /* Pisanje stabla koren-levo-desno: */ printf ("Stablo= "); pisi_kld (koren); putchar ('\n'); break; case 'q': case 'Q': /* Pisanje stabla levo-koren-desno: */ printf ("Stablo= "); pisi_lkd (koren); putchar ('\n'); break; case 'r': case 'R': /* Pisanje stabla levo-desno-koren: */ printf ("Stablo= "); pisi_ldk (koren); putchar ('\n'); break; case 's': case 'S': /* Crtanje stabla: */ crtaj (koren, 0); break; case '1': /* Velicina stabla: */ printf ("Vel= %d\n", vel (koren)); break; case '2': /* Zbir elemenata stabla: */ printf ("Zbir= %d\n", zbir (koren)); break; case '3': /* Broj pojavljivanja datog broja: */ printf ("Broj? "); scanf ("%d", &broj); printf ("Broj se pojavljuje %d puta.\n", pojav (koren, broj)); break; case '4': /* Praznjenje stabla: */ koren = brisi (koren); break; case '0': /* Zavrsetak rada: */ kraj = 1; break; default: /* Pogresan izbor: */ printf ("*** Nedozvoljeni izbor! ***\a\n"); break; } }

7 STABLA

} Stablo stvori (void) { return NULL; } /* Stvaranje praznog stabla. */ int vel (Stablo koren) /* Broj cvorova u stablu. */ { return koren ? 1 + vel (koren->levo) + vel (koren->desno) : 0; } int zbir (Stablo koren) /* Zbir brojeva u stablu. */ { return koren ? koren->broj + zbir (koren->levo) + zbir (koren->desno) : 0; } void pisi_kld (Stablo koren) { /* Prefiksno ispisivanje. */ if (koren) { printf ("%d ", koren->broj); pisi_kld (koren->levo); pisi_kld (koren->desno); } } void pisi_lkd (Stablo koren) { /* Infiksno ispisivanje. */ if (koren) { pisi_lkd (koren->levo); printf ("%d ", koren->broj); pisi_lkd (koren->desno); } } void pisi_ldk (Stablo koren) { /* Postfiksno ispisivanje. */ if (koren) { pisi_ldk (koren->levo); pisi_ldk (koren->desno); printf ("%d ", koren->broj); } } void crtaj (Stablo koren, int nivo) { /* Graficki prikaz stabla. */ if (koren) { crtaj (koren->desno, nivo+1); printf ("%*s%d\n", 4*nivo, "", koren->broj); crtaj (koren->levo, nivo+1); } } int pojav (Stablo koren, int b) /* Broj pojavljivanja broja b u stablu. */ { return koren ? (koren->broj==b)+pojav(koren->levo,b)+pojav(koren->desno,b) : 0;} int min_u (Stablo koren) /* Najmanji u uredjenom stablu. */ { return koren->levo ? min_u (koren->levo ) : koren->broj; }

OPERACIJE NA BINARNIM STABLIMA 8

int max_u (Stablo koren) /* Najveci u uredjenom stablu. */ { return koren->desno ? max_u (koren->desno) : koren->broj; } int min_n (Stablo koren) { /* Najmanji u neuredjenom stablu. */ int m = koren->broj, k; if (koren->levo ) { k = min_n (koren->levo ); if (k < m) m = k; } if (koren->desno) { k = min_n (koren->desno); if (k < m) m = k; } return m; } int max_n (Stablo koren) { /* Najveci u neuredjenom stablu. */ int m = koren->broj, k; if (koren->levo ) { k = max_n (koren->levo ); if (k > m) m = k; } if (koren->desno) { k = max_n (koren->desno); if (k > m) m = k; } return m; } int uredjeno (Stablo koren) { /* Da li je stablo uredjeno? */ if (! koren) return 1; if (koren->levo && (! uredjeno (koren->levo ) || max_u (koren->levo) > koren->broj)) return 0; if (koren->desno && (! uredjeno (koren->desno) || min_u (koren->desno) < koren->broj)) return 0; return 1; } Cvor *nadji_u (Stablo koren, int b) { /* Trazenje u uredjenom stablu. */ if (! koren) return NULL; if (koren->broj == b) return koren; if (koren->broj > b) return nadji_u (koren->levo, b); return nadji_u (koren->desno, b); } Cvor *nadji_n (Stablo koren, int b) { /* Trazenje u neuredjenom stablu. */ if (! koren) return NULL; if (koren->broj == b) return koren; { Cvor *cvr = nadji_n (koren->levo, b); if (cvr) return cvr; } return nadji_n (koren->desno, b); } Stablo dodaj_u (Stablo koren, int b) { /* Dodavanje u uredjeno stablo. */ if (! koren) { koren = malloc (sizeof(Cvor)); koren->broj = b; koren->levo = koren->desno = NULL; } else if (koren->broj > b) koren->levo = dodaj_u (koren->levo, b); else if (koren->broj < b) koren->desno = dodaj_u (koren->desno, b);

9 STABLA

else if (rand() / (RAND_MAX+1.) < 0.5) koren->levo = dodaj_u (koren->levo, b); else koren->desno = dodaj_u (koren->desno, b); return koren; } Stablo dodaj_n (Stablo koren, int b) { /* Dodavanje u neuredjeno stablo. */ if (! koren) { koren = malloc (sizeof(Cvor)); koren->broj = b; koren->levo = koren->desno = NULL; } else if (rand() / (RAND_MAX+1.) < 0.5) koren->levo = dodaj_u (koren->levo, b); else koren->desno = dodaj_u (koren->desno, b); return koren; } Stablo citaj_u (int n) { /* Citanje uredjenog stabla. */ Stablo koren = NULL; int i, b; for (i=0; i<n; i++) { scanf ("%d", &b); koren = dodaj_u (koren, b); } return koren; } Stablo citaj_n (int n) { /* Citanje neuredjenog stabla. */ Stablo koren = NULL; int i, b; for (i=0; i<n; i++) { scanf ("%d", &b); koren = dodaj_n (koren, b); } return koren; } Stablo brisi (Stablo koren) { /* Brisanje celog stabla. */ if (koren) { koren->levo = brisi (koren->levo); koren->desno = brisi (koren->desno); free (koren); koren = NULL; } return koren; } Stablo izost_u (Stablo koren, int b) { /* Izost. iz uredjenog stabla. */ if (koren) { if (koren->broj > b) koren->levo = izost_u (koren->levo, b); else if (koren->broj < b) koren->desno = izost_u (koren->desno, b); else if (koren->levo) { int m = max_u (koren->levo); koren->broj = m; koren->levo = izost_u (koren->levo, m); } else if (koren->desno) { int m = min_u (koren->desno); koren->broj = m; koren->desno = izost_u (koren->desno, m); } else { free (koren); koren = NULL; } }

OPERACIJE NA BINARNIM STABLIMA 10

return koren; } Stablo izost_n (Stablo koren, int b) { /* Izost. iz neuredjenog stabla. */ if (koren) { if (koren->broj == b) { if (koren->levo ) { koren->broj = koren->levo->broj; koren->levo = izost_n (koren->levo, koren->broj); } else if (koren->desno) { koren->broj = koren->desno->broj; koren->desno = izost_n (koren->desno, koren->broj); } else { free (koren); koren = NULL; } } else { int v = vel (koren->levo); koren->levo = izost_n (koren->levo, b); if (v == vel (koren->levo)) koren->desno = izost_n (koren->desno, b); } } return koren; } Stablo balans_u (Stablo koren) { /* Balansiranje uredjenog stabla. */ if (koren) { int k = vel (koren->levo) - vel (koren->desno); for (; k>1; k-=2) { koren->desno = dodaj_u (koren->desno, koren->broj); koren->broj = max_u (koren->levo ); koren->levo = izost_u (koren->levo , koren->broj); } for (; k<-1; k+=2) { koren->levo = dodaj_u (koren->levo , koren->broj); koren->broj = min_u (koren->desno); koren->desno = izost_u (koren->desno, koren->broj); } koren->levo = balans_u (koren->levo ); koren->desno = balans_u (koren->desno); } return koren; } Stablo balans_n (Stablo koren) { /* Balansiranje neuredjenog satbla.*/ if (koren) { int k = vel (koren->levo) - vel (koren->desno); for (; k>1; k-=2) { koren->desno = dodaj_n (koren->desno, koren->broj); koren->broj = koren->levo ->broj; koren->levo = izost_n (koren->levo , koren->broj); } for (; k<-1; k+=2) { koren->levo = dodaj_n (koren->levo , koren->broj); koren->broj = koren->desno->broj; koren->desno = izost_n (koren->desno, koren->broj);

11 STABLA

} koren->levo = balans_n (koren->levo ); koren->desno = balans_n (koren->desno); } return koren; } int moze (Stablo koren) { /* Da li moze uredjena radnja? */ if (! uredjeno (koren)) { printf ("*** Stablo nije uredjeno! ***\a\n"); return 0; } else return 1; } /* Primena operacije na stablo za svaki procitani broj: */ Stablo radi (Stablo (*f)(Stablo,int), Stablo koren) { int b; char zn; printf ("Brojevi? "); do { scanf ("%d%c", &b, &zn); koren = (*f) (koren, b); } while (zn != '\n'); return koren; /* do kraja reda */ }

1. U datoteci zad1in.txt se nalazi niz reči (koje su zapisane u posebnim redovima datoteke) od kojih ni jedna nije dužine veće od 30 karaktera. Ispisati na standardni izlaz samo različite reči sortirane leksikografski. Uz svaku reč ispisati i broj pojava. Kraj unosa je marker kraja (EOF). Smatrati da je reč niska sastavljen isključivo od slova i cifara i broj pojava svake reči nije veći od 10000.

/* Samoreferentne strukture , binarno pretrazivacko stablo */ #include <stdio.h> #include <ctype.h> #include <string.h> #include <stdlib.h> #define MAXREC 31 typedef struct drvo_tag{ char *rec; /* pokazuje na rec teksta */ int broj; /* broj pojavljivanja pod pretpostavkom da je int dovoljan */ struct drvo_tag *levo; /* leva grana */ struct drvo_tag *desno; /* desna grana */ } drvo; /* Prototipovi funkcija */ drvo *addtree(drvo *, char *); void treeprint(drvo *); int uzmi_rec(char *, int, FILE*); drvo *talloc( void ); char *strdpl(char *); void osloboditi( drvo *k); main(){ drvo *koren; /*koren stabla pretrazivanja */ char rec[MAXREC]; /*sadrzaj reci sa ulaza */ FILE *f; /*ucitavanje reci ciji broj pojavljivanja se broji */

OPERACIJE NA BINARNIM STABLIMA 12

koren = NULL; f=fopen("zad1in.txt","r"); while( 1 ){ int kraj = uzmi_rec(rec, MAXREC, f); /*dodavanje novog cvora u stablo ili izmena nad poljem broj vec postojeceg cvora */ if( strlen(rec)) koren = addtree( koren, rec); if(kraj == EOF) break; /*ucitavanje se vrsi do markera kraja */ } /*stampanje sadrzaja stabla -leksikografski poredak reci */ treeprint(koren); /*oslobadjanje zauzetog prostora za stablo pretrage */ osloboditi(koren); fclose(f); return 0; } /* addtree - dodaje cvor sa tekstom na koji pokazuje w, na ili ispod p u drvetu*/ drvo *addtree( drvo *p, char *w ) { int cond; if( p == NULL ) /* naisla nova rec */ { p = talloc(); p->rec = strdpl(w); p->broj = 1; p->levo = p->desno = NULL; } else if ((cond = strcmp(w,p->rec)) == 0 ) p->broj++; /* ponovljena rec */ else if ( cond < 0 ) /* manje => levi ogranak */ p->levo = addtree(p->levo, w); else /*vece =>desni ogranak*/ p->desno = addtree(p->desno, w); return p; } void treeprint(drvo *p) /* treeprint - rekurzivno stampanje drveta*/ { if( p != NULL ) { treeprint( p->levo ); printf("%4d %s\n", p->broj, p->rec ); treeprint( p->desno ); } } int uzmi_rec(char s[], int lim, FILE *f) { char c, i = 0; /* preskociti sve znake do slova ili cifre */ while(!isalnum(c = s[0] = fgetc(f)) && c!=EOF); if( c==EOF ) {s[0] = '\0'; return EOF;} /* prazna rec i vratiti EOF */ /* ucitati ostatak reci: (u s[0] se vec nalazi prvo slovo) */ while((c = fgetc(f)) != EOF && isalnum(c) && i < lim) s[++i] = c; s[++i] = '\0'; /* zavrsiti nisku */

13 STABLA

if( c==EOF ) return EOF; return i; } /* talloc pravi jedan cvor drveta */ drvo *talloc(void) { return (drvo *) malloc(sizeof(drvo)); } char *strdpl(char *s) /* pravi se kopija niske s */ { char *p; p = (char *) malloc(strlen(s) + 1 ); if( p != NULL ) strcpy(p,s); return p; /* u <string.h> postoji standardna funkcija "strdup" koja obavlja navedene operacije*/ } void osloboditi( drvo *k) { /*rekurzivno se oslobadja levo i desno podstablo korena zadatog stabla */ if (k->levo) osloboditi (k->levo); if (k->desno) osloboditi (k->desno); free (k); /*brisanje cvora koji predstavlja koren zadatog stabla */ }

2. Napisati program koji sa standardnog ulaza čita aritmetički izraz zapisan u prefksnoj notaciji operator izraz1 izraz2, smešta ga u niz karaktera dužine do 20 karaktera i formira stablo u čijem se korenu nalazi zadati operator, u levom podstablu izraz1 a u desnom izraz2. Pri tome se izraz zadaje ili kao ceo broj ili kao operator izraz1 izraz2. Napisati rekurzivnu funkciju koja od učitanog stringa formira binarno stablo. Prilikom zadavanja izraza očekujemo da su svi operandi razdvojeni jedan od drugog razmakom i da je izraz pravilno zadat. Napisati i funkcije koje ovako zadato stablo ispisuju u prefksnom i infiksnom poretku i funkciju koja računa vrednost izraza koji se nalazi u stablu.

#include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> /* Definisemo novi tip operacija */ typedef enum operacija {pl = '+',min = '-', pod = '/', put = '*'} operacija; /* Struktura koja predstavlja jedan cvor */ typedef struct _cvor{ int ind; /* indikator da li se u cvoru nalazi operator (u tom slucaju ind ima vrednost 1) ili broj (u tom slucaju ind ima vrednost 0) */ int br; /* polje u kome cuvamo ceo broj, popunjeno samo ako je polje ind postavljeno na 0 */ operacija op; /* polje u kome cuvamo operator, popunjeno samo ako je polje ind postavljeno na 1 */ struct _cvor *l, *d; } cvor; /* Funkcija koja pravi jedan cvor pri cemu ostavlja neinicijalizovane vrednosti br i op */ cvor * napravi_cvor(){ cvor * novi = (cvor *)malloc(sizeof(cvor)); if (novi == NULL){

OPERACIJE NA BINARNIM STABLIMA 14

fprintf(stderr, "greska prilikom alokacije memorije\n"); exit(1); } novi->l = NULL; novi->d = NULL; return novi; } /* Rekurzivna funkcija koja uklanja drvo, obilazak mora biti postorder */ void obrisi_drvo(cvor *drvo){ if (drvo!=NULL){ obrisi_drvo(drvo->l); obrisi_drvo(drvo->d); free(drvo); } } /* Rekurzivna funkcija koja parsira string. Predajemo joj adresu stringa da bi znali dokle smo stigli u toku parsiranja */ void pretvori_u_stablo(char ** s, cvor ** pdrvo){ int c, br; cvor *novi; /* Uzimamo sledeci karakter iz stringa. */ c = *(*s); /* Ako smo stigli do kraja stringa izlazimo */ if (c == '\0') return; /* U suprotnom popunjavamo kreiramo novi cvor */ novi = napravi_cvor(); /* Ako nije u pitanju cifra znaci da smo ucitali operator... */ if (!isdigit(c)){ /* ...postavljamo indikator na 1 */ novi->ind = 1; /* ...i operator na ucitan karakter */ novi->op = c; /* Unosimo podatak u drvo */ *pdrvo = novi; /* Prelazimo na sledeci relevantan podatak, preskacemo blanko*/ *s = *s+2; /* I rekurzivno parsiramo string da bismo formirali levo pa zatim i desno podstablo */ pretvori_u_stablo(s, &((*pdrvo)->l)); pretvori_u_stablo(s, &((*pdrvo)->d)); } /* A ako je u pitanju cifra... */ else{ /* ...ucitavamo i ostatak broja ako postoji*/ br = 0; while(isdigit(c)){ br = br*10+c-'0'; /* uracunavamo tekuci karakter u broj */ (*s)++; /* pomeramo se za jedno mesto u stringu */ c = *(*s); /* i citamo sledeci karakter */ } /* postavljamo indikator na 0 */

15 STABLA

novi->ind = 0; /* i brojevnu vrednost na br */ novi->br = br; /* Unosimo podatak u drvo */ *pdrvo = novi; /* Prelazimo na sledeci relevantan podatak. Uzimamo u obzir da pokazivac vec pokazuje na blanko */ *s = *s+1; } } void ispisi_drvo(cvor *drvo){ if (drvo!=NULL){ /* Prvo ispisujemo koren. Proveravamo vrednost indikatora */ if (!drvo->ind) /* ako je indikator jednak 0 stampamo broj */ printf("%d", drvo->br); else /* a inace stampamo karakter */ printf("%c", drvo->op); /* ...a zatim i levo pa desno podstablo. */ ispisi_drvo(drvo->l); ispisi_drvo(drvo->d); } } void ispisi_drvo_infiksno(cvor *drvo){ if (drvo == NULL) return; if (!drvo->ind) /* Ako smo naisli na brojevnu vrednost stampamo je. */ printf("%d", drvo->br); else{ /* U suprotnom imamo pravo stablo pa ispisujemo prvo levu zagradu... */ printf("("); /* pa levi izraz... */ ispisi_drvo_infiksno(drvo->l); /* pa operator... */ printf(" %c ", drvo->op); /* pa desni izraz... */ ispisi_drvo_infiksno(drvo->d); /* i na kraju ispisujemo desnu zagradu */ printf(")"); } } int izracunaj_drvo(cvor *drvo){ if (!drvo->ind) return drvo->br; else switch (drvo->op){ case '+': return izracunaj_drvo(drvo->l) +

OPERACIJE NA BINARNIM STABLIMA 16

izracunaj_drvo(drvo->d); case '-': return izracunaj_drvo(drvo->l) –

izracunaj_drvo(drvo->d); case '*': return izracunaj_drvo(drvo->l) *

izracunaj_drvo(drvo->d); case '/': return izracunaj_drvo(drvo->l) /

izracunaj_drvo(drvo->d); } } main(){ cvor * drvo = NULL; char s[20], *ps; int i=0; printf("unesite aritmeticki izraz u prefiksnoj notaciji, novi red za kraj\n"); /* Funkcija fgets cita sa standardnog ulaza (stdin) liniju (karaktere do unetog novog reda) ili dok se ne popuni 20 karaktera */ if (fgets(s, 20, stdin) == NULL){ fprintf(stderr, "greska"); exit(1); } /* Kako s sada sadrzi i oznaku za novi red na kraju brisemo je */ while(s[i]!='\n') i++; /* i na kraju dodajemo '\0' da bi pripremili string za prenosenje u funkciju */ s[i] = s[i+1]; /* Kako je s niz karaktera (a niz je konstantni pokazivac) on ne sme biti predat funkciji koja kreira stablo (posto funkcija menja pokazivac koji dobija kao prvi argument) pa uzimamo pomocni pokazivac da bismo mogli da menjamo njegovu vrednost */ ps = s; /* Kreiramo stablo pozivom funkcije prevori_u_stablo */ pretvori_u_stablo(&ps, &drvo); /* Ispisujemo ga u prefiksnom poredku */ ispisi_drvo(drvo); printf("\n"); /* A zatim i u infiksnom. */ ispisi_drvo_infiksno(drvo); /* Ispisujemo vrednost pocetnog izraza */ printf(" = %d\n", izracunaj_drvo(drvo)); obrisi_drvo(drvo);

}

17 GRAFOVI

1.2. GRAFOVI Definicija: Graf G je uređeni par G=(V,E) gde je E⊆VxV. Skup V je skup čvorova, dok skup E

predstavlja skup grana (veza između čvorova).

• Grane usmerenog grafa su uređeni parovi čvorova i redosled dva čvora koje povezuje grana je bitan.

• Za neusmeren graf važi da ukoliko je čvor u u vezi sa čvorom v, onda je i čvor v u vezi sa čvorom u.

• Težinski graf je graf čijim granama su pridruženi jedan ili više realnih brojeva ( kao vrednost rastojanja, težina, cene,...).

usmeren graf neusmeren graf težinski graf

• Stepen d(v) čvora v je broj grana susednih čvoru v (broj grana koje direktno povezuju čvor v sa

nekim drugim čvorom).

• U usmerenom grafu razlikuju se ulazni stepen (broj grana čiji kraj je čvor v) i izlazni stepen (broj

grana za koje je čvor v početak).

• Bipartitivni graf je graf čiji se čvorovi mogu podeliti na dva disjunktna podskupa tako da u grafu

postoje samo grane između čvorova iz različitih podskupova.

• Put od v1 do vk je niz čvorova v1, v2, . . . ,vk povezanih granama (v1, v2), (v2, v3), . . . , (vk-1, vk ).

• Hamiltonov put je prosti ciklus ili put u kom se svaki čvor grafa pojavljuje tačno jednom.

NAPOMENA: Sve date definicije su opisne. Za precizne definicije matematičkih pojmova vezanih

za grafove konsultovati literaturu.

Predstavljanje grafova u memoriji računara 18

1.2.1. Predstavljanje grafova u memoriji računara

Predstavljanje uz pomoć matrice susedstva

Jedan način predstavljanja grafa sa n čvorova je uz pomoć matrice susedstva dimenzija n x n (svaka

vrsta i kolona odgovaraju po jednom čvoru grafa). Ukoliko postoji veza između čvorova vi i vj tada se u

vrsti koja odgovara čvoru vi i koloni koja odgovara čvoru vj je 1 (adj[vi, vj] = 1, ako (vi, vj) ivica u G).

Ukoliko je e ukupan broj ivica grafa, tada će u matrici postojati 2e elemenata matrice koji su jednaki 1,

ako je G neorijentisan graf. Ako je G usmeren graf, samo e elemenata matrice će biti 1. Primeri

predstavljanja grafova uz pomoć matrice susedstva su dati na sledećim slikama:

Graf Matrica susedstva Ulazni i izlazni stepeni čvora

Predstavljanje grafova uz pomoć povezanih listi

Drugi način predstavljanja grafa G je uz pomoć povezanih listi. Za svaki čvor konsturišemo povezanu

listu koja sadrži sve čvorove susedne datom čvoru.

19 GRAFOVI

1.2.2. Računanje ulaznog i izlaznog stepena čvora grafa predstavljenog pomoću matrice susedstva

#include <stdio.h> #define MAX 10 /* funkcija uz pomoc koje pravimo matricu susedstva */ void buildadjm(int adj[][MAX], int n){ int i,j; for(i=0;i<n;i++) for(j=0;j<n;j++){ printf("Enter 1 if there is an edge from %d to %d, otherwise enter 0 \n",i,j); scanf("%d",&adj[i][j]); } } /* funkcija za racunanje izlaznog stepena cvora */ int outdegree(int adj[][MAX],int x,int n){ int i, count =0; for(i=0;i<n;i++) if( adj[x][i] ==1) count++; return(count); } /* funkcija za racunanje ulaznog stepena grafa */ int indegree(int adj[][MAX],int x,int n){ int i, count =0; for(i=0;i<n;i++) if( adj[i][x] ==1) count++; return(count); } void main(){ int adj[MAX][MAX],node,n,i; printf("Enter the number of nodes in graph maximum = %d\n",MAX);

Obilazak grafa u širinu (BFS) 20

scanf("%d",&n); buildadjm(adj,n); for(i=0;i<n;i++){ printf("The indegree of the node %d is %d\n",i,indegree(adj,i,n)); printf("The outdegree of the node %d is %d\n",i,outdegree(adj,i,n)); } }

1.2.3. Obilazak grafa u širinu (BFS)

BREADTH-FIRST SEARCH je jeadan od najjednostavnijih algoritama za obilazak grafa. Za dati graf

G=(V,E) i startni čvor s, BFS obilazi graf G „otkrivajući“ svaki čvor koji je dostižan iz s. Istovremeno,

možemo računati i rastojanje čvora s do svakog čvora do kojeg postoji put iz s. Izvršenjem algoritma

možemo konstruisati tzv. BFS stablo koje će sadržati sve čvorove grafa G dostižne iz s. Algoritam se

može primeniti i na orijentisane i neorijentisane grafove. Na sledećoj slici ilustrovano je izvršenje BFS

algoritma za dati graf G i startni čvor s.

BFS(G, s) for each vertex u ∈ V [G] - {s}

do color[u] ← WHITE d[u] ← ∞ π[u] ← NIL

color[s] ← GRAY d[s] ← 0 π[s] ← NIL

21 GRAFOVI

Q ← Ø ENQUEUE(Q, s) while Q ≠ Ø

do u ← DEQUEUE(Q) for each v ∈ Adj[u]

do if color[v] = WHITE then color[v] ← GRAY

d[v] ← d[u] + 1 π[v] ← u ENQUEUE(Q, v)

color[u] ← BLACK

1.2.4. Obilazak grafa u dubinu (DFS)

Strategija koju koristi DFS algoritam je tražiti čvorove sve „dublje“ u grafu kad god je to moguće.

Kod DFS algoritma pretražujemo granu koja napušta čvor v i a zatim i sve naredne grane. Kada završimo

sa pretragom svih grana koje napuštaju čvor v vraćamo se unazad da bi smo nastavili sa pretragom grana

koje napuštaju čvor iz kojeg smo stigli u čvor v.

DFS(G) for each vertex u _ V [G]

do color[u] ← WHITE π[u] ← NIL

time ← 0 for each vertex u ∈ V [G]

do if color[u] = WHITE then DFS-VISIT(u)

DFS-VISIT(u)

Obilazak grafa u dubinu (DFS) 22

color[u] ← GRAY White vertex u has just been discovered. time ← time +1 d[u] time for each v _ Adj[u] Explore edge(u, v).

do if color[v] = WHITE then π[v] ← u

DFS-VISIT(v) color[u] BLACK Blacken u; it is finished. f [u] ▹ time ← time +1

#include <stdio.h> #include <stdlib.h> #define MAX 10 struct node{ int data; struct node *link; }; /* funkcija uz pomoc koje pravimo matricu susedstva */ void buildadjm(int adj[][MAX], int n){ int i,j; for(i=0;i<n;i++) for(j=0;j<n;j++){ printf("Unesi 1 ako postoji veza izmedju %d i %d, inace unesi 0 \n",i,j); scanf("%d",&adj[i][j]); } } /* funkcija za racunanje izlaznog stepena cvora */ int outdegree(int adj[][MAX],int x,int n){ int i, count =0; for(i=0;i<n;i++) if( adj[x][i] ==1) count++; return(count); } /* funkcija za racunanje ulaznog stepena grafa */ int indegree(int adj[][MAX],int x,int n){ int i, count =0; for(i=0;i<n;i++) if( adj[i][x] ==1) count++; return(count); } /* A function to insert a new node in queue*/ struct node *addqueue(struct node *p,int val){ struct node *temp; if(p == NULL){ p = (struct node *) malloc(sizeof(struct node)); /* insert the new node first node*/

23 GRAFOVI

if(p == NULL){ printf("Rezervisanje memorije nije uspelo\n"); exit(0); } p->data = val; p->link=NULL; } else { temp= p; while(temp->link != NULL){ temp = temp->link; } temp->link = (struct node*)malloc(sizeof(struct node)); temp = temp->link; if(temp == NULL){ printf("Rezervisanje memorije nije uspelo\n"); exit(0); } temp->data = val; temp->link = NULL; } return(p); } struct node *deleteq(struct node *p,int *val){ struct node *temp; if(p == NULL){ printf("red je prazan\n"); return(NULL); } *val = p->data; temp = p; p = p->link; free(temp); return(p); } void bfs(int adj[][MAX], int x,int visited[], int n, struct node **p){ int y,j,k; *p = addqueue(*p,x); do{ *p = deleteq(*p,&y); if(visited[y] == 0){ printf("\nObilazim cvor = %d\t",y); visited[y] = 1; for(j=0;j<n;j++) if((adj[y][j] ==1) && (visited[j] == 0)) *p = addqueue(*p,j); } }while((*p) != NULL); } void dfs(int x,int visited[],int adj[][MAX],int n){ int j; visited[x] = 1; printf("Posecen je cvor %d\n",x);

Topološko sortiranje 24

for(j=0;j<n;j++) if(adj[x][j] ==1 && visited[j] ==0) dfs(j,visited,adj,n); } void main(){ int adj[MAX][MAX],n,i,s; struct node *start=NULL; int visited[MAX]; printf("Unesi broj cvorova grafa, maximum = %d\n",MAX); scanf("%d",&n); buildadjm(adj,n); for(i=0;i<n;i++){ printf("Ulazni stepen cvora %d je %d\n",i,indegree(adj,i,n)); printf("Izlazni stepen cvora %d je %d\n",i,outdegree(adj,i,n)); } while(1){ printf("\n****************** BFS algoritam *****************\n"); printf("Unesi startni cvor: "); scanf("%d",&s); if(s==0) break; for(i=0; i<n; i++) visited[i] = 0; bfs(adj,s,visited,n,&start); } printf("\n****************** DFS algoritam *****************\n"); for(i=0; i<n; i++) if(visited[i] ==0) dfs(i,visited,adj,n); }

1.2.5. Topološko sortiranje

Topološko sortiranje usmerenog acikličnog grafa G=(V,E) predstavlja linearno uređenje svih

čvorova tako da ukoliko graf G sadrži granu (u,v), tada se čvor u nalazi pre čvora v u dobijenom nizu

čvorova. Ukoliko graf nije acikličan, topološko sortiranje je nemoguće. Usmeren acikličan graf se veoma

često koristi za predstavljanje veze između događaja. Grana (u,v) znači da se događaj u mora završiti pre

nego što počne izvršenje događaja v.

25 GRAFOVI

Na slici je dat primer grafa koji predstavlja redosled oblačenja delova odeće i topološko sortiranje

datog grafa (slika (b)).

Algoritam topološkog sortiranje koristi DFS algoritam i može se prikazati kao:

TOPOLOGICAL-SORT(G) call DFS(G) to compute finishing times f[v] for each vertex v as each vertex is finished, insert it onto the front of a linked list return the linked list of vertices

3. Napisati program koji će topološki sortirati graf koji je predstavljen uz pomoć povezanih listi.

#include <stdio.h> #include <stdlib.h> #ifndef MAX_NAME_CHAR #define MAX_NAME_CHAR 50 #endif typedef enum {FALSE, TRUE} bool; typedef struct node node; struct node { int count; // za cvorove grafa : ulazni stepen // za cvorove u listi : cvor sa kojim je cvor grafa povezan node *next; }; node *graph; node *zerolist; void addToZerolist(int v){ /* * dodajemo cvor v u zerolist ukoliko v ima ulazni stepen 0 */ node *ptr = (node *)malloc( sizeof(node) );

Topološko sortiranje 26

ptr->count = v; ptr->next = zerolist; zerolist = ptr; } node *buildGraph(char *fileName, int *n) { /* ucitavamo graf iz datoteke pretpostavimo da u prvom redu datoteke imamo date brojeve m n m - broj cvorova n - broj grana a u ostalih n redova sve grane grafa u obliku u v, gde su u i v cvorovi */ int i,edges,u,v; FILE *f; f=fopen(fileName,"r"); fscanf(f,"%d %d",n,&edges); // inicijalizujemo graf graph = (node *)malloc((*n)*sizeof(node)); for(i=0;i<(*n);i++) { graph[i].count = 0; graph[i].next = NULL; } // now add the list entries. for( i=0; i<edges; ++i ) { // dodajemo novi cvor u graf node *ptr = (node *)malloc( sizeof(node) ); fscanf(f,"%d - %d",&u,&v); ptr->count = v; ptr->next = graph[u].next; graph[u].next = ptr; // increase indegree of dst. graph[v].count++; } // kreiramo listu cvorova sa ulaznim stepenom 0 zerolist = NULL; for(i=0;i<(*n);i++) if(graph[i].count == 0 ) { addToZerolist(i); } } void printGraph(node *graph, int n) { int i; node *ptr; for(i=0;i<n;i++){ node *ptr; printf( "%d: pred=%d: ",i,graph[i].count); for(ptr=graph[i].next; ptr; ptr=ptr->next ) printf( "%d ", ptr->count ); printf( "\n" ); } printf( "zerolist: " ); for( ptr=zerolist; ptr; ptr=ptr->next )

27 GRAFOVI

printf( "%d ", ptr->count ); printf( "\n" ); } int getZeroVertex() { /* * vraca cvor sa ulaznim stepenom 0. * ukoliko takav cvor ne postoji vraca -1. */ int v; node *ptr; if( zerolist == NULL ) return -1; ptr = zerolist; v = ptr->count; zerolist = zerolist->next; free(ptr); return v; } void removeVertex( int v ) { /* * brise cvor v i sve njegove odlazne grane iz grafa */ node *ptr; graph[v].count = -1; // oslobadjamo listu graph[v].next. for( ptr=graph[v].next; ptr; ptr=ptr->next ) { if( graph[ ptr->count ].count > 0 ) // graph[ ptr->count ].count--; if( graph[ ptr->count ].count == 0 ) addToZerolist( ptr->count ); } } void topsort( int nvert ) { /* * rekurzivna funkcija koja topoloski sortira graf */ int v; if( nvert > 0 ) { v = getZeroVertex(); if( v == -1 ) { fprintf( stderr, "graph contains a cycle.\n" ); return; } printf( " -> %d", v ); removeVertex(v); topsort( nvert-1 ); } }

Topološko sortiranje 28

int main() { int n; buildGraph("mat.txt",&n); printGraph(graph,n); topsort(n); }

Objašnjenje

Digraf G je predstavljen uz pomoć povezanih listi. U ovakvoj reprezentaciji G je niz graph[0…n–1], gde je svaki element graph[i] povezana lista čvorova grafa sa kojima je čvor i povezan, a n je broj čvorova u grafu G.

Promenljiva zerolist služi da bismo čuvali listu čvorova koji nemaju prethodnike.

Algoritam topsort() se služi rekurzijom. Iz promenljive zerolist, uklanjamo vektor v koji ima 0 prethodnika i štampa ga. Ovaj čvor v ili nema prethodnika u grafu G, ili su svi njegovi prethodnici već obiđeni. Svi čvorovi u zerolist su potencijalni kandidati za sledeći čvor koji će biti odštampan, tj. ubačen u sortirani niz. Nakon što smo odštampali v svi čvorovi sa kojima je on u vezi mogu postati kandidati za sledeći odštampani čvor.

Primer: Analiza algoritma na sledećem grafu

Korak Zerolist Izlaz

0 {0} nil 1 {1, 2, 3} 0 2 {2, 3} 1 3 {3} 2 4 {4,5} 3 5 {5} 4

29 GRAFOVI

Korak Zerolist Izlaz

6 {} 5

1.2.6. Jako povezane komponente grafa

Povezana komponenta grafa je maksimalni podgraf dataog grafa koji je povezan. Razmotrimo

sledeći graf.

Povezana komponenta ovog grafa je:

Jako povezane komponente

Za digraf G, jako povezana komponenta je podgraf grafa G u kojem za svaki par čvorova (u,v) važi

da postoji put i od u do v i od čvora v do čvora u. Posmatrajmo graf na sledećoj slici.

Jako povezane komponente grafa 30

Jako povezane komponente datog grafa su:

31 GRAFOVI

#include <stdio.h> #include <stdlib.h> #define MAXVERTICES 20 #define MAXEDGES 20 typedef enum {FALSE, TRUE, TRISTATE} bool; typedef struct node node; struct node { int dst; node *next; }; void printGraph( node *graph[], int nvert ) { /* * prints the graph. */ int i; for( i=0; i<nvert; ++i ) { node *ptr; for( ptr=graph[i]; ptr; ptr=ptr->next ) printf( "[%d] ", ptr->dst ); printf( "\n" ); } } void insertEdge( node **ptr, int dst ) { /* * insert a new node at the start. */ node *newnode = (node *)malloc( sizeof(node) ); newnode->dst = dst; newnode->next = *ptr; *ptr = newnode; } void buildGraph( node *graph[], int edges[2][MAXEDGES], int nedges ) { /* * fills graph as adjacency list from array edges. */ int i; for( i=0; i<nedges; ++i ) { insertEdge( graph+edges[0][i], edges[1][i] ); insertEdge( graph+edges[1][i], edges[0][i] ); // undirected graph. } } void dfs( int v, int *visited, node *graph[] ) { /* * recursively traverse graph from v using visited. * and mark all the vertices that come in dfs path to TRISTATE. */ node *ptr;

Jako povezane komponente grafa 32

visited[v] = TRISTATE; //printf( "%d \n", v ); for( ptr=graph[v]; ptr; ptr=ptr->next ) if( visited[ ptr->dst ] == FALSE ) dfs( ptr->dst, visited, graph ); } void printSetTristate( int *visited, int nvert ) { /* * prints all vertices of visited which are TRISTATE. * and set them to TRUE. */ int i; for( i=0; i<nvert; ++i ) if( visited[i] == TRISTATE ) { printf( "%d ", i ); visited[i] = TRUE; } printf( "\n\n" ); } void compINC(node *graph[], int nvert) { /* * prints all connected components of graph represented using INC lists. */ int *visited; int i; visited = (int *)malloc( nvert*sizeof(int) ); for( i=0; i<nvert; ++i ) visited[i] = FALSE; for( i=0; i<nvert; ++i ) if( visited[i] == FALSE ) { dfs( i, visited, graph ); // print all vertices which are TRISTATE. // and mark them to TRUE. printSetTristate( visited, nvert ); } free( visited ); } int main() { FILE *f; int edges[2][MAXEDGES],i,nvert,nedges; node **graph; f=fopen("mat.txt","r"); fscanf(f,"%d %d",&nvert,&nedges); for(i=0;i<nedges;i++) fscanf(f,"%d %d",&edges[0][i],&edges[1][i]); graph = (node **)calloc(nvert, sizeof(node *) ); buildGraph( graph, edges, nedges );

33 GRAFOVI

printGraph( graph, nvert ); compINC( graph, nvert ); fclose(f); return 0; }

Graf je predstavljen uz pomoć povezanih listi. Graf je predstavljen kao niz od n pokazivača gde n

predstavlja broj čvorova grafa. Svaki član niza i sadrži pokazivač na povezanu listu čvorova sa kojima je

čvor i u vezi. Na primer, sledeći graf:

se predstavlja sledećom strukturom:

1.2.7. Minimum Spanning Tree (minimalno razapinjuće stablo)

Definicija: Neka je dat povezan, neorijentisani graf G = (V, E) sa težinskom funkcijom w : E → R.

Minimalno razapinjuće stablo grafa G je podgraf (V,T), gde je T ⊆ E, koji povezuje sve čvorove grafa G i

čija je ukupna težina ∑ 𝑤𝑤(𝑢𝑢, 𝑣𝑣)(𝑢𝑢 ,𝑣𝑣)𝜖𝜖𝜖𝜖 minimalna.

Pretpostavimo da imamo povezan, neorijentisani graf G = (V, E) sa težinskom funkcijom w : E →

R, i želimo da nadjemo minimalno razapinjuće stablo za graf G. Dva algoritma koja ćemo predstaviti u

Minimum Spanning Tree (minimalno razapinjuće stablo) 34

osnovi imaju istu strategiju nalaženja minimalnog razapinjućeg stabla. Naime, oba polaze od skupa grana

A koji je na početku prazan, a u svakom koraku dodaju tom skupu po jednu granu, koju nazivamo sigurna

grana za podgraf A (safe edge), sve dok ne dobijemo minimalno razapinjuće stablo (znači imamo n-1

koraka). U svakom koraku A je podskup minimalnog razapinjućeg stabla.

GENERIC-MST(G, w) A ← Ø while A does not form a spanning tree

do find an edge (u, v) that is safe for A A ← A ∪ {(u, v)}

return A

Kruskal-ov algoritam MST-KRUSKAL(G, w) A ← Ø for each vertex v∈ V[G]

do MAKE-SET(v) sort the edges of E into nondecreasing order by weight w for each edge (u, v) ∈ E, taken in nondecreasing order by weight

do if FIND-SET(u) ≠ FIND-SET(v) then A ← A _ {(u, v)}

UNION(u, v) return A

U toku generisanja minimalnog drveta razapinjanja, sve vreme se manipuliše sa supergrafom koji je

indukovan polaznim grafom. Ako je polazni graf G=(V,E), onda je supergraf uređeni par SG=(SV,SE), takav

da je:

• SV skup čvorova;

• SE skup superivica;

• svaki superčvor sv je podskup skupa čvorova V;

• svaka dva superčvora su disjunktna;

• unija svih superčvorova je skup V;

• između superčvorova su i sv postoji ivica ako i samo ako postoji čvor u∈ su i čvor v∈ sv, tako da je (u,v)∈E.

• dužina superivice (su,sv) je dužina najkraće ivice grafa (G,E) čiji je jedan kraj u superčvoru su, a drugi u superčvoru su.

35 GRAFOVI

Na početku izvršenja Kruskalovog algoritma superčvorovi su jednočlani, tj. svakom čvoru u

polaznog grafa odgovara superčvor su={u}, a Eπ je prazan. Algoritam je iterativan. U svakoj iteraciji SE

izvršava sledeći niz radnji:

• dva superčvora se spajaju u jedan, čime se broj superčvorova smanjuje;

• jedna ivica se dodaje minimalnom drvetu (skupu Eπ).

Pri tome:

• spajaju se najbliži superčvorovi (superčvorovi između kojih je najmanje rastojanje);

• određuje se najkraća ivica između čvorova koji su sadržani u paru prethodno određenih superčvorova i ta ivica se dodaje drvetu.

Petlja se ponavlja dok ne dobijemo supergraf sa samo jednim superčvorom. Kako je na početku

ukupno |V| superčvorova, izvršiće se ukupno |V|-1 iteracija.

#include <stdio.h> #include <stdlib.h> #define MAXV 20 #define MAXEDGES 190 void buildGraph(int adj[][MAXV], int nver, int edges[3][MAXEDGES], int nedges ) { /* * popunjavamo matricu susedstva */ int i,j; for(i=0;i<nver;i++) for(j=0;j<nver;j++) adj[i][j]=0; for( i=0; i<nedges; ++i ) { adj[edges[0][i]][edges[1][i]]=edges[2][i]; adj[edges[1][i]][edges[0][i]]=edges[2][i];//neorijentisani graf } } int mdr_kruskal(int vn, int e[][MAXV], int v1[], int v2[]){ /************************************** vn - broj cvorova u polaznom grafu e - matrica duzina ivica v1 - niz za zapis prvog kraja ivica drveta v2 - niz za zapis drugog kraja ivica drveta rezultat: 0 - polazni graf je povezan, odredjeno drvo razapinjanja -1 - polazni graf nije povezan, drvo nije odredjeno ****************************************/ int i,j,k,l1,l2;

Minimum Spanning Tree (minimalno razapinjuće stablo) 36

int sv[MAXV]; for(i=0;i<vn;i++) sv[i]=i; for(i=0;i<vn-1;i++){ for(j=0;j<vn;j++){ for(k=0;k<vn;k++) if((sv[j]!=sv[k]) && (e[j][k]!= 0)) break; if(k<vn) break; } if(j==vn) return -1; l1=j;l2=k; for(;j<vn;j++) for(k=0;k<vn;k++) if((sv[j]!=sv[k]) && (e[j][k]!=0) && (e[j][k]<e[l1][l2])){ l1=j; l2=k; } v1[i]=l1;v2[i]=l2; j=min(sv[l1],sv[l2]); k=max(sv[l1],sv[l2]); l1=j;l2=k; for(j=0;j<vn;j++) if(sv[j]==l2) sv[j]=l1; } return 0; } int main() { FILE *f; int edges[3][MAXEDGES],i,nvert,nedges,adj[MAXV][MAXV],v1[MAXV],v2[MAXV]; f=fopen("mat.txt","r"); fscanf(f,"%d %d",&nvert,&nedges); for(i=0;i<nedges;i++) fscanf(f,"%d %d %d",&edges[0][i],&edges[1][i],&edges[2][i]); fclose(f); buildGraph(adj,nvert,edges,nedges); if(mdr_kruskal(nvert,adj,v1,v2)==-1){ printf("\nPolazni graf nije povezan. Nemoguce naci drvo razapinjanja."); return 1; } printf("\nPronadjeno minimalno drvo razapinjanja:"); for(i=0;i<nvert-1;i++) printf("\n%d -> %d",v1[i],v2[i]); printf("\n*************** kraj ****************\n"); return 0; }

Isti algoritam se može modifikovati tako da postane efikasniji. Naime, u svakom prolazu kroz

spoljašnju petlju se određuje najkraća ivica čiji su krajevi u različitim superčvorovima. Ako bi se ivice

sortirale pre početka izvršavanja te petlje, onda se računanje pojednostavljuje. Tada bismo u petlji

pronalazili sledeću ivicu čiji su krajevi u različitim superčvorovima, i dodavali bismo je drvetu.

37 GRAFOVI

Minimum Spanning Tree (minimalno razapinjuće stablo) 38

Prim-ov algoritam MST-PRIM(G, w, r) for each u _ V [G]

do key[u] ← ∞ π[u] ← NIL

key[r] ← 0 Q ← V [G] while Q ≠ Ø

do u ← EXTRACT-MIN(Q) for each v∈ Adj[u]

do if v ∈ Q and w(u, v) < key[v] then π[v] ← u

key[v] ← w(u, v)

Kod Primovog algoritma drvo se generiše tako što se kreće od proizvoljnog čvora koji će biti koren

drveta, i dodaje jedan po jedan čvor. Tako da u toku izvršenja algoritma imamo:

• skup U koji se sastoji od čvorova već dodatih drvetu i

• skup V\U koji se sastoji od čvorova koji nisu u drvetu

Na početku se skup U sastoji samo od jednog čvora – izabrani koren drveta. U svakoj iteraciji se bira

nakraća ivica čiji je jedan kraj (čvor u) u skupu U, a drugi (čvor v) u skupu V\U. Čvor v se dodaje skupu U,

a ivica (u,v) se dodaje drvetu (Eπ).

39 GRAFOVI

Izbor najkrće ivice sa zadanim svojstvima je ekvivalentan izboru čvora iz skupa V\U koji je najbliži

skupu čvorova U. Rastojanje između čvora i skupa čvorova se definiše kao dužina najkraće ivice čiji je

jedan kraj upravo taj čvor, a drugi kraj je u skupu.

Da bismo smanjili računanje, nakon dodavanja novog čvora (u) u skup U, za sve čvorove iz skupa

V\U korigujemo najkraće rastojanje do skupa U. Korekciju izvodimo po formuli:

d(v,U∪{u}) = min {d(v,U),e(u,v)},

gde je e(u,v) dužina ivice (u,v).

Postupak se ponavlja sve dok skup U ne postane jednak skupu V. Kako se u svakom prolazu u skup

U dodaje jedan čvor, izvršava se ukupno |V|-1 iteracija. Funkcija se može zapisati na sledeći način:

(glavni program je identičan kao kod kruskalovig algoritma, sem poziva funkcije mdr_Prim umesto

mdr_kruskal)

int mdr_Prim(int vn, int e[][MAXV],int v1[], int v2[]){ int i,j,k; int vi[MAXV]; int vp[MAXV]; int dm[MAXV]; /*** vi[i] ima vrednost 1, ako je i vec ukljucen u drvo, 0 u suprotnom ***/ vi[0] = 1; for(i=1;i<vn;i++){ vi[i]=0; dm[i]=0; } for(i=1;i<vn;i++) if(e[0][i] != 0){ /*** dm[i] je rastojanje cvora i od drveta, vp[i] je cvor iz drveta koji je najblizi cvoru i, tj. vazi dm[i]=e[vp[i]][i] ***/ vp[i]=0; dm[i]=e[0][i]; } for(i=0;i<vn-1;i++){ j=0; while((j<vn)&&(vi[j] || (dm[j] == 0))) j++; if(j==vn) return -1; for(k=j++;j<vn;j++) if(!vi[j] && (dm[j] != 0) && (dm[k] > dm[j])) k=j; vi[k]=1; v1[i]=vp[k]; v2[i]=k; for(j=0;j<vn;j++) if(!vi[j] && (e[k][j] != 0))

Minimum Spanning Tree (minimalno razapinjuće stablo) 40

if((dm[j] == 0) || (dm[j] > e[k][j])){ vp[j]=k; dm[j]=e[k][j]; } } return 0; }

41 GRAFOVI

1.2.8. Nalaženje najkraćeg puta od jednog do svih ostalih čvorova INITIALIZE-SINGLE-SOURCE(G, s) 1 for each vertex v _ V[G] 2 do d[v] ← ∞ 3 π[v] ← NIL 4 d[s] 0 RELAX(u, v, w) 1 if d[v] > d[u] + w(u, v) 2 then d[v] ← d[u] + w(u, v) 3 π[v] ← u

Bellman – Ford algoritam

Bellman-Ford algoritam nalazi najkraće rastojanje od datog čvora do svih ostlih čvorova grafa čak i

za slučaj kada postoje ivice sa negativnom dužinom. Algoritam otkriva postojanje negativnih petlji i u

tom slučaju daje odgovarajući izveštaj, odnosno ne određuje rastojanja.

Polazna ideja je da svaki put, pa i najkraći između bilo koja dva čvora, može imati najviše |V| -1

ivicu. Tako se izvršava |V| -1 prolaz kroz petlju u kojoj se proverava za svaku ivicu da li smanjuje najkraće

rastojanje između polaznog čvora i nekog od preostalih čvorova.

Nakon toga još jednom ponovimo navedeni postupak za svaku ivicu. Ako se tim prolazom skrati

neko od izračunatih rastojanja, u grafu postoji petlja negativne dužine do koje se može stići od polaznog

čvora (s) i određivanje najkraćih rastojanja nema smisla.

BELLMAN-FORD(G, w, s) INITIALIZE-SINGLE-SOURCE(G, s) for i ← 1 to |V[G]| - 1

do for each edge (u, v) _ E[G] do RELAX(u, v, w)

for each edge (u, v) _ E[G] do if d[v] > d[u] + w(u, v) then return FALSE return TRUE

Na slici je prikazan graf i rezultat izvršenja ovog algoritma.

Nalaženje najkraćeg puta od jednog do svih ostalih čvorova 42

#include <stdio.h> #include <stdlib.h> #define MAXV 20 #define MAXEDGES 190 #define NEPOZNATO 1000000 void buildGraph(int adj[][MAXV], int nver, int edges[3][MAXEDGES], int nedges ) { /* * popunjavamo matricu susedstva */ int i,j; for(i=0;i<nver;i++) for(j=0;j<nver;j++) adj[i][j]=0; for( i=0; i<nedges; ++i ) { adj[edges[0][i]][edges[1][i]]=edges[2][i];//graf je orijentisan } }

43 GRAFOVI

int Ford_Belman(int vn, int vs, int e[][MAXV], int d[]){ /***** ulaz: vn - broj cvorova u grafu vs - polazni cvor e - duzine ivica izlaz: d - duzine najkracih puteva od vs do ostalih cvorova rezultat: 0 - nije odredjeno rastojanje zato sto se od vs moze stici do ciklusa negativne duzine 1 - odredjena su najkrca rastojanja i upisana su u niz d ******/ int i,j,k; for(i=0;i<vn;i++) if(e[vs][i] != 0) d[i]=e[vs][i]; else d[i]= NEPOZNATO; for(i=1;i<vn;i++) for(j=0;j<vn;j++) for(k=0;k<vn;k++) if(e[j][k] != 0) if(d[j] != NEPOZNATO) if((d[k]==NEPOZNATO) || (d[j]+e[j][k]<d[k])) d[k]=d[j]+e[j][k]; for(j=0;j<vn;j++) for(k=0;k<vn;k++) if(e[j][k] != 0) if((d[j] != NEPOZNATO) && (d[k] != NEPOZNATO) && (d[j]+e[j][k]<d[k])) return 0; return 1; } int main() { FILE *f; int edges[3][MAXEDGES],i,nvert,nedges,adj[MAXV][MAXV],d[MAXV],vs; f=fopen("graph.txt","r"); fscanf(f,"%d %d",&nvert,&nedges); for(i=0;i<nedges;i++) fscanf(f,"%d %d %d",&edges[0][i],&edges[1][i],&edges[2][i]); fclose(f); buildGraph(adj,nvert,edges,nedges); printf("\n************* ALGORITAM BELLMAN-FORD ******************\n"); printf("\nUnesi startni cvor (0-%d):",nvert-1); scanf("%d",&vs); if(!Ford_Belman(nvert,vs,adj,d)){ printf("\nPolazni graf sadrzi cikl negativne duzine. Nemoguce odrediti rastojanja!"); return 1; } for(i=0;i<nvert;i++)

Nalaženje najkraćeg puta od jednog do svih ostalih čvorova 44

printf("\nRastojanje od cvora %d do cvora %d iznosi: %d",vs,i,d[i]); printf("\n*************** kraj ****************\n"); return 0; }

Dijkstrin algoritam za nalaženje najkraćih puteva

U svakoj iteraciji ovog algoritma se određuje najkraće rastojanje od početnog čvora (s) do jednog

od preostalih čvorova. U toku izvršavanja algoritma manipuliše se skupom čvorova U za koje je

izračunato najkraće rastojanje od čvora s.

Na početku taj skup se sastoji samo od jednog čvora (U={s}). U svakoj iteraciji skupu U se dodaje

jedan čvor. Pored toga, za svaki od čvorova v∈V\U određuje se najkraći put (dužina najkraćeg puta) od

čvora s do čvora v, pri čemu svi čvorovi na tom putu osim čvora v moraju biti u skupu U, naravno, ako

takav put postoji. Skupu U se dodaje čvor iz skupa V\U kome je taj put najkraći.

Dužine puteva se određuju na početku (kada je U={s}), posle čega se koriguje pri dodavanju novog

elementa u skup U. Na početku je dužina puta jednaka:

dužini ivice između čvora s i odgovarajućeg čvora ako ivica postoji;

+∞, tj. put ne postoji, ako ivica od čvora s do tog čvora ne postoji.

Po dodavanju novog čvora (u) u skup U, računati put se može promeniti. Jasno, taj put se menja

samo ako se na njemu nalazi i čvor u. Novi put vodi od čvora s do čvora u, a nakon toga duž ivice od

čvora u do čvora v (ako postoji ivica (u,v)). Tako novi put ima dužinu jednaku zbiru dužine puta od čvora s

do u i dužinu ivice od čvora u do čvora v (ako ta ivica postoji). Najkraći put je jednak minimumu

dosadašnje dužine puta i gore pomenutog zbira.

#include <stdio.h> #include <stdlib.h> #define MAXV 20 #define MAXEDGES 190 #define NEPOZNATO 1000000 void buildGraph(int adj[][MAXV], int nver, int edges[3][MAXEDGES], int nedges ) { /* * popunjavamo matricu susedstva */ int i,j; for(i=0;i<nver;i++) for(j=0;j<nver;j++) adj[i][j]=0;

45 GRAFOVI

for( i=0; i<nedges; ++i ) { adj[edges[0][i]][edges[1][i]]=edges[2][i];//graf je orijentisan } } void dijkstra(int vn, int vs, int e[][MAXV], int d[]){ int i,j,vm; int id[MAXV]; for(i=0;i<vn;i++) d[i]=e[vs][i]?e[vs][i]:NEPOZNATO; d[vs]=0; for(i=0;i<vn;i++) id[i]=0; id[vs]=1; for(i=0;i<vn;i++){ j=0; while((j<vn) && (id[j] || (d[j] == NEPOZNATO))) j++; if(j>=vn) break; vm=j; while(j<vn){ if(!id[j] && (d[j]!= NEPOZNATO) && (d[j]<d[vm])) vm=j; j++; } id[vm]=1; for(j=0;j<vn;j++) if(!id[j] && (e[vm][j] != 0)) if((d[j] == NEPOZNATO) || (d[vm]+e[vm][j]<d[j])) d[j]=d[vm]+e[vm][j]; } } int main() { FILE *f; int edges[3][MAXEDGES],i,nvert,nedges,adj[MAXV][MAXV],d[MAXV],vs; f=fopen("graph.txt","r"); fscanf(f,"%d %d",&nvert,&nedges); for(i=0;i<nedges;i++) fscanf(f,"%d %d %d",&edges[0][i],&edges[1][i],&edges[2][i]); fclose(f); buildGraph(adj,nvert,edges,nedges); printf("\n************* ALGORITAM DIJKSTRA ********************\n"); printf("\nUnesi startni cvor (0-%d):",nvert-1); scanf("%d",&vs); dijkstra(nvert,vs,adj,d); for(i=0;i<nvert;i++) printf("\nRastojanje od cvora %d do cvora %d iznosi: %d",vs,i,d[i]); printf("\n*************** kraj ****************\n"); return 0;

}

Najkraće rastojanje između svih parova čvorova (Floyd-Warshall) 46

1.2.9. Najkraće rastojanje između svih parova čvorova (Floyd-Warshall)

Alogoritam Floyd-Warshall – a podrazumeva iterativni postupak za određivanje najkraćih rastojanja

između svih parova čvorova.

Na početku čvorove numerišemo brojevima od 0 do n-1. Neka je d(k)i,j najkraći put između čvorova i

i j takav da su svi čvorovi na tom putu, osim krajnjih, manji ili jednaki čvoru k. Jasno, tada je d(-1)i,j jednako

dužini ivice između čvorova i i j, ako postoji. Takođe je d(n-1)i,j jednako upravo najkraćem putu između

čvorova i i j.

Računanje vrednosti d(k+1)i,j je prilično jednostavno. Može se pokazati da je:

47 GRAFOVI

d(k+1)i,j = min(d(k)

i,j , d(k)

i,k+1 + d(k)k+1,j)

Program za računanje najkraćih rastojanja izgleda ovako:

#include <stdio.h> #include <stdlib.h> #define MAXV 20 #define MAXEDGES 190 #define NEPOZNATO 1000000 void buildGraph(int adj[][MAXV], int nver, int edges[3][MAXEDGES], int nedges ) { /* * popunjavamo matricu susedstva */ int i,j; for(i=0;i<nver;i++) for(j=0;j<nver;j++) adj[i][j]=0; for( i=0; i<nedges; ++i ) { adj[edges[0][i]][edges[1][i]]=edges[2][i];//graf je orijentisan } } void floyd_warshall(int vn, int ve[][MAXV], int me[][MAXV]){ int vi, vj, vk; for(vi=0;vi<vn;vi++) for(vj=0;vj<vn;vj++) if(vi==vj) me[vi][vj]=0; else if(ve[vi][vj] != 0) me[vi][vj] = ve[vi][vj]; else me[vi][vj] = NEPOZNATO; for(vk=0;vk<vn;vk++) for(vi=0;vi<vn;vi++){ for(vj=0;vj<vn;vj++){ if((me[vi][vk] != NEPOZNATO) && (me[vk][vj] != NEPOZNATO)) if((me[vi][vj] == NEPOZNATO) ||(me[vi][vk]+me[vk][vj] < me[vi][vj])) me[vi][vj] = me[vi][vk] + me[vk][vj]; } } } int main() { FILE *f; int edges[3][MAXEDGES],i,j,nvert,nedges,adj[MAXV][MAXV],shortest[MAXV][MAXV]; f=fopen("graph.txt","r"); fscanf(f,"%d %d",&nvert,&nedges); for(i=0;i<nedges;i++) fscanf(f,"%d %d %d",&edges[0][i],&edges[1][i],&edges[2][i]); fclose(f); buildGraph(adj,nvert,edges,nedges);

Najkraće rastojanje između svih parova čvorova (Floyd-Warshall) 48

printf("\n******** ALGORITAM FLOYD - WARSHALL *****************\n"); floyd_warshall(nvert,adj,shortest); for(i=0;i<nvert;i++) for(j=0;j<nvert;j++) printf("\nRastojanje od cvora %d do cvora %d iznosi: %d",i,j,shortest[i][j]); printf("\n*************** kraj ****************\n"); return 0; }

49 GRAFOVI

2. Zadaci za vežbu

1. Napisati program koji čvorove binarnog stabla štampa u redosledu koji odgovara

nivoima (prvo element koji se nalazi u prvom nivou, pa elemente drugog nivoa i tako

redom). Za ispis elemenata stabla po nivoima koristiti red.

/* Struktura reda u kojoj cemo cuvati pokazivace na elemente stabla */ typedef struct red{

cvor *el; struct red * sl;

} red; /* Struktura binarnog stabla*/

typedef struct cvor { int broj; struct cvor *levo, *desno; } Cvor; typedef Cvor *Stablo;

2. Napisati metodu koja ce vratiti broj cvorova binarnog stabla kod kojih je zbir elemenata levog

podstabla veći od zbira elemenata desnog podstabla.

/* Struktura binarnog stabla*/

typedef struct cvor { int broj; struct cvor *levo, *desno; } Cvor; typedef Cvor *Stablo;

3. Napisati metodu koja će vratiti broj čvorova stabla koji su roditelji barem jednog lista (tj. koji

imaju barem jedan list kao direktnog potomka).

/* Struktura binarnog stabla*/

typedef struct cvor { int broj; struct cvor *levo, *desno; } Cvor; typedef Cvor *Stablo;

4. Napisati metodu koja vraća labelu onog lista binarnog stabla koji ima najmanji nivo. Ukoliko

postoji više listova na najmanjem nivou, treba vratiti onaj sa najmanjom labelom. Koren stabla

se nalazi na nivou 0.

/* Struktura binarnog stabla*/

typedef struct cvor { char c; struct cvor *levo, *desno; } Cvor; typedef Cvor *Stablo;

5. Dva binarna stabla su „slična kao u ogledalu“ ako su oba prazna ili ako nisu prazna, ako je levo stablo svakog stabla „slično kao u ogledalu“ desnom stablu onog drugog. Na sledećoj slici su prikazana dva „slična kao u ogledalu“ stabla:

Napišite funkciju koja će proveriti da li su dva binarna stabla „slična kao u ogledalu“.

Najkraće rastojanje između svih parova čvorova (Floyd-Warshall) 50

/* Struktura binarnog stabla*/

typedef struct cvor { int broj; struct cvor *levo, *desno; } Cvor; typedef Cvor *Stablo;

6. Rekonstruišite binarno stablo čiji čvorovi imaju imena A, B, . . ., J ako je poznato da INORDER obilazak ispisuje čvorove u redosledu GDJEAICHBF, a POSTORDER obilazak u redosledu DJGIABHFCE. Detaljno obrazložite svaki korak. Nakon rekonstrukcije ispišite i PREORDER obilazak dobijenog stabla. Nacrtajte još neko stablo koje ima iste PREORDER i POSTORDER obilaske kao ovo.

/* Struktura binarnog stabla*/

typedef struct cvor { int broj; struct cvor *levo, *desno; } Cvor; typedef Cvor *Stablo;

7. Kažemo da je čvor p binarnog stabla zajednički predak čvorova u i v ako je p istovremeno predak čvora u i predak čvora v. Čvor np je najbliži predak od u i v ako od svih njihovih zajedničkih predaka upravo np ima najveću visinu. Napišite funkciju node np(BTREE T, node u, node v) koja vraća ime najbližeg pretka čvorova u i v. U primeru desno, čvorovi a i c su zajednički pretci čvorova f i e - ali kako c ima veću visinu, on je najbliži predak.

8. Udaljenost čvorova u i v računamo kao broj grana koji treba preći na putu koji spaja u i v. Napišite funkciju int udalj(BTREE T, node u, node v) koja vraća udaljenost čvorova u i v u stablu T. Možete koristiti pozive funkcije iz (a). U primeru desno, dužina puta koji spaja f i e je jednaka 3.

9. Kažemo da je čvor v binarnog stabla T k-potomak čvora u ako se v nalazi u podstablu od T kojem je u koren i pri tome je nivo čvora v za k veći od nivoa čvora u. Tako su npr. deca nekog čvora njegovi 1-potomci, deca od dece 2-potomci, itd. Napišite funkciju sa prototipom labeltype potomak(BTREE T, int k) koja vraća label onog ćvora bin. stabla T koji ima najviše k-potomaka. Ako ima više takvih čvorova, vratite labelu bilo kojeg. Funkcija treba biti neovisna o implementaciji atp-a BTREE; ne smete koristiti pomoćne atp-ove. Možete definisati pomoćne funkcije i globalne varijable. U primeru desno, uzmimo da je k = 2. Čvor a ima dvoje 2-potomaka, a čvor c jednog; kako ostali čvorovi nemaju 2-potomaka, funkcija treba vratiti a.

51 GRAFOVI

10. Napišite funkciju sa prototipom labeltype najvisi_list(BTREE T) koja vraća labelu onog lista binarnog stabla T koji ima najmanji nivo. Ako ima više listova na najmanjem nivou, treba vratiti onaj sa najmanjim label. U primeru desno, listovi su b, e i f, najmanji nivo od njih ima b i taj nivo je 1; dakle funkcija za ovaj primer treba vratiti b.

11. Rekonstruišite binarno stablo ćiji čvorovi imaju imena A, B, . . ., J ako je poznato da INORDER obilazak ispisuje čvorove u redosledu GEAFDBHICJ, a POSTORDER obilazak u redosledu EFAGIHJCBD. Detaljno obrazložite svaki korak. Nakon rekonstrukcije ispišite i PREORDER obilazak dobivenog stabla.

12. Date su dve datoteke koje u svakoj liniji sadrže po jednu nisku sa ne više od 80 karaktera. Napisati program koji na standardni izlaz ispisuje sve niske prve datoteke koje nisu sadržane u drugoj datoteci. Zadatak realizovati korišćenjem binarnog stabla pretrage.