You are on page 1of 226

Programiranje 2

Programski jezik C Zadaci sa ve zbi


Milena Vujo sevi c - Jani ci c 2008/2009

Predgovor
Ovo je prate ci materijal za ve zbe koje dr zim iz predmenta Programiranje 2. On ne mo ze zameniti pohad anje ve zbi niti kori s cenje druge preporu cene literature. Deo materijala cine zadaci i re senja mr Filipa Mari ca (raspolo zivi na www.matf.bg.ac.yu/~filip/pp/0405/index.pl). Takod e kori s cen je i materijal sa sajta koleginice Jelene Grmu se www.matf.bg.ac.yu/~jelenagr kolege Miroslava Mari ca www.matf.bg.ac.yu/~maricm i kolege Milana Bankovi ca www.matf.bg.ac.yu/~milan. Tekstovi i obja snjenja su uglavnom zasnovani na knjizi Programski jezik C, autora Kerninghan & Ritchie. Zahvaljujem svojim studentima na aktivnom u ce s cu u nastavi cime su mi pomogli u uobli cavanju ovog materijala. Svi komentari i sugestije vezane za ovaj materijal bi ce veoma dobrodo sli. Milena Vujo sevi c-Jani ci c www.matf.bg.ac.yu/~milena

Sadr zaj
1 Argumenti komandne linije 2 Datoteke 3 Bitski operatori 4 7 19

4 Ve zbanje 30 4.1 Polinomi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.2 Veliki brojevi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 5 Sortiranje 5.1 Vremenska slo zenost 5.2 Selection sort . . . . 5.3 Bubble sort . . . . . 5.4 Insertion sort . . . . 5.5 Razni zadaci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 40 41 43 45 46 58 59 64 65 65 69 70 81 82 84 86 89 93 97 97

6 Pokaziva ci 6.1 Pokaziva cka aritmetika primeri . . . . . . . . 6.2 Niska karaktera i pokaziva c na konstantnu nisku 6.3 Pokaziva ci na pokaziva ce . . . . . . . . . . . . . 6.4 Nizovi pokaziva ca . . . . . . . . . . . . . . . . . 7 Rekurzija 7.1 Osnovni primeri . . . 7.2 Binarna pretraga . . 7.3 MergeSort algoritam 7.4 QuickSort algoritam . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8 Pokaziva ci na funkcije 8.1 Funkcija qsort iz standardne biblioteke . . 8.2 Funkcija bsearch iz standardne biblioteke 8.3 Funkcije lsearch i lfind . . . . . . . . . 8.4 Pokaziva ci . . . . . . . . . . . . . . . . . .

SADRZAJ

SADRZAJ

9 Dinami cka alokacija memorije 99 9.1 Matrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 9.2 Realokacija memorije . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 10 Liste 10.1 Implementacija . . . . . 10.2 Kru zna (cikli cna) lista . 10.3 Red . . . . . . . . . . . 10.4 Stek . . . . . . . . . . . 10.5 Dvostruko povezane liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 118 143 144 151 160

11 Stabla 166 11.1 Binarno pretra ziva cko stablo . . . . . . . . . . . . . . . . . . . . . . . 167 12 Grafovi 209

Glava 1

Argumenti komandne linije


Primer 1.1 Argumenti komandne linije programa. /* Program pozivati sa npr.: a.out a.out prvi a.out prvi drugi treci a.out -a -bc ime.txt */ #include <stdio.h> /* Imena ovih promenljivih mogu biti proizvoljna. Npr: main (int br_argumenata, char** argumenti); ipak, uobicajeno je da se koriste sledeca imena: main(int argc, char** argv) Cesto se koristi i sledeca (ekvivalentna) notacija main(int argc, char* argv[]) */ main(int argc, char* argv[]) { int i; printf("argc = %d\n", argc); /* Nulti argument uvek je ime programa (a.out)*/ for (i = 0; i<argc; i++) printf("argv[%d] = %s\n", i, argv[i]); } Primer 1.2 Program ispisuje opcije navedene u komandnoj liniji. /* Opcije se navode koriscenjem znaka -, pri cemu je moguce da
4

Milena Vujo sevi cJani ci c

Argumenti komandne linije

iza jednog znaka - sledi i nekoliko opcija. Npr. za a.out -abc -d -fg dat.txt -vxy su prisutne opcije a b c d f g */ #include <stdio.h> main(int argc, char** argv) { /* Za svaki argument komande linije, pocevsi od argv[1] (preskacemo ime programa) */ int i; for (i = 1; i < argc; i++) { /* Ukoliko i-ti argument pocinje crticom */ if (argv[i][0] == -) { /* Ispisujemo sva njegova slova pocevsi od pozicije 1 */ int j; for (j = 1; argv[i][j] != \0; j++) printf("Prisutna je opcija : %c\n", argv[i][j]); } /* Ukoliko ne pocinje crticom, prekidamo */ else break; } } Izlaz: Prisutna opcija : a Prisutna opcija : b Prisutna opcija : c Prisutna opcija : d Prisutna opcija : f Prisutna opcija : g Primer 1.3 Program ispisuje opcije navedene u komandnoj liniji. K&R re senje. #include <stdio.h> /* Resnje se intenzivno zasniva na pokazivackoj aritmetici i prioritetu operatora */ main(int argc, char** argv) { char c; /* Dok jos ima argumenata i dok je karakter na poziciji 0 upravo crtica */ while(--argc>0 && (*++argv)[0]==-)
5

Milena Vujo sevi cJani ci c

Argumenti komandne linije

/* Dok god ne dodjemo do kraja tekuceg stringa */ while (c=*++argv[0]) printf("Prisutna opcija : %c\n",c); }

Glava 2

Datoteke
Primer 2.1 Program demonstrira otvaranje datoteka ("r" - read i "w" - write mod) i osnovne tehnike rada sa datotekama. /* U datoteku se upisuje prvih 10 prirodnih brojeva, a zatim se iz iste datoteke citaju brojevi dok se ne stigne do kraja i ispisuju se na standardni izlaz */ #include <stdio.h> #include <stdlib.h> main() { int i; /* Otvaramo datoteku sa imenom podaci.txt za pisanje */ FILE* f = fopen("podaci.txt", "w"); /* Ukoliko otvaranje nije uspelo, fopen vraca NULL. U tom slucaju, prijavljujemo gresku i zavrsavamo program */ if (f == NULL) { printf("Greska prilikom otvaranja datoteke podaci.txt za pisanje\n"); exit(1); } /* Upisujemo u datoteku prvih 10 prirodnih brojeva (svaki u posebnom redu) */ for (i = 0; i<10; i++) fprintf(f, "%d\n", i); /* Zatvaramo datoteku */
7

Milena Vujo sevi cJani ci c

Datoteke

fclose(f); /* Otvaramo datoteku sa imenom podaci.txt za citanje */ f = fopen("podaci.txt", "r"); /* Ukoliko otvaranje nije uspelo, fopen vraca NULL. U tom slucaju, prijavljujemo gresku i zavrsavamo program */ if (f == NULL) { printf("Greska prilikom otvaranja datoteke podaci.txt za citanje\n"); exit(1); } /* Citamo brojeve iz datoteke dok ne stignemo do kraja i ispisujemo ih na standardni izlaz */ while(1) { int br; /* Pokusavamo da procitamo broj */ fscanf(f, "%d", &br); /* Ukoliko smo dosli do kraja datoteke, prekidamo */ if (feof(f)) break; /* Ispisujemo procitani broj */ printf("Procitano : %d\n", br); } /* Funkciju feof ne treba pozivati pre pokusaja citanja. Sledeci kod moze dovesti do greske: while (!feof(f)) fscanf(f,"%d",&br); */ /* Zatvaramo datoteku */ fclose(f); } Pokaziva ci stdin, stdout i stderr su denisani u okviru stdio.h i mogu se koristiti za pisanje na standardni ulaz, izlaz i izlaz za gre sku. FILE* stdin; FILE* stdout; FILE* stderr; Primer 2.2 Program demonstrira a - append mod datoteka - nadovezivanje. #include <stdio.h>
8

Milena Vujo sevi cJani ci c

Datoteke

#include <stdlib.h> main() { FILE* datoteka; /* Otvaramo datoteku za nadovezivanje i proveravamo da li je doslo do greske */ if ( (datoteka=fopen("dat.txt","a"))==NULL) { /*Funkcija fprintf u odnosu na funkciju printf ima jedan argument vise --- prvi argument je pokazivac na datoteku u koju treba upisati poruku*/ fprintf(stderr,"Greska prilikom otvaranja dat.txt\n"); /*exit je funkcija iz biblioteke stdlib koja omogucava trenutno zaustavljanje rada programa*/ exit(1); } /* Upisujemo sadrzaj u datoteku */ fprintf(datoteka,"Zdravo svima\n"); /* Zatvaramo datoteku */ fclose(datoteka); } Primer 2.3 Program kopira sadrzaj datoteke ulaz.txt u datoteku izlaz.txt. #include <stdio.h> #include <stdlib.h> main() { int c; FILE *in, *out; if ((in = fopen("ulaz.txt","r")) == NULL) { fprintf(stderr, "Neuspesno otvaranje datoteke ulaz.txt\n"); exit(1); } if ((out = fopen("izlaz.txt","w")) == NULL) { fprintf(stderr, "Neuspesno otvaranje datoteke izlaz.txt\n");
9

Milena Vujo sevi cJani ci c

Datoteke

exit(1); } /*Funkcija fgetc ucitava iz datoteke jedan karakter. Funkcija fputc upisuje jedan karakter u datoteku. */ while ((c=fgetc(in)) != EOF) fputc(c,out); /* Zatvaramo datoteke */ fclose(in); fclose(out); } Primer 2.4 Program kopira n puta sadrzaj datoteke ulaz.txt u datoteku izlaz.txt. #include <stdio.h> #include <stdlib.h> main() { int c, n; FILE *in, *out; if ((in = fopen("ulaz.txt","r")) == NULL) { fprintf(stderr, "Neuspesno otvaranje datoteke ulaz.txt\n"); exit(1); } if ((out = fopen("izlaz.txt","w")) == NULL) { fprintf(stderr, "Neuspesno otvaranje datoteke izlaz.txt\n"); exit(1); } printf("Unesi prirodan broj\n"); scanf("%d", &n); for(i=0; i<n; i++) { /* Prepisuje se sadrzaj datoteke*/ while ((c=fgetc(in)) != EOF) fputc(c,out); /* in se pozicionira na pocetak datoteke*/ rewind(in);
10

Milena Vujo sevi cJani ci c

Datoteke

} /* Zatvaramo datoteke */ fclose(in); fclose(out); } Primer 2.5 Program ilustruje rad sa datotekama. Program kopira datoteku cije se ime zadaje kao prvi argument komandne linije u datoteku cije se ime zadaje kao drugi argument komandne linije. Uz svaku liniju se zapisuje i njen redni broj. #include <stdio.h> #include <stdlib.h> #define MAX_LINE 256 /* Funkcija fgets definisana je u stdio.h char* fgets(char *s, int n, FILE* stream) fgets ucitava najvise sledecih n-1 znakova u polje niza karaktera s, zaustavljajuci se ako naidje na novu liniju koju takodje upisuje u polje. Na kraju upisuje \0. Funkcija vraca s ili NULL ako dodje do kraja datoteke ili se pojavi greska. Funkcija fputs definisana je u stdio.h int fputs(char *s, FILE *stream) fputs upisuje nisku s u datoteku na koju pokazuje stream, vraca nenegativnu vrednost ili EOF ako se pojavi greska */ main(int argc, char* argv[]) { char line[MAX_LINE]; FILE *in, *out; int line_num; if (argc != 3) { fprintf(stderr,"Neispravna upotreba programa!\n Pravilna upotreba: %s ulazna_datoteka izlazna_datoteka\n",argv[0]);
11

Milena Vujo sevi cJani ci c

Datoteke

exit(1); } if ((in = fopen(argv[1],"r")) == NULL) { fprintf(stderr, "Neuspesno otvaranje datoteke %s\n", argv[1]); exit(1); } if ((out = fopen(argv[2],"w")) == NULL) { fprintf(stderr, "Neuspesno otvaranje datoteke %s\n", argv[2]); exit(1); } line_num = 1; /* Citamo liniju po liniju sa ulaza*/ while (fgets(line, MAX_LINE, in) != NULL) { /* Ispisujemo broj linije i sadrzaj linije na izlaz */ fprintf(out, "%d :\t", line_num++); fputs(line, out); } /* Zatvaramo datoteke */ fclose(in); fclose(out); } Primer 2.6 Prodavnica - ilustruje citanje niza struktura iz tektsualne datoteke. /* Datoteka, cije se ime zadaje kao argument komandne linije ili ako se ne zada onda se ime unosi sa standardnog ulaza, sadrzi podatke o proizvodima koji se prodaju u okviru odredjene prodavnice. Svaki proizvod se odlikuje sledecim podacima: bar-kod - petocifreni pozitivan broj ime - niska karaktera cena - realan broj zaokruzen na dve decimale pdv - stopa poreza - realan broj zaokruzen na dve decimale Pretpostavljamo da su podaci u datoteci korektno zadati. Pretpostavljamo da se u prodavnici ne prodaje vise od 1000 razlicitih artikala Na standardni izlaz ispisujemo podatke o
12

Milena Vujo sevi cJani ci c

Datoteke

svim proizvodima koji se prodaju u prodavnici. Zadatak je moguce resiti i bez koriscenja nizova (i takvo resenje je bolje). */ #include <stdio.h> #include <stdlib.h> /* Maksimalna duzina imena proizvoda */ #define MAX_IME 30 /* Maksimalni broj artikala */ #define MAX_ARTIKALA 1000 /* Struktura za cuvanje podataka o jednom artiklu */ typedef struct _artikal { int bar_kod; char ime[MAX_IME]; float cena; float pdv; } artikal; /* Ucitava podatke o jednom artiklu iz date datoteke. Vraca da li su podaci uspesno procitani */ int ucitaj_artikal(FILE* f, artikal* a) { /* Citamo bar kod, ime, cenu, pdv */ fscanf(f, "%d%s%f%f", &(a->bar_kod), a->ime, &(a->cena), &(a->pdv)); /* Ukoliko smo dosli do kraja datoteke prilikom pokusaja ucitavanja prijavljujemo neuspeh */ if (feof(f)) return 0; /* Prijavljujemo uspeh */ return 1; } /* Izracunava ukupnu cenu datog artikla */ float cena(artikal a) { return a.cena*(1+a.pdv); }

13

Milena Vujo sevi cJani ci c

Datoteke

/* Ispisuje podatke o svim artiklima */ void ispisi_artikle(artikal artikli[], int br_artikala) { int i; for (i = 0; i<br_artikala; i++) printf("%-5d %-10s %.2f %.2f = %.2f\n", artikli[i].bar_kod, artikli[i].ime, artikli[i].cena, artikli[i].pdv, cena(artikli[i])); } main(int argc, char* argv[]) { FILE* f; /* Niz struktura u kome se cuvaju podaci o artiklima */ artikal artikli[MAX_ARTIKALA]; /* Broj trenutno ucitanih artikala */ int br_artikala; /* Ukoliko nije navedeno ime kao argument komandne linije, trazimo od korisnika da ga unese */ if (argc<2) { /* Ucitavamo ime datoteke */ char ime_datoteke[256]; printf("U kojoj datoteci se nalaze podaci o proizvodima: "); scanf("%s", ime_datoteke); /* Otvaramo datoteku i proveravamo da li smo uspeli */ if ( (f = fopen(ime_datoteke, "r")) == NULL) { printf("Greska prilikom otvaranja datoteke %s\n", ime_datoteke); exit(1); } } /* Ime datoteke je prvi argument komandne linije */ else { /* Otvaramo datoteku i proveravamo da li smo uspeli */ if ( (f = fopen(argv[1], "r")) == NULL) { printf("Greska : datoteka %s ne moze biti otvorena\n", argv[1]); exit(1); }

14

Milena Vujo sevi cJani ci c

Datoteke

} br_artikala=0; /* Ucitavamo artikle */ while (ucitaj_artikal(f, &artikli[br_artikala])) br_artikala++; /* Ispisujemo podatke o svim artiklima */ ispisi_artikle(artikli, br_artikala); /* Zatvara se datoteka*/ fclose(f); } Primer 2.7 Program u datoteku cije se ime zadaje kao prvi argument komandne linije upisuje prvih N prirodnih brojeva (N se zadaje sa standardnog ulaza) i zatim izra cunava du zinu datoteke u bajtovima. #include <stdio.h> #include <stdlib.h> /*Deklaracija funkcije za racunanje duzine datoteke*/ long filesize(FILE *f); void main() { FILE *f; int i, n; /* Ukoliko nije navedeno ime datoteke kao argument komandne linije, zavrsavamo rad programa*/ if (argc<2) { printf("Program se pokrece sa a.out ime_datoteke!\n"); exit(1); } /* Otvaramo datoteku i proveravamo da li smo uspeli */ if ( (f = fopen(argv[1], "w+")) == NULL) { printf("Greska: datoteka %s ne moze biti otvorena\n", argv[1]); exit(1); } printf("Unesi prirodan broj\n"); scanf("%d", &n);
15

Milena Vujo sevi cJani ci c

Datoteke

/* Upisuje se sadrzaj u datoteku*/ for(i=0; i<n; i++) fprintf(f, "%d ", i); printf("Velicina datoteke je %ld bajtova\n", filesize(f)); fclose(f); } /*Funkcija koja racuna duzinu datoteke*/ long filesize(FILE *f) { long trenutna_pozicija, duzina; /*Funkcija ftell vraca trenutnu poziciju u datoteci*/ /*Pamti se trenutna pozicija da bismo mogli na nju da se vratimo*/ trenutna_pozicija = ftell(f);

/* Funkcija fseek se pozicionira na odredjeno mesto u datoteci i ima sledece argumente 1. FILE* f - datoteka sa kojom se radi 2. int offset - rastojanje u bajtovima od odgovarajuce pozicije 3. int origin - pozicija u odnosu na koju se gleda offset i moze da ima vrednost jedne od sledecih konstanti SEEK_END - kraj SEEK_SET - pocetak SEEK_CUR - trenutna pozicija */ /*pozicioniranje na kraj fajla*/ fseek(f, 0, SEEK_END); /*uzimamo poziciju u broju bajtova od pocetka fajla, to je duzina*/ duzina = ftell(f); /*vracamo se nazad na zapamcenu poziciju*/ fseek(f, trenutna_pozicija, SEEK_SET); return duzina; } Primer 2.8 Napisati program koji u datoteci cije se ime zadaje kao prvi argument
16

Milena Vujo sevi cJani ci c

Datoteke

komandne linije zamenjuje sva pojavljivanja niske koja se zadaje kao drugi argument komandne linije niskom koja se zadaje kao tre ci argument komandne linije (pretpostaviti da ce obe niske iste du zine i da nisu duze od 20 karaktera). Na primer, ako se program pokrene sa a.out datoteka.txt ana Ana tada se svako pojavljivanje re ci ana u datoteci datoteka.txt zamenjuje sa Ana. #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char* argv[]) { FILE *f; int c; int i; long pos; int duzina; char rec1[20]; char rec2[20]; /* Ukoliko nisu navedeni odgovarajuci argumenti program se zavrsava */ if (argc<4) { printf("Program se pokrece sa a.out datoteka rec zamena!\n"); exit(1); } /* Otvaramo datoteku i proveravamo da li smo uspeli */ if ( (f = fopen(argv[1], "r+")) == NULL) { printf("Greska: datoteka %s ne moze biti otvorena\n", argv[1]); exit(1); } /*Prepisujemo argumente komandne linije u rec1 i rec2*/ strcpy(rec1, argv[2]); strcpy(rec2, argv[3]); while ((c = fgetc(f))!=EOF) { /*ako smo naisli na prvi karakter niske*/ if (c == rec1[0]) {
17

Milena Vujo sevi cJani ci c

Datoteke

pos = ftell(f); /*zapamtimo trenutnu poziciju*/ duzina = strlen(rec1); /*petlja koja prepoznaje rec*/ for(i=1; i<duzina; i++) /*ako se karakteri ne poklapaju izlazimo iz petlje if (rec1[i]!=fgetc(f)) break; /*ako je bila pronadjena cela rec prepisujemo je zamenom*/ if (i==duzina) { /*vracamo se na pocetak reci da bismo je prepisali fseek(f,pos-1,SEEK_SET); for(i=0; rec2[i]; i++) fputc(rec2[i], f); fseek(f,0,SEEK_CUR); /*pozicionira na tu poziciju (jer inace fputc smatra da upisuje na kraj fajla pa fseek pocisti taj fleg*/ } else /*vracamo se na pocetak reci ali iza prvog slova*/ fseek(f, pos, SEEK_SET); } } fclose(f); }

18

Glava 3

Bitski operatori
Bitski operatori su operatori koji deluju nad bitovima i va zno je razlikovati ih od logi ckih operatora. & bitsko AND | bitsko OR ^ bitsko ekskluzivno OR << levo pomeranje >> desno pomeranje ~ jedinicni komplement Primer 3.1 Demonstracija bitskih operatora #include <stdio.h> main() { printf("%o %o\n",255,15); printf( "255 & 15 = %d\n", printf( "255 | 15 = %d\n", printf( "255 ^ 15 = %d\n", printf( "2 << 2 = %d\n", printf( "16 >> 2 = %d\n", } Izlaz iz 377 17 255 & 15 255 | 15 255 ^ 15 2 << 2 16 >> 2 programa je: = = = = = 15 255 240 8 4

255 & 15 ); 255 | 15 ); 255 ^ 15 ); 2 << 2 ); 16 >> 2 );

Primer 3.2 print bits - stampa bitove u zapisu datog celog broja x. #include <stdio.h>
19

Milena Vujo sevi cJani ci c

Bitski operatori

/* Funkcija stampa bitove datog celog broja x. Vrednost bita na poziciji i je 0 ako i samo ako se pri konjunkciji broja x sa maskom 000..010....000 - sve 0 osim 1 na poziciji i, dobija 0. Funkcija krece od pozicije najvece tezine kreirajuci masku pomeranjem jedinice u levo za duzina(x) - 1 mesto, i zatim pomerajuci ovu masku za jedno mesto u levo u svakoj sledecoj iteraciji sve dok maska ne postane 0. */ void print_bits(int x) { /* Broj bitova tipa unsigned */ int wl = sizeof(int)*8; unsigned mask; for (mask = 1<<wl-1; mask; mask >>= 1) putchar(x&mask ? 1 : 0); putchar(\n); } main() { print_bits(127); print_bits(128); print_bits(0x00FF00FF); print_bits(0xFFFFFFFF); } Izlaz iz programa: 00000000000000000000000001111111 00000000000000000000000010000000 00000000111111110000000011111111 11111111111111111111111111111111 Primer 3.3 Program proverava da li se na k-tom mestu broja n nalazi bit koji ima vrednost 1. #include <stdio.h> /* Pozicije brojimo kao u sledecem primeru: poz: ... 10 9 8 7 6 5 4 3 2 1 0
20

Milena Vujo sevi cJani ci c

Bitski operatori

bit: ...

0 0 0 1 1 1 0 0 1 1 0

*/

main() { int n,k; printf("Unesite broj i poziciju tog broja koju zelite da proverite:\n"); scanf("%d%d",&n,&k); if ((n & (1 << k))!=0) printf("Bit je 1\n"); else printf("Bit je 0\n"); } Primer 3.4 Program postavlja na k-to mesto broja n bit 1. #include <stdio.h> void print_bits(int x) { /* Broj bitova tipa unsigned */ int wl = sizeof(int)*8; unsigned mask; for (mask = 1<<wl-1; mask; mask >>= 1) putchar(x&mask ? 1 : 0); putchar(\n); } main(){ int n,k; printf("Unesite broj i poziciju tog broja koju zelite da postavite na 1:\n"); scanf("%d%d",&n,&k); printf("Binarno, uneseni broj je\n"); print_bits(n); printf("Novi broj je %d\n",(n |(1<<k))); printf("Binarno, novi broj je\n"); print_bits((n |(1<<k))); } Izrazom a>>b vr si se pomeranje sadr zaja operanda a predstavljenog u binarnom obliku za b mesta u desno. Popunjavanje upra znjenih mesta na levoj strani zavisi od tipa podataka i vrste ra cunara. Ako se pomeranje primenjuje nad operandom tipa unsigned popunjavanje je nulama. Ako se radi o ozna cenom operandu popunjavanje je jedinicama kada je u krajnjem levom bitu jedinica, a nulama kada je u krajnjem levom bitu nula.
21

Milena Vujo sevi cJani ci c

Bitski operatori

Da li va zi slede ca jednakost (za prirodan broj k): ((~0) >> k) == (~0)? Po sto je 0 konstanta celobrojnog tipa, to je ~0 takodje tipa int, pri cemu je to broj koji je zapisan svim jedinicama. Prema tome, njegovim siftovanjem u desno dobijamo isti taj broj, jer se upra znjen prostor sa desne strane popunjava jedinicama. VAZNO : Vrednost izraza a<<b je jednaka vrednosti a 2b . Vrednost izraza a>>b je jednaka vrednosti a/2b . Bitovski operatori su ekasniji od operacija * i / tako da svaki put kad se javi potreba za mno zenjem ili deljenjem stepenom broja dva, treba koristiti operacije << i >>. Primer 3.5 sum of bits - izra cunava sumu bitova datog neozna cenog broja. #include <stdio.h> /* Pomocna funkcija - stampa bitove neoznacenog broja */ void print_bits(unsigned x) { int wl = sizeof(unsigned)*8; unsigned mask; for (mask = 1<<wl-1; mask; mask >>= 1) putchar(x&mask ? 1 : 0); putchar(\n); } int sum_of_bits(unsigned x) { int br; for (br = 0; x; x>>=1) if (x&1) br++; /* Drugo resenje: int wl = sizeof(unsigned)*8; int br = 0; unsigned mask; for (mask = 1<<wl-1; mask; mask >>= 1) if(x&mask) br++ ; */ return br; }

22

Milena Vujo sevi cJani ci c

Bitski operatori

main() { printf("Binarni zapis broja 127 je\n"); print_bits(127); printf("Suma bitova broja 127 je %d\n",sum_of_bits(127)); printf("Binarni zapis broja 128 je\n"); print_bits(128); printf("Suma bitova broja 128 je %d\n",sum_of_bits(128)); printf("Binarni zapis broja 0x00FF00FF je\n"); print_bits(0x00FF00FF); printf("Suma bitova broja 0x00FF00FF je %d\n",sum_of_bits(0x00FF00FF)); printf("Binarni zapis broja 0xFFFFFFFF je\n"); print_bits(0xFFFFFFFF); printf("Suma bitova broja 0xFFFFFFFF je %d\n",sum_of_bits(0xFFFFFFFF)); } Izlaz iz programa: Binarni zapis broja 127 je 00000000000000000000000001111111 Suma bitova broja 127 je 7 Binarni zapis broja 128 je 00000000000000000000000010000000 Suma bitova broja 128 je 1 Binarni zapis broja 0x00FF00FF je 00000000111111110000000011111111 Suma bitova broja 0x00FF00FF je 16 Binarni zapis broja 0xFFFFFFFF je 11111111111111111111111111111111 Suma bitova broja 0xFFFFFFFF je 32 Primer 3.6 get_bits, set_bits, invert_bits - izdvajanje, postavljanje i invertovanje pojedina cnih bitova. #include <stdio.h> /* Pomocna funkcija - stampa bitove neoznacenog broja */ void print_bits(unsigned x) { int wl = sizeof(unsigned)*8; unsigned mask; for (mask = 1<<wl-1; mask; mask >>= 1) putchar(x&mask ? 1 : 0); putchar(\n); }
23

Milena Vujo sevi cJani ci c

Bitski operatori

/* Funkcija vraca n bitova broja x koji pocinju na poziciji p */ unsigned get_bits(unsigned x, int p, int n) { /* Gradimo masku koja ima poslednjih n jedinica 0000000...00011111 tako sto sve jedinice ~0 pomerimo u levo za n mesta 1111111...1100000 a zatim komplementiramo */ unsigned last_n_1 = ~(~0 << n); /* x pomerimo u desno za odgovarajuci broj mesta, a zatim konjunkcijom sa konstruisanom maskom obrisemo pocetne cifre */ return (x >> p+1-n) & last_n_1; } /* Funkcija vraca modifikovano x tako sto mu je izmenjeno n bitova pocevsi od pozicije p i na ta mesta je upisano poslednjih n bitova broja y */ unsigned set_bits(unsigned x, int p, int n, unsigned y) { /* Maska 000000...000111111 - poslednjih n jedinica */ unsigned last_n_1 = ~(~0 << n); /* Maska 1111100..000111111 - n nula pocevsi od pozicije p */ unsigned middle_n_0 = ~(last_n_1 << p+1-n); /* Brisemo n bitova pocevsi od pozicije p */ x = x & middle_n_0; /* Izdvajamo poslednjih n bitova broja y i pomeramo ih na poziciju p */ y = (y & last_n_1) << p+1-n; /* Upisujemo bitove broja y u broj x i vracamo rezultat */ return x | y; } /* Invertuje n bitova broja x pocevsi od pozicije p */ unsigned invert_bits(unsigned x, int p, int n) { /* Maska 000000111...1100000 - n jedinica pocevsi od pozicije p */

24

Milena Vujo sevi cJani ci c

Bitski operatori

unsigned middle_n_1 = ~(~0 << n) << p+1-n; /* Invertujemo koristeci ekskluzivnu disjunkciju */ return x ^ middle_n_1; } main() { unsigned x = 0x0AA0AFA0; print_bits(x); print_bits(get_bits(x, 15, 8)); print_bits(set_bits(x, 15, 8, 0xFF)); print_bits(invert_bits(x, 15, 8)); } Izlaz iz programa: 00001010101000001010111110100000 00000000000000000000000010101111 00001010101000001111111110100000 00001010101000000101000010100000 Primer 3.7 right_rotate_bits, mirror_bits - rotiranje i simetrija bitova. #include <stdio.h> /* Pomocna funkcija - stampa bitove neoznacenog broja */ void print_bits(unsigned x) { int wl = sizeof(unsigned)*8; unsigned mask; for (mask = 1<<wl-1; mask; mask >>= 1) putchar(x&mask ? 1 : 0); putchar(\n); } /* Funkcija vrsi rotaciju neoznacenog broja x za n pozicija u desno */ unsigned right_rotate(unsigned x, int n) { int i; int wl = sizeof(unsigned)*8; /* Postupak se ponavlja n puta */ for (i = 0; i < n; i++)
25

Milena Vujo sevi cJani ci c

Bitski operatori

{ /* Poslednji bit broja x */ unsigned last_bit = x & 1; /* x pomeramo za jedno mesto u desno */ x >>= 1; /* Zapamceni poslednji bit stavljamo na pocetak broja x*/ x |= last_bit<<wl-1; } return x; } /* Funkcija obrce binarni zapis neoznacenog broja x tako sto bitove cita unatrag */ unsigned mirror(unsigned x) { int i; int wl = sizeof(unsigned)*8; /* Rezultat inicijalizujemo na poslednji bit broja x */ unsigned y = x & 1; /* Postupak se ponavlja wl-1 puta */ for (i = 1; i<wl; i++) { /* x se pomera u desno za jedno mesto */ x >>= 1; /* rezultat se pomera u levo za jedno mesto */ y <<= 1; /* Poslednji bit broja x upisujemo na poslednje mesto rezultata */ y |= x & 1; } return y; } main() { unsigned x = 0xFAF0FAF0; print_bits(x);

26

Milena Vujo sevi cJani ci c

Bitski operatori

print_bits(mirror(x)); print_bits(right_rotate(x, 2)); } Izlaz iz programa: 11111010111100001111101011110000 00001111010111110000111101011111 00111110101111000011111010111100 ce biti vrednost promenljive c posle izraza Primer 3.8 Promeljiva c je tipa char. Sta dodele c=(15|3)>>1;? c=(15 & 3)<<1; ? Primer 3.9 Napisati program koji sa standardnog ulaza u ccitava pozitivan ceo broj, a na standardni izlaz ispisuje vrednost tog broja sa razmenjenim vrednostima bitova na pozicijama i, j. Pozicije i, j se u citavaju kao parametri komandne linije. Smatrati da je krajnji desni bit binarne reprezentacije 0-ti bit. Pri re savanju nije dozvoljeno koristiti pomo cni niz niti aritmeticke operatore +,-,/,*,%. #include <stdio.h> #include <stdlib.h> int razmeni(unsigned n, unsigned i, unsigned j) { int rezultat = n; /* Broj bitova tipa unsigned */ int wl = sizeof(int)*8; unsigned mask_i = (1 << i); unsigned mask_j = (1 << j); if(n & mask_i) /* Ako je na i-tom mestu 1, upisujemo to na j-to mesto*/ rezultat |= mask_j; /* Ako je na i-tom mestu 0, upisujemo to na j-to mesto*/ else rezultat &= ~mask_j; if(n & mask_j) /* Ako je na j-tom mestu 1, upisujemo to na i-to mesto*/
27

Milena Vujo sevi cJani ci c

Bitski operatori

rezultat |= mask_i; /* Ako je na j-tom mestu 0, upisujemo to na i-to mesto*/ else rezultat &= ~mask_i; return rezultat; } int main(int argc, char** argv) { unsigned n, i, j, m; if(argc < 3) { fprintf(stderr, "Neispravna upotreba programa!\n"); exit(1); } printf("Unesi broj\n"); scanf("%u", &n); /* Racunaju se brojevi na osnovu argumenata komandne linije. */ i = atoi(argv[1]); j = atoi(argv[2]); m = razmeni(n, i, j); printf("Broj nastao razmenom bitova (%u, %u) broja %u je %u\n", i, j, n, m); return 0; } Primer 3.10 Napisati funkciju koja prima ceo broj n i ispisuje razliku broja jedinica na parnim i neparnim pozicijama u binarnom zapisu broja n. PRIMER: za n = 19 = (10011)2 izlaz je 1. Napisati program koji pozivom ove funkcije ra cuna i stampa razliku broja jedinica za sve argumente komandne linije (pretpostavka je da su argumenti komandne linije zadati korektno). #include <stdio.h> #include <stdlib.h> int par_nepar(int n) {
28

Milena Vujo sevi cJani ci c

Bitski operatori

int br1_par = 0; int br1_nepar = 0;

/* int rezultat=0;*/

/* Broj bitova tipa unsigned */ int wl = sizeof(int)*8; unsigned mask=1; char par = 1; for( ; mask; mask <<= 1) { if(mask&n) if(par) br1_par++; /* if(par) rezultat++; */ else br1_nepar++; /* else rezultat--; */ par = 1 - par; } return br1_par - br1_nepar; /* return rezultat;*/ } main(int argc, char** argv) { unsigned n, i, m; if(argc < 2) { fprintf(stderr, "Neispravna upotreba programa!\n"); exit(1); } for(i=1; i<argc; i++) { n = atoi(argv[i]); m = par_nepar(n); printf("Razlika jedinica na parnim i neparnim za broj %u je %d\n", n, m); } }

29

Glava 4

Ve zbanje
4.1 Polinomi

Primer 4.1 Program ilustruje rad sa polinomima. #include <stdio.h> #include <math.h> #define max(a, b) ((a) > (b) ? (a) : (b)) /* * Polinom 3*x^3 + 2*x + 1.5 se predstavlja kao: * stepen -> 3 * koeficijenti -> 1.5 2 0 3 x x x x x ... x */ typedef struct polinom { float koeficijenti[21]; int stepen; } Polinom; /* * Funkcija vraca koeficijent uz x^i u polinomu p * Zbog efikasnosti ne prenosimo celu strukturu vec * samo pokazivac na strukturu. */ float vratiKoeficijent(Polinom* p, int i) { return i <= p->stepen ? p->koeficijenti[i] : 0.0f; } /* * Funkcija postavlja koeficijent uz x^i u polinomu p na * dati koeficijent k
30

Milena Vujo sevi cJani ci c

4.1 Polinomi

*/ void postaviKoeficijent(Polinom* p, int i, float k) { int j; /* Ukoliko je stepen polinoma bio manji, postavljamo sve koeficijente izmedju na 0.0 i uvecavamo stepen */ if (i > p->stepen) { for (j = p->stepen+1; j < i; j++) p->koeficijenti[j] = 0.0f; p->stepen = i; } p->koeficijenti[i] = k; } /* * Funkcija kreira polinom datog stepena sa datim nizom * koeficijenata. Pretpostavlja se da su koeficijenti * u datom nizu koeficijenti[] uredjeni opadajuce po stepenima * polinoma. */ Polinom napraviPolinom(int stepen, float koeficijenti[]) { int i; Polinom p; p.stepen = stepen; for (i = 0; i <= stepen; i++) { postaviKoeficijent(&p, i, koeficijenti[stepen - i]); } return p; } /* * Funkcija ispisuje polinom u citljivijem obliku. * Na primer: 3.0*x^3 + 0.0*x^2 + 2.0*x^1 + 1.5*x^0 */ void ispisiPolinom(Polinom* p) { int i; for (i = p->stepen; i >= 0; i--)

31

Milena Vujo sevi cJani ci c

4.1 Polinomi

{ printf("%.2f*x^%d", vratiKoeficijent(p, i), i); if (i > 0) printf(" + "); } printf("\n"); } /* * Funkcija izracunava vrednost polinoma u tacki x * Hornerovom shemom. Na primer, * 3*x^3 + 2*x + 1.5 = * (((0*x + 3)*x + 0)*x + 2)*x + 1.5 * Postupak izracunavanja p(10): * 0.0 * 10 * 0.0 + 3 = 3.0 * 10 * 3.0 + 0 = 30.0 * 10 * 30.0 + 2 = 302.0 * 10 * 302.0 + 1.5 = 3021.5 */ float vrednost(Polinom* p, float x) { int i; float suma = 0.0f; for (i = p->stepen; i >= 0; i--) { suma = suma*x + vratiKoeficijent(p, i); } return suma; } /* * Funkcija sabira dva polinoma */ Polinom saberi(Polinom* p, Polinom* q) { int i; Polinom zbir; zbir.stepen = max(p->stepen, q->stepen); for (i = 0; i <= zbir.stepen; i++) postaviKoeficijent(&zbir, i, vratiKoeficijent(p, i) + vratiKoeficijent(q, i)); return zbir;

32

Milena Vujo sevi cJani ci c

4.1 Polinomi

} /* * Funkcija mnozi dva polinoma. Na primer, * * 1*x^2 + 2*x + 3 * 4*x + 7 * * 0.0*x^3 + 0.0*x^2 + 0.0*x + 0.0 * 0.0*x^3 + 0.0*x^2 + 0.0*x + 21.0 i=0 j=0 * 0.0*x^3 + 0.0*x^2 + 12.0*x + 21.0 i=0 j=1 * 0.0*x^3 + 0.0*x^2 + 26.0*x + 21.0 i=1 j=0 * 0.0*x^3 + 8.0*x^2 + 26.0*x + 21.0 i=1 j=1 * 0.0*x^3 + 15.0*x^2 + 26.0*x + 21.0 i=2 j=0 * 4.0*x^3 + 15.0*x^2 + 26.0*x + 21.0 i=2 j=1 */ Polinom pomnozi(Polinom* p, Polinom* q) { int i, j; Polinom proizvod; proizvod.stepen = p->stepen + q->stepen; for (i = 0; i <= proizvod.stepen; i++) { postaviKoeficijent(&proizvod, i, 0.0f); } for (i = 0; i <= p->stepen; i++) { for (j = 0; j <= q->stepen; j++) { /* r[i+j] = r[i+j] + p[i]*q[j] */ postaviKoeficijent(&proizvod, i+j, vratiKoeficijent(&proizvod, i+j) + vratiKoeficijent(p, i) * vratiKoeficijent(q, j)); } } return proizvod; } main() { float pkoeficijenti[] = {1.0f, 2.0f, 1.0f}; Polinom p = napraviPolinom(2, pkoeficijenti); float qkoeficijenti[] = {1.0f, 1.0f}; Polinom q = napraviPolinom(1, qkoeficijenti); Polinom r = pomnozi(&p, &q);

33

Milena Vujo sevi cJani ci c

4.2 Veliki brojevi

ispisiPolinom(&r); }

4.2

Veliki brojevi

Primer 4.2 Program ilustruje rad sa velikim celim brojevima. Brojevi se interno reprezentuju preko niza svojih cifara. #include <stdio.h> #include <stdlib.h> #include <ctype.h> #define MAX_CIFRE 100 /* Funkcija obrce cifre prosledjenog niza */ void obrni_cifre (int cifre[], int duzina) { int i, j; for (i=0, j=duzina-1; i<j; i++, j--) { int pom = cifre[i]; cifre[i] = cifre[j]; cifre[j] = pom; } } /* Funkcija sa standardnog ulaza ucitava niz cifara, duzine najvise max_cifre i smesta ga u niz brojeva cifre[]. Zatim se niz cifre[] obrce kako bi cifre najmanje tezine bile na pocetku niza. Kao rezultat, funkcija vraca broj cifara ucitanog broja */ int uzmi_broj (int cifre[], int max_cifre) { int duzina = 0; char c; while ( --max_cifre>0 && isdigit(c=getchar())) cifre[duzina++]=c-0; obrni_cifre(cifre, duzina); return duzina; } /* Funkcija ispisuje "veliki" broj predstavljen nizom cifara cifre, duzine duzina, na standardni izlaz imajuci u vidu da su cifre u nizu zapisane "naopako" tj. pocevsi od cifre najmanje tezine */ void ispisi_broj(int cifre[],int duzina)
34

Milena Vujo sevi cJani ci c

4.2 Veliki brojevi

{ int i; for (i=duzina-1; i>=0; i--) printf("%d",cifre[i]); putchar(\n); } /* Da li su dva broja data svojim nizovima cifara i duzinama jednaka? Funkcija vraca 1 ako jesu, a 0 ako nisu */ int jednaki(int a[], int duzina_a, int b[], int duzina_b) { int i; /* Poredimo duzine */ if (duzina_a != duzina_b) return 0; /* Ako su brojevi iste duzine, poredimo cifru po cifru*/ for (i=0; i<duzina_a; i++) if (a[i] != b[i]) return 0; return 1; } /* Funckija poredi dva broja a i b, data svojim nizovima cifara i duzinama i vraca: 1 ako je a>b 0 ako je a=b -1 ako je b>a */ int uporedi(int a[], int duzina_a, int b[], int duzina_b) { int i; /* Uporedjujemo duzine brojeva a i b */ if (duzina_a > duzina_b) return 1; if (duzina_a < duzina_b) return -1; /* U ovom trenutku znamo da su brojevi iste duzine, tako da prelazimo na poredjenje cifre po cifre, pocevsi od cifre najvece tezine */ for (i=duzina_a-1; i>=0; i--) { if (a[i] > b[i])

35

Milena Vujo sevi cJani ci c

4.2 Veliki brojevi

return 1; if (a[i] < b[i]) return -1; } return 0; } /* Funkcija sabira dva broja data svojim nizovima cifara i duzinama */ void saberi(int a[], int duzina_a, int b[], int duzina_b, int rezultat[], int* duzina_rezultata) { int i; /* Prenos sa prethodne pozicije */ int prenos = 0; /* Sabiranje vrsimo dok ne prodjemo sve cifre duzeg od brojeva a i b */ for(i=0; i<duzina_a || i<duzina_b; i++) { /* Sabiramo i-tu cifra broja a (ako postoji) sa i-tom cifrom broja b (ako postoji) i prenos sa prethodne pozicije */ int cifra_rezulata = ((i<duzina_a)? a[i] : 0) + ((i<duzina_b)? b[i] : 0) + prenos; /* Nova cifra rezultata */ rezultat[i] = cifra_rezulata%10; /* Prenos na sledecu poziciju */ prenos = cifra_rezulata/10; } /* Kada smo zavrsili sa svim ciframa brojeva a i b moguce je da je postojao prenos na sledecu poziciju u kom slucaju uvecavamo duzinu rezultata. Inace duzinu rezultata postavljamo na duzinu duzeg od brojeva a i b, koja se nalazi trenutno u promenjivoj i */ if (prenos != 0) { if (i>=MAX_CIFRE) {printf("Doslo je do prekoracenja!\n"); exit(1);} else {

36

Milena Vujo sevi cJani ci c

4.2 Veliki brojevi

rezultat[i] = prenos; *duzina_rezultata = i+1; } } else *duzina_rezultata=i; return; } /* Funkcija mnozi broj, dat svojim nizom cifara i duzinom, datom cifrom c */ void pomnozi_cifrom (int a[], int duzina_a, int cifra, int rezultat[], int* duzina_rezultata) { int i, prenos = 0; for (i=0; i<duzina_a; i++) { /* Svaku cifru broja a mnozimo cifrom c, dodajemo na to prenos sa prethodne pozicije i to smestamo u promenljivu pom */ int pom = a[i]*cifra + prenos; /* Nova cifra rezultata */ rezultat[i] = pom%10; /* Prenos na sledecu poziciju */ prenos = pom/10; } /* Kada smo zavrsili sa svim ciframa broja a, moguce je da je postojao prenos na sledecu poziciju u kom slucaju uvecavamo duzinu rezultata. Inace duzinu rezultata postavljamo na duzinu broja a, koja se nalazi trenutno u promenjivoj i */ if (prenos != 0) { if (i>=MAX_CIFRE) {printf("Doslo je do prekoracenja !\n"); exit(1);} else { rezultat[i] = prenos; *duzina_rezultata = duzina_a+1; } } else

37

Milena Vujo sevi cJani ci c

4.2 Veliki brojevi

*duzina_rezultata = duzina_a; } /* Funkcija mnozi dva broja data svojim nizovima cifara i duzinama*/ void pomnozi (int a[], int duzina_a, int b[], int duzina_b, int rezultat[], int* duzina_rezultata) { /* Ova funkcija se gradi kombinovanjem algoritama mnozenja broja cifrom i sabiranja dva broja */ int i,j,k; /* Broj pom ce da sadrzi rezultat mnozenja broja a jednom po jednom cifrom broja b, dok ce na broj rezultat da se dodaje svaki put (10^i)*pom */ int pom[MAX_CIFRE], duzina_pom, prenos; *duzina_rezultata = 0; /* b=351 * a=123 --------------1 korak ===> rez: 351 * 3 = 1053 2 korak ===> 351 * 2 = 702 -----------------rez: 8073 3 korak ===> 351 * 1 = 351 -----------------rez: 43173 */ /* Za svaku cifru broja a */ for (i=0; i<duzina_a; i++) { /* vrsimo mnozenje broja b i-tom cifrom broja a */ pomnozi_cifrom(b, duzina_b, a[i], pom, &duzina_pom); /* Zatim dodajemo broj pom na rezultat, ali dodavanje pocinjemo od i-te cifre rezultata.*/ prenos = 0; for (k=0; k<duzina_pom; k++) { int pm=((i+k<*duzina_rezultata) ? rezultat[i+k] : 0) + pom[k] + prenos; rezultat[i+k] = pm%10; prenos = pm/10;

38

Milena Vujo sevi cJani ci c

4.2 Veliki brojevi

} if (prenos) { rezultat[i+k] = prenos; *duzina_rezultata = i+k+1; } else *duzina_rezultata = i+k; } } /* Primer koriscenja funkcija */ main() { /* duzina brojeva a i b */ int duzina_a, duzina_b; /* nizovi cifara brojeva a i b */ int a[MAX_CIFRE], b[MAX_CIFRE], zbir[MAX_CIFRE], proizvod[MAX_CIFRE]; /* Ucitavaju se brojevi */ printf("Unesite prvi broj : "); duzina_a = uzmi_broj(a,MAX_CIFRE); printf("Unesite drugi broj : "); duzina_b = uzmi_broj(b, MAX_CIFRE); /* Sabiraju se i ispisuje se zbir */ saberi(a, duzina_a, b, duzina_b, zbir, &duzina_zbira); printf("Zbir je : "); ispisi_broj(zbir, duzina_zbira); /* Mnoze se i ispisuje se proizvod */ pomnozi(a, duzina_a, b, duzina_b, proizvod, &duzina_proizvoda); printf("Proizvod je : "); ispisi_broj(proizvod, duzina_proizvoda); }

39

Glava 5

Sortiranje
Niz mo ze biti sortiran ili ured jen u opadaju cem poretku: niz [0] > niz [1] > ... > niz [i] > niz [i + 1] > ... > niz [n 1] rastu cem poretku: niz [0] < niz [1] < ... < niz [i] < niz [i + 1] < ... < niz [n 1] neopadaju cem poretku: niz [0] niz [1] ... niz [i] niz [i + 1] ... niz [n 1] nerastu cem poretku: niz [0] niz [1] ... niz [i] niz [i + 1] ... niz [n 1] U nastavku su dati primeri algoritama za sortiranje u nerastu cem poretku celobrojnog niza. Jednostavnim modikacijama svakim od ovih algoritama niz se mo ze sortirati i u opadaju cem, rastu cem ili neopadaju cem poretku.

5.1

Vremenska slo zenost

Pogledati slike koje oslikavaju vremensku slo zenost za razli cite algoritme i razli cite nizove: Rezultati dobijeni za slucajno generisane nizove: http://www.matf.bg.ac.yu/~filip/pp/0405/random-sort.gif Rezultati dobijeni za vec sortirane nizove: http://www.matf.bg.ac.yu/~filip/pp/0405/sorted-sort.gif Rezultati dobijeni za naopako sortirane nizove: http://www.matf.bg.ac.yu/~filip/pp/0405/reverse-sorted-sort.gif
40

Milena Vujo sevi cJani ci c

5.2 Selection sort

5.2

Selection sort

Primer 5.1 (Selection sort) U prvom prolazu se razmenjuju vrednosti a[0] sa onim clanovima ostatka niza koji su ve ci od njega. Na taj na cin ce se posle prvog prolaza kroz niz a[0] postaviti na najve ci element niza. U svakom slede cem prolazu, postavlja se na i-to mesto najveci element niza a[i], a[i+1] ... a[n-1]. Primer sortiranja: n = 6 ------------i = 0 j = 1 => i = 0 j = 2 => i = 0 j = 3 => i = 0 j = 4 => i = 0 j = 5 => ------------i = 1 j = 2 => i = 1 j = 3 => i = 1 j = 4 => i = 1 j = 5 => ... #include<stdio.h> #include<stdlib.h> #define MAXDUZ 100 void selection(int a[], int n) { /* pomocna i brojacke promenljive */ int pom,i,j; /*Sortiranje*/ for(i=0; i<n-1; i++) for(j=i+1; j<n; j++) if(a[i]<a[j]) { pom=a[i]; a[i]=a[j]; a[j]=pom; } } main() { /* Niz od maksimalno MAXDUZ elemenata*/
41

a: 1 3 5 2 6 8 a: a: a: a: a: a: a: a: a: 3 5 5 6 8 8 8 8 8 1 1 1 1 1 3 3 5 6 5 3 3 3 3 1 1 1 1 2 2 2 2 2 2 2 2 2 6 6 6 5 5 5 5 3 3 8 8 8 8 6 6 6 6 5

Milena Vujo sevi cJani ci c

5.2 Selection sort

int a[MAXDUZ]; /* Dimenzija niza, pomocna i brojacke promenljive */ int n,i; printf("Unesite dimenziju niza\n"); scanf("%d",&n); if (n>MAXDUZ) { printf("Nedozvoljena vrednost za n\n"); exit(1); } /* Unos clanova niza */ for(i=0; i<n; i++) { printf("Unesite %d. clan niza\n",i+1); scanf("%d",&a[i]); } selection(a,n); /* Ispis niza */ printf("Sortirani niz:\n"); for(i=0; i<n; i++) printf("%d\t",a[i]); putchar(\n); } Primer 5.2 (Selection sort2) Modikacija prethodnog re senja radi dobijanja na ekasnosti. Ne vr se se zamene svaki put ve c samo jednom, kada se pronad e odgovaraju ci element u nizu sa kojim treba izvr siti zamenu tako da u nizu bude postavljen trenutno najve ci element na odgovaraju ce mesto. Primer sortiranja: n = 6 a: ------------i = 0 => a: ------------i = 1 => a: ------------i = 2 => a: ------------1 3 5 2 6 8 8 1 5 2 6 3 8 6 5 2 1 3 8 6 5 2 1 3

42

Milena Vujo sevi cJani ci c

5.3 Bubble sort

... void selection2(int a[], int n) { int i, j, max; /*Sortiranje - bez stalnih zamena vec se pronalazi indeks trenutno najveceg clana niza*/ for(i=0; i<n-1; i++) { max = i; for(j=i+1; j<n; j++) if(a[max]<a[j]) max=j; /* Vrsi se zamena onda kada na i-tom mestu nije najveci element. Tada se na i-to mesto postavlja najveci element koji se nalazio na mestu ind. */ if(i != max) { pom=a[max]; a[max]=a[i]; a[i]=pom; } } }

5.3

Bubble sort

Primer 5.3 (Bubble sort) Algoritam sortiranja buble sort poredi dva susedna elementa niza i ako su pogre sno raspored eni zamenjuje im mesta. Posle pored enja svih susednih parova najmanji od njih ce isplivati na kraj niza. Zbog toga se ovaj metod naziva metod mehuri ca. Da bi se najmanji broj nesortiranog dela niza doveo na svoje mesto treba ponoviti postupak. Primer sortiranja: n = 6 ------------i = 5 j = 0 => i = 5 j = 1 => i = 5 j = 2 => i = 5 j = 3 => i = 5 j = 4 => ------------a: 1 3 5 2 6 8 a: a: a: a: a: 3 3 3 3 3 1 5 5 5 5 5 1 2 2 2 2 2 1 6 6 6 6 6 1 8 8 8 8 8 1

43

Milena Vujo sevi cJani ci c

5.3 Bubble sort

i = 4 j = 0 => a: 5 3 2 6 8 1 ... void bubble(int a[], int n) { int i, j, pom; for(i=n-1; i>0; i--) for(j=0; j<i; j++) if(a[j]<a[j+1]) { pom=a[j]; a[j]=a[j+1]; a[j+1]=pom; } } Primer 5.4 (Bubble sort 2) Unapredjujemo prethodni algoritam kako bismo obezbedli da se ne vr se provere onda kada je niz ve c sortiran nego da se u tom slu caju prekine rad. void bubble2(int a[], int n) { /* Promenljiva koja govori da li je izvrsena zamena u i-tom prolazu kroz niz pa ako nije sortiranje je zavrseno jer su svaka dva susedna elementa niza u odgovarajucem poretku */ int zam; zam=1; for(i=n-1; zam && i>0; i--) for(zam=0,j=0; j<i; j++) if(a[j]<a[j+1]) { /* Zamena odgovarajucih clanova niza */ pom=a[j]; a[j]=a[j+1]; a[j+1]=pom; /* Posto je u i-tom prolazu izvrsena bar ova zamena zam se postavlja na 1 sto nastavlja sortiranje */ zam=1; }
44

Milena Vujo sevi cJani ci c

5.4 Insertion sort

5.4

Insertion sort

Primer 5.5 (Insertion sort) U svakom trenutku je po cetak niza sortiran, a sortiranje se vr si tako sto se jedan po jedan element niza sa kraja ubacuje na odgovaraju ce mesto. Primer sortiranja: n = 6 ------------i = 1 j = 1 => ------------i = 2 j = 2 => i = 2 j = 1 => ------------i = 3 j = 3 => ------------i = 4 j = 4 => ... a: 1 3 5 2 6 8 a: 3 1 5 2 6 8 a: 3 5 1 2 6 8 a: 5 3 1 2 6 8 a: 5 3 2 1 6 8 a: 5 3 2 6 1 8

void insertion(int a[], int n) { /* Pomocna i brojacke promenljive */ int pom,i,j; for(i=1; i<n; i++) for(j=i; (j>0) && (a[j]>a[j-1]); j--) { pom=a[j]; a[j]=a[j-1]; a[j-1]=pom; } } Primer 5.6 (Insertion sort 2) Broj dodela se moze redukovati tako sto se umesto stalnih zamena koristi dodela privremenoj promenljivoj elemente niza pomeramo za jedno mesto sve dok ne nad emo poziciju na koju treba da postavimo dati element (koji je sa cuvan u privremenoj promenljivoj). void insertion2(int a[], int n) { int i, j, tmp; for (i = 1; i < n; i++) { int tmp = a[i];
45

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

for (j = i; j > 0 && a[j-1] < tmp; j--){ a[j] = a[j-1]; } a[j] = tmp; } }

5.5

Razni zadaci

Primer 5.7 Napisati funkciju koja od dva sortirana niza formira tre ci sortiran niz (nizovi su sortirani neopadaju cie). #include <stdio.h> /* Funkcija spaja dva uredjena niza: a koji ima na elemenata i b koji ima nb elemenata i na osnovu njih gradi uredjeni niz c ciji broj elemenata vraca. Pretpostavlja se da u nizu c ima dovoljno prostora. */ int merge(int a[], int na, int b[], int nb, int c[]) { /* Dok god ima elemenata u oba niza uzima se manji od dva tekuca i prepisuje u niz c */ int i = 0, j = 0, k = 0; while(i < na && j < nb) { if (a[i] < b[j]) c[k++] = a[i++]; else c[k++] = b[j++]; } /* Prepisujemo eventualno preostale elemente niza a */ for (; i < na; i++, k++) c[k] = a[i]; /* Prepisujemo eventualno preostale elemente niza b */ for (; j < nb; j++, k++) c[k] = b[j]; return k; }

46

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

main() { int int int int int int int i; a[] = {1, 5, 6, 9, 13}; b[] = {2, 3, 7, 11, 12, 15}; c[30]; na = sizeof(a)/sizeof(int); nb = sizeof(b)/sizeof(int); nc = merge(a, na, b, nb, c);

for (i = 0; i < nc; i++) printf("%d ", c[i]); printf("\n"); } Primer 5.8 Program u citava niz studenata i sortira ih po njihovim ocenama. #include <stdio.h> #include <ctype.h> #define MAX_IME 20

typedef struct _student { char ime[MAX_IME]; char prezime[MAX_IME]; int ocena; } student; /* Funkcija ucitava rec i vraca njenu duzinu ili -1 ukoliko smo dosli do znaka EOF*/ int getword(char word[],int max) { int c, i=0; while (isspace(c=getchar())) ; while(!isspace(c) && c!=EOF && i<max-1) { word[i++]=c; c = getchar(); } word[i]=\0; if (c==EOF) return -1;
47

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

else return i; } /* Funkcija ucitava niz studenata, vraca duzinu niza koji ucita */ int UcitajPodatke(student studenti[], int max) { int i=0; while(i<max && getword(studenti[i].ime, MAX_IME)>0) { if (getword(studenti[i].prezime, MAX_IME) < 0) break; scanf("%d",&studenti[i].ocena); i++; } return i; } void IspisiPodatke(student studenti[], int br_studenata) { int i; printf("IME PREZIME OCENA\n"); printf("--------------------------------------\n"); for (i=0; i<br_studenata; i++) printf("%-20s %-20s %5d\n",studenti[i]. ime, studenti[i].prezime, studenti[i].ocena); } /* Sortiranje studenata po ocenama */ void SelectionSort(student studenti[], int br_studenata) { int i,j; for (i=0; i<br_studenata-1; i++) for (j=i; j<br_studenata; j++) if (studenti[i].ocena<studenti[j].ocena) { student tmp=studenti[i]; studenti[i]=studenti[j]; studenti[j]=tmp; } } main() { student studenti[100];

48

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

int br_studenata = UcitajPodatke(studenti,100); SelectionSort(studenti, br_studenata); IspisiPodatke(studenti, br_studenata); } Primer 5.9 Program u citava informacije o studentima iz datoteke cije se ime zadaje iz komandne linije, i koja je u formatu: n prezime1 ime1 smer1 prosek1 prezime2 ime2 smer2 prosek2 ... prezimen imen smern prosekn Studenti se sortiraju leksikografski po prezimenima i imenima, a zatim se njihovi podaci tako sortirani ispisuju na izlaz, ili u izlazni fajl, ako je njegovo ime zadato u komandnoj liniji. #include <stdio.h> #include <string.h> #define MAX_IME 20 #define MAX_PREZIME 30 #define MAX_SMER 10 #define MAX_STUDENATA 100 /* Struktura koja predstavlja informacije o studentu */ typedef struct student { char ime[MAX_IME]; char prezime[MAX_PREZIME]; char smer[MAX_SMER]; double prosek; } Student;

/* Funkcija uporedjuje dva studenta po leksikografskom poretku njihovih imena i prezimena. Funkcija vraca vrednost manju od nule ako je prvi student "manji" od drugog, vrednost vecu od nule ako je prvi student "veci" od drugog, dok nulu vraca ako se studenti zovu isto. */ int uporedi(Student *s, Student *p) { int t;
49

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

/* Najpre poredimo prezimena. Ako su prezimena ista, onda poredimo i imena. */ if((t = strcmp(s->prezime, p->prezime)) != 0) return t; else return strcmp(s->ime, p->ime); }

/* Funkcija sortira niz studenata u leksikografskom poretku */ void sortiraj(Student studenti[], int n) { int i, j; int min; Student pom; /* Studenti se porede funkcijom uporedi(). Zamena elemenata niza se obavlja na uobicajen nacin, zato sto je dodela struktura legitimna operacija i zato sto struktura ne sadrzi pokazivace za koje je memorija alocirana dinamicki. */ for (i = 0; i < n - 1; i++) { min = i; for (j = i + 1; j < n; j++) if (uporedi(&studenti[j], &studenti[min]) < 0) min = j; if (min != i) { pom = studenti[i]; studenti[i] = studenti[min]; studenti[min] = pom; } } } /* Funkcija ucitava podatke o studentu */ void ucitaj(FILE *in, Student *s) { fscanf(in, "%s", s->prezime); fscanf(in, "%s", s->ime); fscanf(in, "%s", s->smer);

50

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

fscanf(in, "%lf", &s->prosek); } /* Ispisujemo informacije o studentu */ void ispisi(FILE *out, Student *s) { fprintf(out, "%s %s %s %f\n", s->prezime, s->ime, s->smer, s->prosek); } /* Funkcija main */ int main (int argc, char ** argv) { int n, i; FILE *in = stdin, *out = stdout; Student studenti[MAX_STUDENATA]; /* Ako je korisnik uneo ime ulaznog fajla...*/ if(argc > 1) if((in = fopen(argv[1], "r")) == NULL) { fprintf(stderr, "Greska prilikom otvaranja fajla!\n"); return 1; } /* Ako je korisnik uneo i ime izlaznog fajla...*/ if(argc > 2) if((out = fopen(argv[2], "w")) == NULL) { fprintf(stderr, "Greska prilikom otvaranja fajla!\n"); return 1; } /* Ucitavamo broj studenata */ fscanf(in, "%d", &n); /* Ucitavamo informacije o studentima */ for(i = 0; i < n; i++) ucitaj(in, &studenti[i]); /* Sortiramo niz studenata */ sortiraj(studenti, n); /* Ispisujemo niz studenata */

51

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

for(i = 0; i < n; i++) ispisi(out, &studenti[i]); fclose(in); fclose(out); return 0; } Primer 5.10 Program utvrd uje da li su dve niske karaktera anagrami. Dve niske su anagrami ako se sastoje od istog broja istih karaktera. Na primer, niske trave i vetar jesu anagrami, dok vetar i vatra nisu. #include <stdio.h> #include <string.h> #define MAX 1024 /* Funkcija sortira karaktere stringa s */ void sortiraj (char s[]) { int i, j; int min; char pom; int n; /* Racunamo duzinu stringa */ for (n = 0; s[n] != \0; n++) ; /* Ostatak funkcije je identican funkciji za sortiranje celih brojeva. */ for (i = 0; i < n - 1; i++) { min = i; for (j = i + 1; j < n; j++) if (s[j] < s[min]) min = j; if (min != i) { pom = s[i]; s[i] = s[min]; s[min] = pom; } }
52

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

} /* Funkcija utvrdjuje da li su reci s i t anagrami. Za reci kazemo da su anagrami, ako se jedna rec moze dobiti od druge premestanjem slova u reci */ int anagrami (char *s, char *t) { char sp[MAX]; char tp[MAX]; /* Kopiramo stringove (reci) u pomocne nizove sp i tp zato sto ne zelimo da nasa funkcija promeni originalne nizove. */ strcpy (sp, s); strcpy (tp, t); /* Sortiramo karaktere stringova u pomocnim nizovima */ sortiraj (sp); sortiraj (tp); /* Ako su stringovi nakon sortiranja jednaki, tada su polazne reci bile anagrami */ return strcmp (sp, tp) == 0; }

/* Test program */ int main () { char s[MAX], t[MAX]; /* Ucitavamo dve reci */ printf ("Uneti prvu rec: "); scanf ("%s", s); printf ("Uneti drugu rec: "); scanf ("%s", t); /* Utvrdjujemo da li su anagrami i ispisujemo poruku */ if (anagrami (s, t)) printf ("Reci %s i %s su anagrami\n", s, t); else printf ("Reci %s i %s nisu anagrami\n", s, t);

53

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

return 0; } Primer 5.11 Interpolaciona pretraga se mo ze porediti sa pretragom re cnika: ako neko tra zi re c na slovo B, sigurno ne ce da otvori re cnik na polovini, ve c verovatno negde bli ze po cetku. /* Funkcija trazi u SORTIRANOM nizu a[] duzine n broj x. Vraca indeks pozicije nadjenog elementa ili -1, ako element nije pronadjen */ int interpolaciona_pretraga (int a[], int n, int x) { int l = 0; int d = n - 1; int s; /* Dokle god je indeks l levo od indeksa d... while (l <= d) { */

/* Ako je element manji od pocetnog ili veci od poslednjeg clana u delu niza a[l],...,a[d] tada nije u tom delu niza. Ova provera je neophodna, da se ne bi dogodilo da se prilikom izracunavanja indeksa s izadje izvan opsega indeksa [l,d] */ if(x < a[l] || x > a[d]) return -1; /* U suprotnom, x je izmedju a[l] i a[d], pa ako su a[l] i a[d] jednaki, tada je jasno da je x jednako ovim vrednostima, pa vracamo indeks l (ili indeks d, sve jedno je).Ova provera je neophodna, zato sto bismo inace prilikom izracunavanja s imali deljenje nulom. */ else if(a[l] == a[d]) return l; /* Indeks s je uvek izmedju l i d, ali ce verovatno biti blize trazenoj vrednosti nego da smo prosto uvek uzimali srednji element.*/ /* Racunamo sredisnji indeks */ s = l + ((double)(x - a[l]) / (a[d] - a[l])) * (d - l); /* Ako je sredisnji element veci od x, tada se x mora nalaziti u levoj polovini niza */ if (x < a[s]) d = s - 1;
54

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

/* Ako je sredisnji element manji od x, tada se x mora nalaziti u desnoj polovini niza */ else if (x > a[s]) l = s + 1; else /* Ako je sredisnji element jednak x, tada smo pronasli x na poziciji s */ return s; } /* ako nije pronadjen vracamo -1 */ return -1; } Primer 5.12 Data su dva rastu ca niza A i B, napisati funkciju koja formira niz C koji sadr zi: (a) zajedni cke elemente nizova A i B (presek skupova A i B). (b) elemente koje sadr zi niz A a ne sadr zi niz B (razlika skupova A i B). (c) sve elemente koje sadr ze nizovi A i B (unija skupova A i B) ovo je ekvivalentno spajanju dva ured ena niza primer 5.7. /* Funkcija kreira presek dva rastuca niza a i b i rezultat smesta u niz c (takodje u rastucem radosledu). Funkcija vraca broj elemenata preseka */ int kreiraj_presek(int a[], int n_a, int b[], int n_b, int c[]) { int i = 0, j = 0, k = 0; /* Dokle god ima elemenata u oba niza... */ while (i < n_a && j < n_b) { /* Ako su jednaki, ubacujemo vrednost u presek, i prelazimo na sledece elemente u oba niza. */ if(a[i] == b[j]) { c[k++] = a[i]; i++; j++; } /* Ako je element niza a manji, tada u tom nizu prelazimo na sledeci element */ else if(a[i] < b[j])
55

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

i++; /* Ako je element niza b manji, tada u tom nizu prelazimo na sledeci element */ else j++; } /* Vracamo broj elemenata preseka */ return k; } /* Funkcija kreira razliku dva rastuca niza a i b i rezultat smesta u niz c (takodje u rastucem radosledu). Funkcija vraca broj elemenata razlike */ int kreiraj_razliku (int a[], int n_a, int b[], int n_b, int c[]) { int i = 0, j = 0 , k = 0; /* Sve dok ima elemenata u oba niza...*/ while(i < n_a && j < n_b) { /* Ako je tekuci element niza a manji, tada je on u razlici, jer su svi sledeci elementi niza b jos veci. Ubacujemo ga u razliku i prelazimo na sledeci element niza a */ if(a[i] < b[j]) c[k++] = a[i++]; /* Ako je tekuci element niza b manji, tada prelazimo na sledeci element u nizu b */ else if (a[i] > b[j]) j++; /* Ako su jednaki, tada ih oba preskacemo. Tekuci element niza a ocigledno nije u razlici. */ else { i++; j++; } } /* Ako su preostali elementi niza a, tada su oni u razlici jer su svi elementi niza b bili manji od tekuceg elementa niza a, i svih koji za njim slede */ while (i < n_a)

56

Milena Vujo sevi cJani ci c

5.5 Razni zadaci

c[k++] = a[i++]; /* Vracamo broj elemenata u razlici */ return k; }

57

Glava 6

Pokaziva ci
Pokaziva c je promenljiva koja sadr zi adresu promenljive. int x=1, y=1; int *ip; /* ip je pokazivac na int, odnosno *ip je tipa int */ ip = &x; y=*ip; /* ip cuva adresu promenljive x, tj ip pokazuje na x */ /* y dobija vrednost onoga sto se nalazi na adresi koju cuva promenljiva ip, tj posto ip cuva adresu promenljive x, a x ima vrednost 1 to je i y sada 1 */ /* preko pokazivaca njema se sadrzaj na adresi koju cuva ip, prema tome, x je sada 0 */ /* x je /* x je /* x je zagrada sada 10*/ sada 11*/ sada 12, neophodna zbog prioriteta operatora*/

*ip = 0;

*ip+=10; ++*ip; (*ip)++;

Pored pokaziva ca na osnovne tipove, postoji i pokaziva c na prazan tip void. void *pp; Njemu mo ze da se dodeli da pokazuje na int, char, float ili na bilo koji drugi tip ali je neophodno eksplicitno naglasiti na koji tip ukazuje pokaziva c svaki put kada zelimo da koristimo sadr zaj adrese koju pokaziva c cuva. Primer 6.1 Upotreba pokaziva ca na prazan tip. #include<stdio.h> main() { void *pp; /* Ovaj pokazivac moze da pokazuje na adrese na kojima se nalaze razliciti tipovi podataka. */ int x=2;
58

Milena Vujo sevi cJani ci c

6.1 Pokaziva cka aritmetika primeri

char c=a; pp = &x; /* Pokazivac sada sadrzi adresu promenljive tipa int*/ *(int *)pp = 17; /* x postaje 17, kroz pokazivac pp je promenjena vrednost promenljive x. Kastovanje (int *) ukazuje na to da se prilikom citanja vrednosti koja se nalazi na adresi koju cuva pokazivac pp, ovaj pokazivac tretira kao pokazivac na tip int */ printf("Adresa od x je %p\n ", &x); printf("%d i %p\n",*(int*)pp,(int * )pp); pp = &c; /* Pokazivac sada sadrzi adresu promenljive tipa char*/ printf("Adresa od c je %p\n", &c); printf("%c i %p\n",*(char*)pp,(char * )pp); /* Kastovanje (char *) ukazuje na to da se prilikom citanja vrednosti koja se nalazi na adresi koju cuva pokazivac pp, ovaj pokazivac tretira kao pokazivac na tip char */ } /* Adresa od x je 0012FF78 17 i 0012FF78 Adresa od c je 0012FF74 a i 0012FF74 */ Posebna konstanta koja se koristi da se ozna ci da pokaziva c ne pokazuje na neko mesto u memoriji je NULL. Ova konstanta je denisana u biblioteci stdio.h i ima vrednost 0.

6.1

Pokaziva cka aritmetika primeri

Primer 6.2 Funkcija proverava da li se neka vrednost x nalazi u nizu niz dimenzije n, bez kori s cenja indeksiranja. Funkcija vra ca pokaziva c na poziciju pronadjenog elementa ili NULL ukoliko element nije pronad en. #include <stdio.h> int* nadjiint(int* niz, int n, int x) { while (--n >= 0 && *niz != x) niz++;

59

Milena Vujo sevi cJani ci c

6.1 Pokaziva cka aritmetika primeri

return (n>=0) ? niz : NULL; } main() { int a[]={1,2,3,4,5,6,7,8}; int* poz=nadjiint(a,sizeof(a)/sizeof(int),4); if (poz!=NULL) printf("Element pronadjen na poziciji %d\n",poz-a); } Primer 6.3 Funkcije izra cunavaju du zinu niske kori s cenjem pokaziva ca. int string_length1(char *s) { int n; for(n=0; *s != \0; s++) n++; return n; } /* Druga varijanta iste funkcije */ int string_length2(char *s) { char* t; for (t = s; *t; t++) ; return t - s; } Primer 6.4 Funkcija kopira string t u string s (podrazumeva se da je za string s rezervisano dovoljno mesta). void copy(char* dest, char* src) { while (*dest++=*src++) ; } /* Ovo je bio skraceni zapis za sledeci kod while(*src != \0) { *dest=*src; dest++; src++;
60

Milena Vujo sevi cJani ci c

6.1 Pokaziva cka aritmetika primeri

} *dest = \0; */ Primer 6.5 Nadovezuje string t na kraj stringa s. Pretpostavlja da u s ima dovoljno prostora. void string_concatenate(char *s, char *t) { /* Pronalazimo kraj stringa s */ while (*s) /* while (*s != 0)*/ s++; /* Nakon prethodne petlje, s pokazuje na \0, kopiranje pocinje od tog mesta, tako da ce znak \0 biti prepisan. */ /* Kopiranje se vrisi slicno funkciji string_copy */ while (*s++ = *t++) ; } Primer 6.6 Funkcija strcmp vr si leksikografsko pored enje dva stringa. Vra ca: 0 ukoliko su stringovi jednaki <0 ukoliko je s leksikografski ispred t >0 ukoliko je s leksikografski iza t int strcmp(char *s, char *t) { /* Petlja tece sve dok ne naidjemo na prvi razliciti karakter */ for (; *s == *t; s++, t++) if (*s == \0) /* Naisli smo na kraj oba stringa, a nismo nasli razliku */ return 0;

/* *s i *t su prvi karakteri u kojima se niske razlikuju. Na osnovu njihovog odnosa, odredjuje se odnos stringova */ return *s - *t; } Primer 6.7 Pronalazi prvu poziciju karaktera c u stringu s, i vra ca pokaziva c na nju, odnosno NULL ukoliko s ne sadr zi c. char* string_char(char *s, char c) {
61

Milena Vujo sevi cJani ci c

6.1 Pokaziva cka aritmetika primeri

int i; for (; *s; s++) if (*s == c) return s; /* Nije nadjeno */ return NULL; } Primer 6.8 Pronalazi poslednju poziciju karaktera c u stringu s, i vra ca pokaziva c na nju, odnosno NULL ukoliko s ne sadr zi c. char* string_last_char(char *s, char c) { char *t = s; /* Pronalazimo kraj stringa s */ while (*t) t++; /* Krecemo od kraja i trazimo c unazad */ for (t--; t >= s; t--) if (*t == c) return t; /* Nije nadjeno */ return NULL; } Primer 6.9 Za svaku liniju ucitanu sa ulaza proverava se da li sadr zi re c zdravo. #include <stdio.h> /* Proverava da li se niska t nalazi unutar niske s Vraca poziciju na kojoj string pocinje odnosno -1 ukoliko niska t nije podniska niske s. */ int sadrzi_string(char s[], char t[]) { int i; for (i = 0; s[i]; i++) { int j; for (j=0, k=0; s[i+j]==t[j]; j++) if (t[j+1]==\0) return i; } return -1;
62

Milena Vujo sevi cJani ci c

6.1 Pokaziva cka aritmetika primeri

} /* Proverava da li string str sadrzi string sub. Vraca pokazivac na kojoj sub pocinje, odnosno NULL ukoliko ga nema. */ char* string_string(char *str, char *sub) { char *s, *t; /* Proveravamo da li sub pocinje na svakoj poziciji i */ for (; *str; str++) /* Poredimo sub sa str pocevsi od pozicije na koju ukazuje str sve dok ne naidjemo na razliku */ for (s = str, t = sub; *s == *t; s++, t++) /* Nismo naisli na razliku a ispitali smo sve karaktere niske sub */ if (*(t+1) == \0) return str; /* Nije nadjeno */ return NULL; } /* Cita liniju sa stadnardnog ulaza i vraca njenu duzinu */ int getline(char* line, int max) { char *s=line; int c; while ( max-->0 && (c=getchar())!=\n && c!=EOF) *s++ = c; /* ekvivalentno sa *s=c; s++; */ if (c==\n) *s++ = c; *s = \0; return s - line; } main() { char rec[]="zdravo"; char linija[100]; while (getline(linija, 100)) if (sadrzi_string_pok(linija, rec))

63

Milena Vujo sevi cJani ci c 6.2 Niska karaktera i pokaziva c na konstantnu nisku

printf("%s",linija); }

6.2

Niska karaktera i pokaziva c na konstantnu nisku

Prilikom deklaracije treba praviti razliku izmed u niza znakova i pokaziva ca na konstantnu nisku. Ukoliko su date deklaracije: char poruka[]="danas je lep dan!"; char *pporuka = "danas je lep dan!"; tada va zi: poruka je niz znakova koji sadr zi tekst danas je lep dan!. Pojedine znake mogu ce je promeniti, na primer, nakon naredbe poruka[0] = D; poruka ce sadr zati tekst Danas je lep dan!. Niska poruka se uvek odnosi na novo mesto u memoriji. pporuka je pokaziva c, koji je inicijalizovan da pokazuje na konstantnu nisku, on mo ze biti preusmeren da pokazuje na ne sto drugo, na primer, mogu ce je preusmeriti ovaj pokaziva c slede com naredbom pporuka = poruka; i u ovom slu caju ce pokaziva c pporuka pokazivati na mesto u memoriji na kojem se nalazi poruka. Poku saj modikacije elementa niske karaktera na koju ukazuje pporuka nije denisan (jer je to konstantna niska). Na primer, rezultat naredbe pporuka[0]=D je nedenisan. Ako deklari semo char *pporuka1 = "danas je lep dan!"; char *pporuka2 = "danas je lep dan!"; char *pporuka3 = "danas pada kisa"; tada ce pokaziva ci pporuka1 i pporuka2 pokazivati na isto mesto u memoriji, a pporuka3 na drugo mesto u memoriji. Ako uporedimo (pporuka1==pporuka3) uporedi ce se vrednosti pokaziva ca. Ako uporedimo (pporuka1 < pporuka2) uporedi ce se vrednosti pokaziva ca. Ako dodelimo pporuka1=pporuka3 tada ce pporuka1 dobiti vrednost pokaziva ca pporuka3 i pokaziva ce na isto mesto u memoriji. Ne ce se izvr siti kopiranje sadr zaja memorije. Primer 6.10 Nakon deklaracije char a[] = "informatika"; char *p = "informatika";
64

Milena Vujo sevi cJani ci c

6.3 Pokaziva ci na pokaziva ce

Loliko elemenata ima niz a? Koju vrednost ima a? Da li se mo ze promeniti vrednost a? Koju vrednost ima a[0]? Da li se mo ze promeniti vrednost a[0]? Koju vrednost ima p? Da li se mo ze promeniti vrednost p? Koju vrednost ima p[0]? Da li se mo ze promeniti vrednost p[0]?

6.3

Pokaziva ci na pokaziva ce

Pokaziva cke promenljive su podaci kao i svi drugi, pa samim tim imaju svoju adresu u memoriji. Zbog toga je mogu ce govoriti o pokaziva cima na pokaziva ce. Na primer, ako je denisan pokaziva c na int: int *p; tada mozemo denisati pokaziva c na pokaziva c na int: int **pp; Ovaj pokaziva c moze uzeti adresu pokaziva cke promenljive p: pp = &p; Nakon toga se pokaziva ckoj promenljivoj p moze pristupiti i indirektno, preko pokaziva ca na pokaziva c pp, zato sto je *pp ekvivalentno sa p. Takod e, celom broju na koji pokazuje p moze se pristupiti i sa **pp zato sto je **pp ekvivalentno sa *p.

6.4

Nizovi pokaziva ca

Elementi nizova mogu biti bilo kog tipa, pa i pokaziva ci. Na primer, niz pokaziva ca na karaktere se mo ze denisati na slede ci na cin: char * pokazivaci[100]; Ovim se rezervi se prostor za niz od 100 pokaziva ca na karaktere. Njima se pristupa putem indeksa, kako je i uobi cajeno. S obzirom da se ime niza uvek pona sa kao adresa prvog objekta u nizu, sledi da je izraz pokazivaci tipa pokaziva c na pokaziva c na char. Otuda se uobi cajena pokaziva cka aritmetika moze primenjivati i na nizove pokaziva ca.

65

Milena Vujo sevi cJani ci c

6.4 Nizovi pokaziva ca

Primer 6.11 Program ucitava linije iz fajla ulaz.txt i zatim ih ispisuje na standardnom izlazu sortirane po du zini, po cev od najkra ce linije. #include #include #include #include <stdio.h> <stdlib.h> <ctype.h> <string.h>

#define MAX_RED 1024 #define MAX_REDOVA 1024 /* Funkcija sortira niz pokazivaca na karaktere, pri cemu je kriterijum sortiranja duzina stringova na koje pokazuju ti pokazivaci. */ void sortiraj(char *redovi[], int n); int main() { char redovi[MAX_REDOVA][MAX_RED]; char *p_redovi[MAX_REDOVA]; int i, n; FILE * in; /* Otvaramo fajl */ in = fopen("ulaz.txt", "r"); /* Proveravamo da li smo uspesno otvorili fajl */ if(in == NULL) { printf("Greska! Fajl nije uspesno otvoren!\n"); exit(1); } /* Citamo linije, i smestamo ih u dvodimenzioni niz karaktera redovi[] (i-tu liniju smestamo u niz redovi[i]). Citamo najvise MAX_REDOVA. Pokazivac p_redovi[i] postavljamo da pokazuje na prvi karakter u nizu redovi[i]. */ for(i = 0; i < MAX_REDOVA; i++) if(fgets(redovi[i], MAX_RED, in) != NULL) p_redovi[i] = redovi[i]; else break; n = i;

66

Milena Vujo sevi cJani ci c

6.4 Nizovi pokaziva ca

/* Zatavaramo fajl */ fclose(in); /* NAPOMENA: Umesto da sortiramo niz nizova, sto podrazumeva zamenu vrednosti citavih nizova, efikasnije je da definisemo niz pokazivaca na karaktere koje cemo da usmerimo najpre na odgovarajuce stringove koji se nalaze u nizu nizova redovi[] (Pokazivac p_redovi[i] se se usmerava na string u nizu redovi[i]). Nakon toga se sortira niz pokazivaca, tj. pokazivaci se ispremestaju tako da redom pokazuju na stringove po rastucim duzinama. Ovim je sortiranje bitno efikasnije jer je brze razmeniti dva pokazivaca nego citave nizove. */ /* Sortiramo niz pokazivaca */ sortiraj(p_redovi, n);

/* Prikazujemo linije u poretku rasta duzina */ for(i = 0; i < n; i++) fputs(p_redovi[i], stdout); return 0; }

/* Funkcija razmenjuje vrednosti dva pokazivaca na karaktere. S obzirom da je potrebno preneti adrese ovih promenljivih, parametri su tipa "pokazivac na pokazivac na char". */ void razmeni(char **s, char **t) { char *p; p = *s; *s = *t; *t = p; } /* Funkcija implementira uobicajeni algoritam sortiranja izborom najmanjeg elementa, pri cemu se pod najmanjim elementom ovde podrazumeva pokazivac koji pokazuje na string koji je najkraci */ void sortiraj(char *redovi[], int n) { int i,j, min;

67

Milena Vujo sevi cJani ci c

6.4 Nizovi pokaziva ca

for(i = 0; i < n - 1; i++) { min = i; for(j = i + 1; j < n; j++) if(strlen(redovi[j]) < strlen(redovi[min])) min = j; if(min != i) { razmeni(&redovi[min], &redovi[i]); } } }

68

Glava 7

Rekurzija
Funkcija1 moze da poziva samu sebe, neposredno ili posredno. Ova pojava se zove rekurzija. Ovakav pristup se cesto koristi prilikom resavanja problema koji imaju prirodnu rekurzivnu deniciju, tj. kod kojih se problem dimenzije n moze jednostavno svesti na problem dimenzije n-1 ili neke druge manje dimenzije. Rekurzija je analogna matematickoj indukciji. Ono sto je potencijalni problem kod razumevanja rekurzije je to sto se u jednom trenutku mogu izvrsavati vise poziva jedne iste funkcije. Na primer, ako funkcija f() prilikom svog izvrsavanja pozove samu sebe, tada se zapocinje novi poziv iste funkcije. Prethodni poziv ceka da se zavrsi tekuci, a zatim nastavlja sa radom. Poziv rekurzivne funkcije koji je zapocet, a cije izvrsavanje jos nije zavrseno nazivamo aktivni poziv. Svaki poziv funkcije izvrsava se nezavisno od svih ostalih aktivnih poziva u tom trenutku, zahvaljujuci cinjenici da svaki od aktivnih poziva ima svoje sopostvene kopije formalnih parametara i lokalnih podataka. Kada se neki od poziva zavrsi, njegove kopije nestaju iz memorije, ali kopije ostalih poziva i dalje postoje u memoriji. Posledica ovog pristupa je da kada rekurzivna funkcija promeni vrednosti svojih lokalnih podataka, ove promene ne uticu na ostale aktivne pozive, zato sto oni imaju svoje lokalne kopije istih podataka. Kreiranje i odrzavanje lokalnih kopija se jednostavno i ekasno ostvaruje zahvaljujuci tome sto su parametri funkcije i lokalne nestaticke promenljive smestene na sistemskom steku u pitanju je struktura podataka u kojoj se novi podaci uvek smestaju na vrh a prilikom uklanjanja podataka takodje se uklanja podatak sa vrha, tj. podatak koji je poslednji dodat (LIFO struktura last in, rst out). Prilikom pozivanja bilo koje funkcije najpre se na stek smeste njeni argumenti (one vrednosti koje su predate prilikom poziva) a zatim se na vrh steka smeste i lokalne promenljive. Nakon toga se zapocne izvrsavanje tela funkcije. Ako se tom prilikom pozove neka druga funkcija (ili ta ista, ako je rekurzija u pitanju) tada se na vrh steka dodaju argumenti ovog poziva, kao i lokalne promenljive te funkcije, itd. Kada se funkcijski poziv zavrsi, tada se sa vrha steka skidaju njegove lokalne promenljive, kao i parametri poziva, nakon cega na vrhu steka ostaju lokalne promenljive prethodnog poziva itd.
1 Tekst

preuzet sa www.matf.bg.ac.rs/milan

69

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

Lokalni staticki podaci (promenljive deklarisane kao static unutar funkcije) se ne kreiraju na steku, vec u statickoj zoni memorije, i zajednicki su za sve pozive. Zato se promene vrednosti ovih promenljivih u jednom pozivu vide i iz drugih aktivnih poziva iste funkcije. Zato treba biti oprezan prilikom koriscenja statickih promenljivih u rekurzivnim funkcijama (obicno se to i ne radi). Svaka rekurzivna funkcija mora imati izlaz iz rekurzije kao i rekurzivni poziv kojim se problem svodi na problem nizeg reda. Izlaz iz rekurzije je najcesce jednostavan, ali ne mora uvek da bude tako.

7.1

Osnovni primeri

Primer 7.1 Ra cunanje faktorijela prirodnog broja. #include<stdio.h> unsigned long faktorijel_iterativno(int n) { long f = 1; int i; for (i = 1; i<=n; i++) f *= i; return f; } unsigned long faktorijel(int n) { if(n==0) return 1; else return n*faktorijel(n-1); /* Alternativni zapis: return n == 0 ? 1 : n*faktorijel(n-1); */ } main() { int n; unsigned long f; printf("Unesite n\n"); scanf("%d", &n); f = faktorijel(n);
70

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

printf("f = %d\n",f); } Primer 7.2 Ra cunanje sume prvih n prirodnih brojeva. #include<stdio.h> unsigned suma(unsigned n) { if(n==0) return 0; else return (n + suma(n-1)); /* Najefikasniji nacin za resavanje ovog problema je return (n*(n+1))/2; */ } main() { int s,n; printf("Unesite n\n"); scanf("%d", &n); s = suma(n); printf("s = %d\n",s); } Primer 7.3 Iterativna i rekurzivna varijanta ra cunanja sume niza. int suma_niza_iterativno(int a[], int n) { int suma = 0; int i; for (i = 0; i<n; i++) suma+=a[i]; return suma; } int suma_niza(int a[], int n) { if (n == 1) return a[0]; else return suma_niza(a,n-1) + a[n-1]; /* Skracen zapis je:
71

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

return n == 1 ? a[0] : suma_niza(a,n-1) + a[n-1];*/ } Primer 7.4 Stampanje celog broja. #include<stdio.h> void printb(long int n) { if(n<0) { putchar(-); n=-n; } if(n>=10) printb(n/10); putchar(n % 10 + 0); } int main() { long int b=-1234; printb(b); putchar(\n); return 0; } Kad funkcija rekurzivno pozove sebe, svakim pozivom pojavljuje se novi skup svih automatskih promenljivih, koji je nezavisan od prethodonog skupa. Prva funkcija printb kao argument dobija broj -12345, ona prenosi 1234 u drugu printb funkciju, koja dalje prenosi 123 u tre cu, i tako redom do poslednje koja prima 1 kao argument. Ta funkcija stampa 1 i zavr sava sa radom tako da se vra ca na prethodni nivo, na kome se stampa dva i tako redom. Primer 7.5 Stepenovanje prirodnog broja iterativno rekurzivno, koriste ci cinjenicu da je xk = x xk1 i x0 = 1 rekurzivno, koriste ci cinjenicu da je xk = (x2 ) 2 ako je k parno ili xk = x(x2 ) ako je k neparno, i x0 = 1 i x1 = x. #include <stdio.h> /* Iterativna verzija prethodne funkcije */ int stepen_iterativno(int x, int k) { int i;
72
k k 1 2

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

int s = 1; for (i = 0; i<k; i++) s*=x; return s; } int stepen(int x, int k) { printf("Racunam stepen(%d, %d)\n",x,k); /* Ilustracije radi! */ if (k == 0) return 1; else return x*stepen(x, k-1); /* Krace se moze zapisati na sledeci nacin return k==0 ? 1 : x*stepen(x, k-1); */ } /* Efikasnija verzija rekurzivne funkcije. */ int stepen2(int x, int k) { printf("Racunam stepen2(%d, %d)\n",x,k); /* Ilustracije radi! */ if (k == 0) return 1; else if (k == 1) return x; else /* Ako je stepen paran*/ if((k%2)==0) return stepen2(x*x, k/2); else return x * stepen2(x*x, k/2); } /* Alternativna funkcija stepen2 int stepen2(int n, int k) { int p, s; if (k == 0) s = 1; else if (k == 1) s = n; else

73

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

{ p = stepen(n, k/2); if(k%2 == 0) s = p*p; else s = p*p*n; } return s; } */ main() { printf("Stepen je: %d\n", stepen(2, 8)); printf("------------------\n"); printf("Stepen je: %d\n", stepen2(2, 8)); } /* Izlaz iz programa: Racunam stepen(2, 8) Racunam stepen(2, 7) Racunam stepen(2, 6) Racunam stepen(2, 5) Racunam stepen(2, 4) Racunam stepen(2, 3) Racunam stepen(2, 2) Racunam stepen(2, 1) Racunam stepen(2, 0) Stepen je: 256 -----------------Racunam stepen2(2, 8) Racunam stepen2(4, 4) Racunam stepen2(16, 2) Racunam stepen2(256, 1) Stepen je: 256 */ Primer 7.6 Fibona cijevi brojevi se deni su rekurentno na slede ci na cin: f(0) = 1, f(1) = 1, f(n) = f(n-1) + f(n-2) Napisati funkciju koja izra cunava n-ti Fibona cijev broj. #include <stdio.h> #include <stdlib.h> /* Rekurzivna implementacija - obratiti paznju na neefikasnost funkcije */ int Fib(int n) { printf("Racunam Fib(%d)\n",n); /* Ilustracije radi! */
74

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

if((n==0)||(n==1)) return 1; else return(Fib(n-1)+Fib(n-2)); /* Alternativni zapis: return (n == 0 || n == 1) ? 1 : Fib(n-1) + Fib(n-2); */ } /* Iterativna verzija bez niza */ int Fib_iterativno(int n) { /* Promenljiva pp cuva pretposlednji, a p poslednji element niza */ int pp = 1, p = 1; int i; for (i = 0; i <= n-2; i++) { int pom = pp; pp = p; p = p + pom; } return p; } main() { printf("Fib(5) = %d\n", Fib(5)); } /* Izlaz iz programa: Racunam Fib(5) Racunam Fib(4) Racunam Fib(3) Racunam Fib(2) Racunam Fib(1) Racunam Fib(0) Racunam Fib(1) Racunam Fib(2) Racunam Fib(1) Racunam Fib(0) Racunam Fib(3) Racunam Fib(2) Racunam Fib(1) Racunam Fib(0)

75

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

Racunam Fib(1) Fib(5) = 8

Kako se izvrsava program za vrednost 5: _______________________________________________________________ Fib(5)? => 8 = 5+3 / / / Fib(4)? => 5=3+2 \ \ \ Fib(3)? => 3=2+1 / \ Fib(2)? Fib(1)? => 2=1+1 | / \ 1 Fib(1) Fib(0) | | 1 1

/ \ Fib(3)? Fib(2)? => 3=2+1 => 2=1+1 / \ / \ Fib(2)? Fib(1) Fib(1) Fib(0) => 2=1+1 | | | / \ 1 1 1 Fib(1) Fib(0) | | 1 1 _______________________________________________________________ */ Primer 7.7 U slu caju da se rekurzijom problem svodi na vi se manjih podproblema koji se mogu preklapati, postoji opasnost da se pojedini podproblemi manjih dimenzija re savaju ve ci broj puta. Na primer, bonacci(20) = bonacci(19) + bonacci(18) bonacci(19) = bonacci(18) + bonacci(17) tj. problem bonacci(18) se resava dva puta. Problemi manjih dimenzija ce se re savati jo s ve ci broj puta. Re senje za ovaj problem je kombinacija rekurzije sa tzv. dinami ckim programiranjem podproblemi se re savaju samo jednom, a njihova re senja se pamte u memoriji (obi cno u nizovima ili matricama), odakle se koriste ako tokom re savanja ponovo budu potrebni. #include <stdio.h> #include <stdlib.h> #define MAX 50 /* Niz koji cuva resenja podproblema. */ int f[MAX];
76

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

/* Funkcija izracunava n-ti fibonacijev broj */ int fibonacci(int n) { /* Ako je podproblem vec resen, uzimamo gotovo resenje! */ if(f[n] != 0) return f[n]; /* Izlaz iz rekurzije */ if(n < 2) return f[n] = 1; else /* Rekurzivni pozivi */ return f[n] = fibonacci(n - 1) + fibonacci(n - 2); } /* Test program */ int main() { int n, i; /*Inicijalizuje se niz*/ for(i=0; i<MAX; i++) f[i] = 0; scanf("%d", &n); printf("%d\n", fibonacci(n)); return 0; } Primer 7.8 Program re sava problem tzv. hanojskih kula: data su tri vertikalna stapa, na jednom se nalazi n diskova polupre cnika 1,2,3,... do n, tako da se najve ci nalazi na dnu, a najmanji na vrhu. Ostala dva stapa su prazna. Potrebno je premestiti diskove na drugi stap tako da budu u istom redosledu, preme staju ci jedan po jedan disk, pri cemu se ni u jednom trenutku ne sme staviti ve ci disk preko manjeg.

77

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

/* | === ===== ======= ========= ----------X | | | | | ----------Y ===> ... | | | | | ----------Y ... ===> | === ===== ======= ========= ----------Y | | | | | ----------Z

| | | | ========= ----------X

| | === ===== ======= ----------Z

| | | | | ----------X */

| | | | | ----------Z

#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_TOWER 100 #define MAX_NAME 64 /* Stuktura definise jednu hanojsku kulu */ typedef struct { int s[MAX_TOWER]; /* Stap sa diskovima */ int n; /* Broj diskova na stapu */ char name[MAX_NAME]; /* Naziv kule */ } Tower; /* Funkcija postavlja naziv kule na dato ime, a zatim na stap postavlja

78

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

diskove velicine n, n-1, ..., 2, 1 redom */ void init_tower(Tower * tower, char * name, int n) { int i; strcpy(tower->name, name); tower->n = n; for(i = 0; i < n; i++) tower->s[i] = n - i; } /* Funkcija prikazuje sadrzaj na datoj kuli */ void print_tower(Tower * tower) { int i; printf("%s: ", tower->name); for(i = 0; i < tower->n; i++) printf("%d ", tower->s[i]); putchar(\n); } /* Funkcija premesta jedan disk sa vrha prve kule na vrh druge kule */ void move(Tower *from, Tower * to) { /* Proveravamo da li je potez ispravan */ if(from->n == 0 || (to->n > 0 && from->s[from->n - 1] >= to->s[to->n - 1])) { printf("Ilegal move: %d from %s to %s!!\n", from->s[from->n - 1], from->name, to->name ); exit(1); } else { /* Prikaz opisa poteza */ printf("Moving disc %d from %s to %s\n", from->s[from->n - 1], from->name, to->name); /* Premestanje diska */ to->s[to->n++] = from->s[--from->n];

79

Milena Vujo sevi cJani ci c

7.1 Osnovni primeri

} } /* Rekurzivna funkcija koja premesta n diska sa kule x na kulu y. Kao pomocna kula koristi se kula z. */ void hanoi(Tower *x, Tower *y, Tower * z, int n) { /* Izlaz iz rekurzije */ if(n == 0) return; /* Rekurzivno premestamo n - 1 disk sa x na z, pomocu kule y */ hanoi(x, z, y, n - 1); /* Premestamo jedan disk sa x na y */ move(x,y); /* Prikaz stanja kula nakon poteza */ print_tower(x); print_tower(y); print_tower(z); /* Premestamo n - 1 disk sa z na y, pomocu kule x */ hanoi(z, y, x, n - 1); }

/* Test program */ int main() { Tower x, y, z; int n; /* Ucitavamo dimenziju problema */ scanf("%d", &n); /* Inicijalizujemo kule. Kula x ima n diskova, ostale su prazne */ init_tower(&x, "X", n); init_tower(&y, "Y", 0); init_tower(&z, "Z", 0); /* Prikaz kula na pocetku */ print_tower(&x); print_tower(&y);

80

Milena Vujo sevi cJani ci c

7.2 Binarna pretraga

print_tower(&z); /* Poziv funkcije hanoi() */ hanoi(&x, &y, &z, n); return 0; }

7.2

Binarna pretraga

Primer 7.9 Rekurzivna varijanta algoritma binarne pretrage. #include <stdio.h> /* Funkcija proverava da li se element x javlja unutar niza celih brojeva a. Funkcija vraca poziciju na kojoj je element nadjen odnosno -1 ako ga nema. !!!!! VAZNO !!!!! Pretpostavka je da je niz a uredjen po velicini */ int binary_search(int a[], int l, int d, int x) { /* Ukoliko je interval prazan, elementa nema */ if (l > d) return -1; /* Srednja pozicija intervala [l, d] */ int s = (l+d)/2; /* Ispitujemo odnos x-a i srednjeg elementa */ if (x == a[s]) /* Element je pronadjen */ return s; else if (x < a[s]) /* Pretrazujemo interval [l, s-1] */ return binary_search(a, l, s-1, x); else /* Pretrazujemo interval [s+1, d] */ return binary_search(a, s+1, d, x); } main()
81

Milena Vujo sevi cJani ci c

7.3 MergeSort algoritam

{ int a[] = {3, 5, 7, 9, 11, 13, 15}; int x; int i; printf("Unesi element kojega trazimo : "); scanf("%d",&x); i = binary_search(a, 0, sizeof(a)/sizeof(int)-1, x); if (i==-1) printf("Elementa %d nema\n", x); else printf("Pronadjen na poziciji %d\n", i); }

7.3

MergeSort algoritam

Primer 7.10 Sortiranje niza celih brojeva u ce sljavanjem - MergeSort algoritam. #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX 100 /* sortiranje ucesljavanjem */ void merge_sort (int a[], int l, int d) { int s; int b[MAX]; /* pomocni niz */ int i, j, k; /* Izlaz iz rekurzije */ if (l >= d) return; /* Odredjujemo sredisnji indeks */ s = (l + d) / 2; /* rekurzivni pozivi */ merge_sort (a, l, s); merge_sort (a, s + 1, d); /* Inicijalizacija indeksa. Indeks i prolazi krozi levu polovinu niza, dok indeks j
82

Milena Vujo sevi cJani ci c

7.3 MergeSort algoritam

prolazi kroz desnu polovinu niza. Indeks k prolazi kroz pomocni niz b[] */ i = l; j = s + 1; k = 0; /* "ucesljavanje" koriscenjem pomocnog niza b[] */ while(i <= s && j <= d) { if(a[i] < a[j]) b[k++] = a[i++]; else b[k++] = a[j++]; } while(i <= s) b[k++] = a[i++]; while(j <= d) b[k++] = a[j++]; /* prepisujemo "ucesljani" niz u originalni niz */ for (k = 0, i = l; i <= d; i++, k++) a[i] = b[k]; } /* Test program */ int main(int argc, char ** argv) { int a[MAX]; int n, i; int x;

/* Niz brojeva se zadaje na komandnoj liniji */ for(n = 0; n + 1 < argc && n < MAX; n++) a[n] = atoi(argv[n + 1]); /* Poziv funkcije */ merge_sort(a, 0, n - 1); /* Prikaz niza */ for(i = 0; i < n; i++) printf("%d ", a[i]);

83

Milena Vujo sevi cJani ci c

7.4 QuickSort algoritam

putchar(\n); return 0; }

7.4

QuickSort algoritam

Primer 7.11 Sortiranje niza celih brojeva - QuickSort algoritam. /* Funkcija menja mesto i-tom i j-tom elementu niza a */ void swap(int a[], int i, int j) { int tmp = a[i]; a[i] = a[j]; a[j] = tmp; } /* Funkcija particionise interval [l, r] niza a, tako da levo od pivota budu svi elementi manji od njega, a desno svi veci od njega. Ovi podnizovi ne moraju biti sortirani. Funkcija vraca poziciju pivota. */ int partition(int a[], int l, int r) { int last, i; /* Srednji element uzimamo za pivot i postavljamo ga na pocetak */ swap(a, l, (l + r)/2); /* Niz organizujemo tako da pivot postavimo na pocetak, zatim da iza njega budu svi elementi manji od njega, pa zatim svi elementi koji su veci od njega tj. pivot < < < < > > > > */ /* last je pozicija poslednjeg elementa niza za koji znamo da je manji od pivota. U pocetku takvih elemenata nema */ last = l; for (i = l+1; i <= r; i++) /* Ukoliko je element na poziciji i manji od pivota, postavljamo ga iza niza elemenata manjih od pivota, menjajuci mu mesto sa prvim sledecim */ if (a[i] < a[l]) swap(a, ++last, i);

84

Milena Vujo sevi cJani ci c

7.4 QuickSort algoritam

/* Zahtevanu konfiguraciju < < < < pivot > > > > dobijamo tako sto zamenimo mesto pivotu i poslednjem elementu manjem od njega */ swap(a, l, last); /* Pivot se sada nalazi na poziciji last */ return last; } /* Funkcija sortira deo niza brojeva a izmedju pozicija l i r*/ void quick_sort(int a[], int l, int r) { int i, last; /* Ukoliko je interval [l, r] prazan nema nista da se radi */ if (l >= r) return; /* Particionisemo interval [l, r] */ int pivot = partition(a, l, r); /* Rekurzivno quick_sort(a, /* Rekurzivno quick_sort(a, } sortiramo elemente manje od pivota */ l, pivot-1); sortiramo elemente vece pivota */ pivot+1, r);

main() { int a[] = {5, 8, 2, 4, 1, 9, 3, 7, 6}; int n = sizeof(a)/sizeof(int); int i; quick_sort(a, 0, n-1); for (i = 0; i < n; i++) printf("%d ", a[i]); printf("\n"); }

85

Glava 8

Pokaziva ci na funkcije
Binarne instrukcije koje cine funkciju su takod e negde u memoriji, i imaju svoju adresu. Zato je mogu ce je denisati pokaziva c na funkciju. Na primer, deklaracija: int (*fp)(int); deni se promenljivu f, koja je tipa pokaziva c na funkciju koja prihvata argument tipa int, i vraca vrednost tipa int. Ova promenljiva sadr zi adresu neke funkcije tog tipa. Na primer, ako je deklarisana funkcija: int f(int a); Tada je moguce dodeliti: fp = &f; /* Operator adrese nije neophodan. */ Promenljiva fp sadr zi adresu funkcije f. Funkcija f se sada mo ze pozivati dereferenciranjem pokaziva ca fp: (*fp)(3); Zagrade oko izraza *fp su neophodne zbog prioriteta operatora. Dereferenciranjem pokaziva ca na funkciju dobija se funkcija, koja se onda poziva na uobi cajen na cin. Primer 8.1 Program demonstrira upotrebu pokaziva ca na funkcije. #include <stdio.h> int kvadrat(int n) { return n*n; } int kub(int n) { return n*n*n; }
86

Milena Vujo sevi cJani ci c

Pokaziva ci na funkcije

int parni_broj(int n) { return 2*n; } /* Funkcija izracunava sumu brojeva f(i) za i iz intervala [1, n]. int (*f) (int) u argumentu funkcije sumiraj je pokazivac na funkciju sa imenom f, koja kao argument prima promenljivu tipa int i vraca kao rezultat vrednost tipa int */ int sumiraj(int (*f) (int), int n) { int i, suma=0; for (i=1; i<=n; i++) suma += (*f)(i); return suma; } main() { /* U pozivu funkcije sumiraj, imena funkcija kvadrat, kub i parni_broj su zapravo istovremneo i adrese funkcija pa operator & nije neophodan ali nije greska ukoliko se se operator & ipak koristi ispred imena funkcije. */ printf("Suma kvadrata brojeva od jedan do 3 je %d\n", sumiraj(kvadrat,3)); printf("Suma kubova brojeva od jedan do 3 je %d\n", sumiraj(kub,3)); printf("Suma prvih pet parnih brojeva je %d\n", sumiraj(parni_broj,5)); /* Ili: printf("Suma kvadrata brojeva od jedan do 3 je %d\n", sumiraj(&kvadrat,3)); printf("Suma kubova brojeva od jedan do 3 je %d\n", sumiraj(&kub,3)); printf("Suma prvih pet parnih brojeva je %d\n", sumiraj(&parni_broj,5)); */ } /*Izlaz: Suma kvadrata brojeva od jedan do 3 je 14 Suma kubova brojeva od jedan do 3 je 36 Suma prvih pet parnih brojeva je 30 */ Primer 8.2 Program tabelarno ispisuje vrednosti raznih matemati ckih funkcija u
87

Milena Vujo sevi cJani ci c

Pokaziva ci na funkcije

ta ckama datog intervala sa datim korakom. #include <stdio.h> #include <math.h> /* Zaglavlje math.h sadrzi deklaracije razih matematickih funkcija. Izmedju ostalog, to su sledece funkcije: double sin(double x); double cos(double x); double tan(double x); double asin(double x); double acos(double x); double atan(double x); double atan2(double y, double x); double sinh(double x); double cosh(double x); double tanh(double x); double exp(double x); double log(double x); double log10(double x); double pow(double x, double y); double sqrt(double x); double ceil(double x); double floor(double x); double fabs(double x); */

/* Funkcija tabeliraj() prihvata granice intervala a i b, korak h, kao i pokazivac f koji pokazuje na funkciju koja prihvata double argument, i vraca double rezultat. Za tako datu funkciju ispisuje njene vrednosti u intervalu [a,b] sa korakom h */ void tabeliraj(double a, double b, double h, double (*f)(double)) { double x; printf("-----------------------\n"); for(x = a; x <= b; x+=h) printf("| %8.3f | %8.3f |\n", x, (*f)(x)); printf("-----------------------\n"); }

88

Milena Vujo sevi cJani ci c

8.1 Funkcija qsort iz standardne biblioteke

/* Funkcija main */ int main() { double a, b, h; /* Unosimo granice intervala */ printf("Uneti granice intervala: "); scanf("%lf%lf", &a, &b); /* Unosimo korak */ printf("Uneti korak: "); scanf("%lf", &h); /* Testiramo funkciju tabeliraj() za sin(), cos() i exp() */ printf("sin(x)\n"); tabeliraj(a, b, h, &sin); printf("cos(x)\n"); tabeliraj(a, b, h, &cos); printf("exp(x)\n"); tabeliraj(a, b, h, &exp); return 0; }

8.1

Funkcija qsort iz standardne biblioteke

Prototip funkcije qsort iz stdlib.h je: void qsort(void *niz, int duzina_niza, int velicina_elementa_niza, int (*poredi)(const void*, const void*) ) Ova funkcija sortira niz niz[0], niz[1], ..., niz[duzina_niza - 1] elemenata veli cine velicina_elementa_niza kori s cenjem funkcije poredi koja upored uje dva elementa niza. Po sto je void* tip niza niz to obezbed uje mogu cnost kori s cenja ove funkcije za nizove razli citih tipova podataka. Da bi funkcija qsort znala sa kakvim elementima barata, potrebno joj je proslediti i veli cinu jednog elementa u nizu (ovo je klju cno za korak u kome se vri si razmena elemenata niza funkcija qsort mora da zna koliko bajtova zauzima jedan element niza kako bi mogla adekvatno da vr si kopiranje memorije). Funkcija poredi vr si pored enje dva elementa niza. Kada ova funkcija vra ca pozitivnu vrednost ako je prvi element ve ci od drugog, 0 ako su jednaki i negativnu vrednost ako je prvi element manji, tada ce se niz sortirati u rastu cem poretku. Modikacijom ove funkcije niz se mo ze sortirati u opadaju cem poretku (ukoliko
89

Milena Vujo sevi cJani ci c

8.1 Funkcija qsort iz standardne biblioteke

vra ca pozitivnu vrednost ako je prvi manji, 0 ako su jednaki i negativnu vrednost ako je prvi veci). Kvalikator const Kljucna rec const u programskom jeziku C sluzi za denisanje konstanti. Ako napisemo: const int a = 2; tada se u daljem toku programa promenljivoj a ne moze dodeljivati vrednost. Za razliku od konstanti denisanih dene direktivom ova konstanta ima jasno denisan tip koji prevodilac proverava prilikom upotrebe. Treba naglasiti, medjutim, da ako se napi se: const int * p = &a; tada se const ne odnosi na promenljivu p, vec na podatak tipa int na koji p pokazuje. Dakle, nije pokaziva c konstantan, ve c on pokazuje na konstantan podatak. Na primer: int b; p = &b; je dozvoljeno, ali: *p = 4; nije dozvoljeno, zato sto je p pokaziva c na konstantan int (odnosno, kroz pokaziva c p nije mogu ce menjati vrednost koja se nalazi na adresi na koju on ukazuje). Ovo se moze razumeti tako sto se deklaracija pro cita sa desna u levo: const int * p; /* p je pokazivac na int koji je konstantan*/

Sli cno bi bilo i ako napi semo: int const * p; /* p je pokazivac na konstantan int. */ Med utim, ako napi semo: int * const p; /* p je konstantan pokazivac na int. */ Kombinacija ovoga je: const int * const p; /* p je konstantan pokazivac koji je konstantan.*/ na int

Dakle, u ovom poslednjem slu caju konstantni su i pokaziva c i ono na sta on pokazuje.

90

Milena Vujo sevi cJani ci c

8.1 Funkcija qsort iz standardne biblioteke

Upotreba qsort Primer 8.3 Upotrebom qsort funkcije iz standardne biblioteke izvr siti sortiranje niza celih i niza realnih brojeva. /* Ilustracija upotrebe funkcije qsort iz stdlib.h Sortira se niz celih brojeva. */ #include <stdlib.h> #include <stdio.h> /* const znaci da ono na sta pokazuje a (odnosno b) nece biti menjano u funkciji */ int poredi(const void* a, const void* b) { /* Skracen zapis za int br_a = *(int*)a; int br_b = *(int*)b; return br_a-br_b; */ return *((int*)a)-*((int*)b); } int poredi_float(const void* a, const void* b) { float br_a = *(float*)a; float br_b = *(float*)b; if (br_a > br_b) return 1; else if (br_a < br_b) return -1; else return 0; } main() { int i; int niz[]={3,8,7,1,2,3,5,6,9}; float nizf[]={3.0,8.7,7.8,1.9,2.1,3.3,6.6,9.9}; int n=sizeof(niz)/sizeof(int); qsort((void*)niz, n, sizeof(int), poredi); for(i=0; i<n; i++) printf("%d",niz[i]);

91

Milena Vujo sevi cJani ci c

8.1 Funkcija qsort iz standardne biblioteke

n=sizeof(nizf)/sizeof(float); qsort((void*)nizf, n, sizeof(float), poredi_float); for(i=0; i<n; i++) printf("%f",nizf[i]); } Primer 8.4 Sortiranje re ci. Ako se sortira niz stringova, onda svaki element je sam po sebi pokaziva c tipa char *, te funkcija pored enja tada prima podatke tipa char ** koji se konvertuju u svoj tip i derefenciraju radi dobijanja podataka tipa char *. #include <stdlib.h> #include <string.h> #include <stdio.h> /* Funkcija koja vrsi leksikografsko poredjenje dve reci. Vraca kao rezultat >0 ukoliko je prva rec veca, 0 ako su jednake i <0 ako je prva rec manja. Sortiranje ce biti u rastucem poretku.*/ int poredi_leksikografski(const void* a, const void* b) { char *s1 = *(char **)a; char *s2 = *(char **) b; return strcmp(s1, s2); /* Prethodno je ekvivalentno sa: return strcmp(*(char**)a,*(char**)b); */ } /* Funkcija koja vrsi poredjenje po duzini dve reci, sortiranje koje koristiovu funkciju ca biti opadajuce!*/ int poredi_po_duzini(const void* a, const void* b) { char *s1 = *(char **) a; char *s2 = *(char **) b; return strlen(s1) - strlen(s2); /* Prethodno je ekvivalentno sa: return strlen(*(char**)b)-strlen(*(char**)a); */ } main() { int i; char* nizreci[]={"Jabuka","Kruska","Sljiva","Dinja","Lubenica"}; qsort((void*)nizreci, sizeof(nizreci)/sizeof(char *), sizeof(char*), poredi_po_duzini);
92

Milena Vujo sevi cJani ci c

8.2 Funkcija bsearch iz standardne biblioteke

for (i=0; i<5; i++) printf("%s\n",nizreci[i]); qsort((void*)nizreci, sizeof(nizreci), sizeof(char*), poredi_leksikografski); printf("Sortirano:\n"); for (i=0; i<sizeof(nizreci); i++) printf("%s\n",nizreci[i]); } /* Izlaz: Dinja Jabuka Kruska Lubenica Sljiva Sortirano: Lubenica Kruska Jabuka Sljiva Dinja */

8.2

Funkcija bsearch iz standardne biblioteke

Prototip funkcije bsearch iz stdlib.h je: void *bsearch (const void *kljuc, const void *niz, int duzina_niza, int velicina_elementa_niza, int (*poredi)(const void*, const void*) ) Ova funkcija u nizu niz[0], niz[1], ..., niz[duzina_niza - 1] elemenata veli cine velicina_elementa_niza tra zi element koji ima krakteristiku na koju ukazuje pokaziva c kljuc. Funkcija poredi mo ze da se razlikuje od funkcije poredi koju prima funkcija qsort. Njen prvi argument je klju c pretrage, a drugi argument je element niza. To zna ci da ova dva argumenta mogu ali i ne moraju da budu istog tipa. Na primer, ako u nizu studenata tra zimo onog studenta koji ima indeks mm08123 tada je klju c tipa char* a element niza je tipa strukture student, zna ci klju c pretrage i element niza su razli citog tipa, pa u skladu sa tim funkcija pored enja prima dva argumenta razli citog tipa. S druge strane, ako u nizu studenata tra zimo onog studenta koji ima indeks mm08123, i ime Pera i prezime Peric, tada je klju c istog tipa kao i element niza, i tada funkcija pored enja prima argumente istog tipa (i analogna je funkciji pored enja koja se pi se za funkciju qsort). Dakle, pored enje se vr si po klju cu koji mo ze biti istog tipa kao i elementi niza ali i ne mora. Funkcija poredi treba da vrati vrednost 0 ako je prvi argument (klju c pretrage) ve ci (u smislu klju ca pretrage) od drugog argumenta (koji je element niza), 0 ako su jednaki (u smislu klju ca pretrage) i broj manji od 0 ako je prvi argument manji (u smislu klju ca pretrage) od drugog argumenta, ukoliko su elementi u nizu niz ured eni
93

Milena Vujo sevi cJani ci c

8.2 Funkcija bsearch iz standardne biblioteke

u rastu cem redosledu u odnosu na dati klju c. Funkcija bsearch vra ca pokaziva c na pronad eni element ili NULL ukoliko element sa datom vredno s cu klju ca ne postoji. Primer 8.5 Binarno pretra zivanje - kori s cenje ugrad ene bsearch funkcije. /* Funkcija ilustruje koriscenje ugradjene funkcije bsearch */ #include <stdlib.h> int poredi(const void* a, const void *b) { return *(int*)a-*(int*)b; } main() { int x=-1; int niz[]={1,2,3,4,5,6,7,8,9,10,11,12}; int* elem=(int*)bsearch((void*)&x, (void*)niz, sizeof(niz)/sizeof(int), sizeof(int), poredi); if (elem==NULL) printf("Element nije pronadjen\n"); else printf("Element postoji na poziciji %d\n",elem-niz); } Primer 8.6 Sa ulaza se unose re ci. Program broji pojavljivanja svake od klju cnih re ci programskog jezika C. Na kraju se re ci ispisuju opadaju ce po broju pojavljivanja. #include <stdio.h> #include <stdlib.h> /* Svaka kljucna rec se odlikuje imenom i brojem pojavljivanja */ typedef struct _keyword { char word[20]; int num; } keyword; /* Kreiramo niz struktura sortiranih leksikografski po imenu kljucne reci, kako bismo ubrzali pronalazak reci */
94

Milena Vujo sevi cJani ci c

8.2 Funkcija bsearch iz standardne biblioteke

keyword keywords[]={ {"break",0}, {"continue",0}, {"float",0}, {"for",0}, {"if",0}, {"return",0}, {"struct",0}, {"while",0} }; /* Funkcija cita sledecu rec sa standardnog ulaza */ int getword(char word[], int lim) { int c, i=0; while(!isalpha(c=getchar()) && c!=EOF) ; if (c==EOF) return -1; do { word[i++]=c; } while(--lim>0 && isalpha(c=getchar())); word[i]=\0; return i; } /* Funkcija leksikografskog poredjenja za bsearch */ int cmp(const void* a, const void* b) { /* Funkcija strcmp prima kao argumente dva pokazivaca na karaktere. U ovom slucaju, prvi argument je rec koju trazimo u nizu --- prilikom poziva funkcije bsearch funkciji se kao prvi argument prosledjuje pokazivac na kljuc pretrage - to znaci da ukoliko se funkciji prosledi adresa podatka tipa char* tada bi bilo potrebno uraditi dereferenciranje sa *(char **)a; medjutim, ukoliko se prosledi samo promenljiva tipa char* tada je potrebno uraditi dereferenciranje sa (char *)a. Drugi argument funkcije je element niza sa kojim se vrsi poredjenje. Pokazivac b konvertujemo u pokazivac na strukturu keyword a zatim posmatramo rec koja se tu cuva */ return strcmp((char*)a, (*(keyword*)b).word); }

95

Milena Vujo sevi cJani ci c

8.2 Funkcija bsearch iz standardne biblioteke

/* Funkcija numerickog poredjenja za qsort */ int numcmp(const void* a, const void* b) { return ((*(keyword*)b).num-(*(keyword*)a).num); } main() { char word[80]; int i; /* Broj kljucnih reci */ int num_of_keywords=sizeof(keywords)/sizeof(keyword); /* Citamo reci */ while (getword(word,80)!=-1) { /* Trazimo rec u spisku kljucnih reci binarnom pretragom */ /* Prvi argument funkcije bsearch treba da bude pokazivac na rec koju trazimo. Posto je word tipa niza karaktera, tada uzimanjem adrese od word dobijamo istu vrednost kao i word, tj nema razlike izmedju word i &word. Zbog toga se u funkciji poredjenja koristi dreferenciranje na sledeci nacin strcmp((char*)a, (*(keyword*)b).word); } Da je word tipa char*, tada bi poziv funkcije bsearch sa argumentom &word povlacilo da funkcija poredjenja koristi dereferenciranje na sledeci nacin strcmp(*(char**)a, (*(keyword*)b).word); } */ keyword* k=(keyword*)bsearch((void*)word, (void*)keywords, num_of_keywords, sizeof(keyword), cmp); /* Ukoliko je pronadjena uvecavamo broj pojavljivanja */ if (k!=NULL) (*k).num++; } /* Sortiramo niz na osnovu broja pojavljivanja */ qsort((void*)keywords, num_of_keywords, sizeof(keyword), numcmp); /* Vrsimo ispis */ for (i=0; i<num_of_keywords; i++)

96

Milena Vujo sevi cJani ci c

8.3 Funkcije lsearch i lfind

printf("%s %d\n", keywords[i].word, keywords[i].num); }

8.3

Funkcije lsearch i lfind

Funkcije za linearnu pretragu standardne biblioteke su funkcije lsearch i lfind. Funkcija lsearch ima slede ce argumente void *lsearch(const void *kljuc, void *niz, int *duzina_niza, int velicina_elementa_niza, int (*comp)(const void *, const void *)); Ova funkcija ima iste argumente kao i funkcija bsearch() jedina razlika je u tome sto se kao tre ci argument ne predaje du zina niza ve c adresa celobrojne promenljive u kojoj se nalazi du zina niza. Ovo je zato sto funkcija lsearch() ukoliko linearnom pretragom ne pronad e element koji se tra zi, ume ce tra zeni element na kraj niza, a du zinu niza uve cava za jedan (sto cini tako sto promenljivoj pozivaju ce funkcije pristupa preko pokazivaca i menja je). Takod e, po sto postiji mogu cnost umetanja elementa u niz, kao klju c pretrage potrebno je koristiti klju c koji je istog tipa kao sto su to elementi niza. Funkcija za uporedjivanja elemenata na koju pokazuje pokazivac comp treba da zadovoljava ista pravila kao i kod funkcije bsearch. Medjutim, s obzirom da se kod linearne pretrage koristi isklju civo pored enje na jednakost, dovoljno je da funkcija za uporedjivanje vraca 0 ako su objekti koji se uporedjuju jednaki, a razlicito od nule u suprotnom. Funkcija lfind: void *lfind(const void *kljuc, void *niz, int *duzina_niza, int velicina_elementa_niza, int (*comp)(const void *, const void *)); funkcioni se isto kao i lsearch, s tom razlikom sto ne ume ce novi element u slu caju neuspe sne pretrage, ve c vra ca NULL (funkcija lsearch u slu caju neuspe sne pretrage ume ce tra zeni element na kraj i vra ca njegovu adresu). Za funkciju lfind mogu ce je koristiti klju c pretrage koji nije istog tipa kao elementi niza, u tom slu caju potrebno je adekvatno denisati funkciju pored enja.

8.4

Pokaziva ci
pokazivac na int pokazivac na pokazivac na tip int niz od 10 elemenata tipa pokazivaca na int niz od 10 elemenata tipa pokazivaca na int

Primer 8.7 Razli cite deklaracije: int *p; int **p; int* p[10]; int *p[10];

97

Milena Vujo sevi cJani ci c

8.4 Pokaziva ci

int p[5][10]; int (*p)[10]; int* p(); int *p(); int (*p)(); int *(*p)(); int* (*p)();

pet nizova od po 10 elemenata tipa int pokazivac na niz od deset celobrojnih vrednosti funkcija bez argumenata koja vraca pokazivac na int funkcija bez argumenata koja vraca pokazivac na int pokazivac na funkciju bez argumenata koja vraca int pokazivac na funkciju bez argumenata koja vraca pokazivac na int pokazivac na funkciju bez argumenata koja vraca pokazivac na int

Razlika izmed u int** t i int (*t)[5] int x; int (*t)[5]; int aa[4][5]; t = aa; x=t[2][2]; /*Da je t tipa int** ova dodela ne bi bila moguca!*/

98

Glava 9

Dinami cka alokacija memorije


Do sada smo memoriju za podatke rezervisali na dva na cina1 : Stati cka alokacija podaci se kreiraju stati cki prilikom pokretanja programa i ostaju u memoriji do kraja njegovog izvr savanja. Ovakvu alokaciju imaju globalne promenljive (promenljive denisane van svake funkcije ili bloka) kao i stati cke lokalne promenljive (deklarisane klju cnom re cju static unutar funkcije ili bloka). Automatska alokacija podaci se kreiraju automatski prilikom ulaska u funkciju ili blok, i nestaju kada se iz funkcije/bloka izad e. Ovakvo pona sanje imaju sve lokalne promenljive koje nisu deklarisane kao static. Prostor za ove promenljive se rezervi se na programskom steku. Glavni nedostatak oba ova pristupa je to sto programer u vreme pisanja programa mora da predvidi koliko ce mu prostora biti potrebno za podatke. Cesto tako ne sto nije mogu ce, jer dimenzije problema mogu varirati. Drugi nedostatak je to sto je zivotni vek svakog podatka jasno denisan gornjim pravilima. Programer mo ze zeleti da nakon kreiranja nekog objekta u memoriji on ostane tu dokle god je potreban, te da ga kasnije ukloni kada vi se za njim nema potrebe. Svi ovi problemi re savaju se dinami ckom alokacijom memorije. Pored stati cke zone i steka, u memoriji postoji i odred eni prostor koji se naziva hip (eng. heap). U ovom prostoru se po potrebi mogu kreirati objekti proizvoljne veli cine. Taj prostor se kasnije mo ze (i treba) osloboditi, kada programer vi se nema potrebu da koristi taj prostor. U programskom jeziku C, ovo se mo ze ostvariti pozivom funkcije standardne biblioteke malloc() koja je deklarisana u zaglavlju stdlib.h: void * malloc(int n); Ova funkcija alocira na hipu kontinualni prostor od n bajtova, i vra ca adresu po cetka tog prostora ili vra ca vrednost NULL ukoliko zahtev ne mo ze da se ispuni. Jedini na cin da se ovom prostoru pristupi je preko pokaziva ca (jer ne postoji ime promenljive kao kod stati ckih i automatskih promenljivih). Osim funkcije malloc() postoje i druge funkcije za alokaciju, na primer:
1 Tekst

preuzet sa sajta www.matf.bg.ac.rs/~milan

99

Milena Vujo sevi cJani ci c

Dinami cka alokacija memorije

void * calloc(int n, int s); Ova funkcija alocira prostor za n susednih elemenata veli cine s bajtova (zapravo alocira n*s bajtova). Ova funkcija jo s i inicijalizuje rezervisani prostor na vrednost 0, za razliku od malloc() funkcije koja prostor ostavlja neinicijalizovan. Funkcija void * realloc(void *p, int n); vr si realokaciju prostora na koji pokazuje pokaziva c p za koji je memorija prethodno bila dinami cki alocirana. Alocira se n bajtova, kopira se stari sadr zaj u novi prostor, a zatim se stari prostor oslobad a. Ova funkcija se obi cno koristi da bi se pro sirio postoje ci dinami cki alocirani prostor. Prostor alociran prethodnim funkcijama se mo ze dealocirati pozivom funkcije: void free(void *p); koja je takod e deklarisana u zaglavlju stdlib.h, i koja za argument mora imati adresu koju je funkcija (m|c|re)alloc() vratila u nekom od prethodnih poziva (drugim re cima, ne mo ze se proizvoljan prostor oslobad ati ovom funkcijom, ve c samo prostor na hipu alociran (m|c|re)alloc()-om). Kada se jednom dealocira, prostor vi se ne sme da se koristi (vrlo je verovatno da ce ga kasnije funkcija koja vr si alokaciju dodeliti nekom drugom). Trenutak oslobad anja prostora na hipu odred uje programer. Nebitno je da li smo iza sli iz funkcije ili bloka u kome je funkcija za alokaciju memorije pozvana, prostor ostaje rezervisan dokle god ne pozovemo free() sa argumentom adrese tog prostora. Ova memorija se mo ze koristiti i u drugim funkcijama sve dok imamo pokaziva c kojim mo zemo da joj pristupimo. Tiha greska je ako izgubimo pokaziva c na dinami cki alocirani prostor, a da ga nismo prethodno dealocirali. U ovom slu caju taj prostor ostaje rezervisan do kraja izvr savanja programa, bez mogu cnosti da ga koristimo ili da ga kasnije obri semo (jer nemamo njegovu adresu). Ovaj fenomen se naziva curenje memorije (eng. memory leak). Odgovornost je programera kao i odlika dobrog stila da se dinami cki prostor uvek oslobodi kada vi se nije potreban. Takod e, gre ska je i ukoliko programer poku sa dva puta da oslobodi istu memoriju ili da pristupi memoriji koja je ve c oslobod ena (ovo dovodi do gre ske prilikom izvr savanja programa). Primer 9.1 Dinami cka alokacija memorije za niz. #include <stdio.h> #include <stdlib.h> main() { int n; int i; int *a; /* Ranije smo alocirali konstantan prostor za niz,
100

Milena Vujo sevi cJani ci c

Dinami cka alokacija memorije

sada cemo alocirati tacno onoliko koliko nam je potrebno. */ printf("Unesi broj clanova niza : "); scanf("%d", &n); /* Kao da smo mogli da deklarisemo int a[n]; Alocira se n mesta za podatke tipa int. Funkcija malloc vraca vrednost tipa void* tako da vrsimo kastovanje. */ a = (int*)malloc(n*sizeof(int)); /* Kad god se vrsi alokacija memorije mora se proveriti da li je alokacija uspesno izvrsena!!! */ if (a == NULL) { printf("Nema slobodne memorije\n"); exit(1); } /* Od ovog trenutka a koristimo kao obican niz */ for (i = 0; i<n; i++) scanf("%d",&a[i]); /* Stampamo niz u obrnutom redosledu */ for(i = n-1; i>=0; i--) printf("%d",a[i]); /* Oslobadjamo memoriju*/ free(a); } Primer 9.2 Demonstracija funkcije calloc - funkcija inicijalizuje sadrzaj memorije na 0. #include <stdio.h> #include <stdlib.h> main() { int *m, *c, i, n; printf("Unesi broj clanova niza : "); scanf("%d", &n);

101

Milena Vujo sevi cJani ci c

Dinami cka alokacija memorije

/* Niz m NE MORA garantovano da ima sve nule */ m = (int*) malloc(n*sizeof(int)); if (m == NULL) { printf("Greska prilikom alokacije memorije!\n"); exit(1); } /* Niz c MORA garantovano da ima sve nule */ c = (int*) calloc(n, sizeof(int)); if (c == NULL) { printf("Greska prilikom alokacije memorije!\n"); free(m); exit(1); } /*Odstampace se sadrzaj neinicijalizovane memorije*/ for (i = 0; i<n; i++) printf("m[%d] = %d\n", i, m[i]); /*Odstampace se nule*/ for (i = 0; i<n; i++) printf("c[%d] = %d\n", i, c[i]); /* Oslobadja se rezervisana memorija. */ free(m); free(c); } Primer 9.3 Da bi funkcija omogu cila pozivaju coj funkciji da koristi vrednosti niza koji je kreirala, neophodno je da memorija kreiranog niza bude dinami cki alocirana kako bi nastavila da postoji i nakon zavr setka rada funkcije. U tom slu caju, pozivaju ca funkcija ima odgovornost i da se brine o dealokaciji rezervisanog prostora. #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX 1000 /* Funkcija dinamicki kreira niz karaktera u koji smesta rezultat nadovezivanja stringova s i t. Adresa niza se vraca kao povratna vrednost. */ char * nadovezi_stringove(char *s, char *t) { /* Dinamicki kreiramo prostor dovoljne velicine */ char *p = (char*) malloc((strlen(s) + strlen(t) + 1) * sizeof(char));
102

Milena Vujo sevi cJani ci c

Dinami cka alokacija memorije

/* Proveravamo uspeh alokacije */ if(p == NULL) { fprintf(stderr, "malloc() greska!\n"); exit(1); } /* Kopiramo i nadovezujemo stringove */ strcpy(p,s); strcat(p,t); /* Vracamo p */ return p; } int main() { char * p = NULL; char s[MAX], t[MAX]; /* Ucitavamo stringove */ scanf("%s", s); scanf("%s", t); /* Nadovezujemo stringove */ p = nadovezi_stringove(s,t); /* Prikazujemo rezultat */ printf("%s\n", p); /* Oslobadjamo memoriju*/ free(p); return 0; } Primer 9.4 Niz pokaziva ca na nizove razli citih du zina. #include <stdio.h> #include <stdlib.h> main() { /* Niz od tri elemenata tipa int*/ int nizi[3];

103

Milena Vujo sevi cJani ci c

Dinami cka alokacija memorije

/* Niz od tri elemenata tipa int*, dakle niz od tri pokazivaca na int*/ int* nizip[3]; /* Alociramo memoriju za prvi element niza*/ nizip[0] = (int*) malloc(sizeof(int)); if (nizip[0] == NULL) { printf("Nema slobodne memorije\n"); exit(1); } /* Upisujemo u prvi element niza broj 5*/ *nizip[0] = 5; printf("%d", *nizip[0]); /* Alociramo memoriju za drugi element niza. Drugi element niza pokazuje na niz od dva elementa*/ nizip[1] = (int*) malloc(2*sizeof(int)); if (nizip[1] == NULL) { printf("Nema slobodne memorije\n"); free(nizip[0]); exit(1); } /* Pristupamo prvom elementu na koji pokazuje pokazivac nizip[1]*/ *(nizip[1]) = 1; /* Pristupamo sledecem elementu u nizu na koji pokazuje nizip[1]. */ *(nizip[1] + 1) = 2; printf("%d", nizip[1][1]); /* Alociramo memoriju za treci element niza nizip. */ nizip[2] = (int*) malloc(sizeof(int)); if (nizip[2] == NULL) { printf("Nema slobodne memorije\n"); free(nizip[0]); free(nizip[1]); exit(1); }

104

Milena Vujo sevi cJani ci c

9.1 Matrice

*(nizip[2]) = 2; printf("%d", *(nizip[2])); /* Oslobadjamo memoriju. */ free(nizip[0]); free(nizip[1]); free(nizip[2]); }

9.1

Matrice

Primer 9.5 Stati cka alokacija prostora za matricu. #include <stdio.h> main() { int i, j; /* Deklaracija i inicijalizacija elemenata matrice */ int a[3][3] = {{0, 1, 2}, {10, 11, 12}, {20, 21, 22}}; /* Alternativni unos elemenata matrice for(i=0; i<3; i++) for(j=0; j<3; j++) { printf("a[%d][%d] = ", i, j); scanf("%d", &a[i][j]); } */ /*Pristup elementima matrice */ a[1][1] = a[0][0] + a[2][2]; /* a[1][1] = 0 + 22 = 22 */ printf("%d\n", a[1][1]); /* 22 */

/* Stampanje elemenata matrice*/ for(i=0; i<3; i++) { for(j=0; j<3; j++) printf("%d\t", a[i][j]); printf("\n");
105

Milena Vujo sevi cJani ci c

9.1 Matrice

} } Ukoliko je potrebna ve ca eksibilnost, tj. da se dimenzije matrice mogu uneti kao parametri programa, tada je neophodno koristiti dinami cku alokaciju memorije. Primer 9.6 Implementacija matrice preko niza. #include <stdlib.h> #include <stdio.h> /* Makro pristupa clanu na poziciji i, j matrice koja ima m vrsta i n kolona */ #define a(i,j) a[(i)*n+(j)] main() { /* Dimenzije matrice */ int m, n; /* Matrica */ int *a; int i,j; /* Suma elemenata matrice */ int s=0; /* Unos i alokacija */ printf("Unesi broj vrsta matrice : "); scanf("%d",&m); printf("Unesi broj kolona matrice : "); scanf("%d",&n); /*Rezervise se prostor za niz a*/ a=(int*)malloc(m*n*sizeof(int)); if (a == NULL) { printf("Greska prilikom alokacije memorije!\n"); exit(1); } for (i=0; i<m; i++) for (j=0; j<n; j++) { printf("Unesi element na poziciji (%d,%d) : ",i,j);
106

Milena Vujo sevi cJani ci c

9.1 Matrice

scanf("%d",&a(i,j)); } /* Racunamo sumu elemenata matrice */ for (i=0; i<m; i++) for (j=0; j<n; j++) s+=a(i,j); /* Ispis unete matrice */ printf("Uneli ste matricu : \n"); for (i=0; i<m; i++) { for (j=0; j<n; j++) printf("%d ",a(i,j)); printf("\n"); } printf("Suma elemenata matrice je %d\n", s); /* Oslobadjamo memoriju */ free(a); } Primer 9.7 Druga opcija za implementaciju matrice je korist ci pokaziva c na pokaziva c. 1. Deni semo pokaziva c na pokaziva c na int: int **p; Ovaj pokaziva c ce cuvati adresu prvog u nizu dinami cki alociranih pokaziva ca na int-ove. 2. Dinami cki alociramo niz od m pokaziva ca na int-ove, i adresu prvog u nizu sme stamo u promenljivu p: p = (int**) malloc(sizeof(int*) * m)); Ovi pokaziva ci slu ze da pokazuju na prve elemente nizova int-ova koji predstavljaju dinami cki alocirane vrste. 3. Svaku vrstu alociramo posebnim malloc-om, i povratnu adresu sme stamo u i-ti pokaziva c u malopre alociranom nizu pokaziva ca. for(i = 0; i < m; i++) p[i] = malloc(sizeof(int) * n); Primetimo da nizovi int-ova koji se alociraju nisu morali biti jednakih dimenzija (ne moraju svi biti duzine n, kao sto je ovde re ceno). Ovo mo ze da bude korisno, ako znamo da ce u nekoj vrsti biti kori s ceno samo nekoliko prvih elemenata, tada mo zemo alocirati manje za tu vrstu, i tako u stedeti prostor.
107

Milena Vujo sevi cJani ci c

9.1 Matrice

4. U toku rada se elementima ovako alocirane matrice pristupa sa p[i][j] p[i] je i-ti pokaziva c u nizu, a kako on pokazuje na po cetni element u i-toj vrsti, tada je p[i][j] upravo j-ti element u i-toj vrsti. 5. Dealokacija se sastoji u brisanju svih vrsta, nakon cega se obri se i niz pokaziva ca (dakle, dealokacija ide u suprotnom redosledu od redosleda alokacije): for(i = 0; i < m; i++) free(p[i]); free(p); #include <stdio.h> #include <stdlib.h> int main() { int **p; int m, n; int i, j; printf("Uneti broj vrsta matrice: "); scanf("%d", &m); printf("Unesite broj kolona matrice: "); scanf("%d", &n); /* Alociramo m pokazivaca na vrste matrice */ if((p = (int**)malloc(sizeof(int*) * m)) == NULL) { fprintf(stderr, "malloc() greska\n"); exit(1); } /* Alociramo m vrsta od po n int-ova */ for(i = 0; i < m; i++) if((p[i] = (int*)malloc(sizeof(int) * n)) == NULL) { fprintf(stderr, "malloc() greska\n"); exit(1); } /* Postavljamo p[i][j] na vrednost abs(i - j) */ for(i = 0; i < m ; i++) for(j = 0; j < n; j++) p[i][j] = i - j >=0 ? i - j : j - i;

108

Milena Vujo sevi cJani ci c

9.1 Matrice

/* Ispis matrice */ for(i = 0; i < m ; i++) { for(j = 0; j < n; j++) printf("%3d ", p[i][j]); putchar(\n); } /* Oslobadjanje vrsta matrice */ for(i = 0; i < m; i++) free(p[i]); /* Oslobadjanje niza pokazivaca */ free(p); return 0; } Primer 9.8 Program ilustruje rad sa kvadratnim matricama i relacijama. K od za alokaciju i dealokaciju memorije je izdvojen u funkcije. Element i je u relaciji sa elementom j ako je m[i][j] = 1, a nisu u relaciji ako je m[i][j] = 0. #include <stdlib.h> #include <stdio.h> /* Dinamicka matrica je odredjena adresom pocetka niza pokazivaca i dimenzijama tj. int** a; int m,n; */ /* Alokacija kvadratne matrice nxn */ int** alociraj(int n) { int** m; int i; m = (int**)malloc(n*sizeof(int*)); if (m == NULL) { printf("Greska prilikom alokacije memorije!\n"); exit(1); } for (i=0; i<n; i++) { m[i]=(int*)malloc(n*sizeof(int)); if (m[i] == NULL)
109

Milena Vujo sevi cJani ci c

9.1 Matrice

{ int k; printf("Greska prilikom alokacije memorije!\n"); for(k=0;k<i;k++) free(m[k]); free(m); exit(1); } } return m; } /* Dealokacija matrice dimenzije nxn */ void obrisi(int** m, int n) { int i; for (i=0; i<n; i++) free(m[i]); free(m); } /* Ispis matrice / void ispisi_matricu(int** m, int n) { int i, j; for (i=0; i<n; i++) { for (j=0; j<n; j++) printf("%d ",m[i][j]); printf("\n"); } } /* Provera da li je relacija predstavljena matricom refleksivna */ int refleksivna(int** m, int n) { int i; for (i=0; i<n; i++) if (m[i][i]==0) return 0; return 1; }

110

Milena Vujo sevi cJani ci c

9.1 Matrice

/* Provera da li je relacija predstavljena matricom simetricna */ int simetricna(int** m, int n) { int i,j; for (i=0; i<n; i++) for (j=i+1; j<n; j++) if (m[i][j]!=m[j][i]) return 0; return 1; } /* Provera da li je relacija predstavljena matricom tranzitivna*/ int tranzitivna(int** m, int n) { int i,j,k; for (i=0; i<n; i++) for (j=0; j<n; j++) for (k=0; k<n; k++) if ((m[i][j]==1) && (m[j][k]==1) && (m[i][k]!=1)) return 0; return 1; } /* Pronalazi najmanju simetricnu relaciju koja sadrzi relaciju a */ void simetricno_zatvorenje(int** a, int n) { int i,j; for (i=0; i<n; i++) for (j=0; j<n; j++) { if (a[i][j]==1 && a[j][i]==0) a[j][i]=1; if (a[i][j]==0 && a[j][i]==1) a[i][j]=1; } } main() { int **m;

111

Milena Vujo sevi cJani ci c

9.1 Matrice

int n; int i,j; printf("Unesi dimenziju matrice : "); scanf("%d",&n); m=alociraj(n); for (i=0; i<n; i++) for (j=0; j<n; j++) scanf("%d",&m[i][j]); printf("Uneli ste matricu : \n"); ispisi_matricu(m,n); if (refleksivna(m,n)) printf("Relacija je refleksivna\n"); if (simetricna(m,n)) printf("Relacija je simetricna\n"); if (tranzitivna(m,n)) printf("Relacija je tranzitivna\n"); simetricno_zatvorenje(m,n); ispisi_matricu(m,n); obrisi(m,n); } Primer 9.9 Izra cunati vrednost determinante matrice preko Laplasovog razvoja. #include <stdio.h> #include <stdlib.h> /* Funkcija alocira matricu dimenzije nxn */ int** allocate(int n) { int **m; int i; m=(int**)malloc(n*sizeof(int*)); if (m == NULL) { printf("Greska prilikom alokacije memorije!\n"); exit(1); } for (i=0; i<n; i++)
112

Milena Vujo sevi cJani ci c

9.1 Matrice

{ m[i]=malloc(n*sizeof(int)); if (m[i] == NULL) { int k; for(k=0;k<i;k++) free(m[k]); printf("Greska prilikom alokacije memorije!\n"); free(m); exit(1); } } return m; } /* Funkcija vrsi dealociranje date matrice dimenzije n */ void deallocate(int** m, int n) { int i; for (i=0; i<n; i++) free(m[i]); free(m); } /* Funkcija ucitava datu alociranu matricu sa standardnog ulaza */ void ucitaj_matricu(int** matrica, int n) { int i,j; for (i=0; i<n; i++) for (j=0; j<n; j++) scanf("%d",&matrica[i][j]); } /* Rekurzivna funkcija koja vrsi Laplasov razvoj */ int determinanta(int** matrica, int n) { int i; int** podmatrica; int det=0,znak; /* Izlaz iz rekurzije je matrica 1x1 */ if (n==1) return matrica[0][0];

113

Milena Vujo sevi cJani ci c

9.2 Realokacija memorije

/* Podmatrica ce da sadrzi minore polazne matrice */ podmatrica=allocate(n-1); znak=1; for (i=0; i<n; i++) { int vrsta,kolona; for (kolona=0; kolona<i; kolona++) for(vrsta=1; vrsta<n; vrsta++) podmatrica[vrsta-1][kolona] = matrica[vrsta][kolona]; for (kolona=i+1; kolona<n; kolona++) for(vrsta=1; vrsta<n; vrsta++) podmatrica[vrsta-1][kolona-1] = matrica[vrsta][kolona]; det+= znak*matrica[0][i]*determinanta(podmatrica,n-1); znak*=-1; } deallocate(podmatrica,n-1); return det; } main() { int **matrica; int n; scanf("%d", &n); matrica = allocate(n); ucitaj_matricu(matrica, n); printf("Determinanta je : %d\n",determinanta(matrica,n)); deallocate(matrica, n); }

9.2

Realokacija memorije

Primer 9.10 Dinami cki niz. /* Program za svaku rec unetu sa standardnog ulaza ispisuje broj pojavljivanja. Verzija sa dinamickim nizom i realokacijom. */ #include #include #include #include <ctype.h> <stdio.h> <stdlib.h> <string.h>
114

Milena Vujo sevi cJani ci c

9.2 Realokacija memorije

/* Rec je opisana imenom i brojem pojavljivanja */ typedef struct _rec { char ime[80]; int br_pojavljivanja; } rec; /* Realokacija se vrsi sa datim korakom */ #define KORAK 10 /* Funkcija ucitava rec i vraca njenu duzinu ili -1 ukoliko smo dosli do znaka EOF*/ int getword(char word[],int max) { int c, i=0; while (isspace(c=getchar())) ; while(!isspace(c) && c!=EOF && i<max-1) { word[i++]=c; c = getchar(); } word[i]=\0; if (c==EOF) return -1; else return i; } main() { /* Dinamicki niz reci je opisan pokazivacem na pocetak, tekucim brojem upisanih elemenata i tekucim brojem alociranih elemenata */ rec* niz_reci; int duzina=0; int alocirano=0; char procitana_rec[80]; int i;

115

Milena Vujo sevi cJani ci c

9.2 Realokacija memorije

while (getword(procitana_rec,80)!=-1) { /* Proveravamo da li rec vec postoji u nizu */ for (i=0; i<duzina; i++) /* Ako bismo uporedili procitana_rec == niz_reci[i].ime bili bi uporedjeni pokazivaci a ne odgovarajuci sadrzaji. Zato koristimo strcmp. */ if (strcmp(procitana_rec, niz_reci[i].ime)==0) { niz_reci[i].br_pojavljivanja++; break; } /* Ukoliko rec ne postoji u nizu */ if (i==duzina) { rec nova_rec; /* Ako bismo dodelili nova_rec.ime = procitana_rec izvrsila bi se dodela pokazivaca a ne kopiranje niske procitana_rec u nova_rec.ime. Zato koristimo strcpy. */ strcpy(nova_rec.ime,procitana_rec); nova_rec.br_pojavljivanja=1; /* Ukoliko je niz "kompletno popunjen" vrsimo realokaciju */ if (duzina==alocirano) { alocirano+=KORAK; /* Sledeca linija zamenjuje blok koji sledi i moze se koristiti alternativno. Blok je ostavljen samo da bi demonstrirao korisnu tehniku */ /* niz_reci=realloc(niz_reci, (alocirano)*sizeof(rec)); */ { /* alociramo novi niz, veci nego sto je bio prethodni */ rec* novi_niz=(rec *)malloc(alocirano*sizeof(rec)); if (novi_niz == NULL) { free(niz_reci); printf("Greska prilikom alokacije memorije"); exit(1); } /* Kopiramo elemente starog niza u novi */

116

Milena Vujo sevi cJani ci c

9.2 Realokacija memorije

for (i=0; i<duzina; i++) novi_niz[i]=niz_reci[i]; /* Uklanjamo stari niz */ free(niz_reci); /* Stari niz postaje novi */ niz_reci=novi_niz; } } /* Upisujemo rec u niz */ niz_reci[duzina]=nova_rec; duzina++; } } /* Ispisujemo elemente niza */ for(i=0; i<duzina; i++) printf("%s - %d\n",niz_reci[i].ime, niz_reci[i].br_pojavljivanja); free(niz_reci); }

117

Glava 10

Liste
Jednostruko povezana lista1 je struktura podataka koja se sastoji od sekvence cvorova. Svaki cvor sadr zi podatak (odred enog tipa) i pokaziva c na slede ci cvor u sekvenci. Prvi cvor u sekvenci naziva se glava liste. Ostatak liste (bez glave) je takod e lista, i naziva se rep liste. Lista koja ne sadr zi cvorove naziva se prazna lista. Prilikom baratanja listom mi cuvamo pokaziva c na glavu liste. Kada pristupimo glavi liste, u njoj imamo zapisanu adresu slede ceg elementa, pa mu samim tim mozemo pristupiti. Kada mu pristupimo, u njemu je sadr zana adresa slede ceg elementa, pa preko tog pokaziva ca mo zemo da mu pristupimo, itd. Poslednji element u listi nema slede ci element: u tom slu caju se njegov pokaziva c na slede ci postavlja na NULL. Takod e, prazna lista se predstavlja NULL pokaziva cem. Prednost kori s cenja povezanih lista u odnosu na dinami cki niz je u tome sto se elementi mogu ekasno umetati i brisati sa bilo koje pozicije u nizu, bez potrebe za realokacijom ili preme stanjem elemenata. Nedostatak ovakvog pristupa je to sto ne mo zemo nasumi cno pristupiti proizvoljnom elementu, ve c se elementi moraju obrad ivati redom (iteracijom kroz listu).

10.1

Implementacija

Prilikom promene liste (dodavanje novog elementa, brisanje elementa, premestanje elemenata, itd.) postoji mogu cnost da glava liste bude promenjena, tj. da to postane neki drugi cvor (sa drugom adresom). U tom slu caju se pokaziva c na glavu liste mora a zurirati. Kada promenu liste obavljamo u posebnoj funkciji (kao sto je bio slu caj u prethodnom primeru, gde smo za dodavanje i brisanje imali posebne funkcije) onda je potrebno da se pozivaju coj funkciji vrati informacija o promeni adrese glave, kako bi pozivaju ca funkcija mogla da a zurira svoju pokaziva cku promenljivu. Ovo se mo ze uraditi na dva na cina: 1. Pozvana funkcija koja vr si promenu na listi vra ca kao povratnu vrednost adresu glave nakon promene. Ova adresa mo ze biti ista kao i pre promene (ako glava nije dirana) a mo ze se i razlikovati. U svakom slu caju, ta adresa treba da se dodeli pokaziva ckoj promenljivoj koja cuva adresu glave u pozivaju coj funkciji.
1 Tekst

preuzet sa sajta www.matf.bg.ac.rs/ milan

118

Milena Vujo sevi cJani ci c

10.1 Implementacija

Zbog toga se funkcija za promenu liste uvek poziva na slede ci nacin: pok = funkcija_za_promenu(pok, ...); tj. promenljivoj cija se vrednost predaje kao adresa glave u pozivu mora se dodeliti povratna vrednost funkcije, za slu caj da je adresa glave interno promenjena. 2. Pozvana funkcija koja vr si promenu na listi prihvata kao argument pokaziva c na pokaziva cku promenljivu koja u pozivaju coj funkciji cuva adresu glave i koju eventalno treba a zurirati. Sada pozvana funkcija mo ze interno da preko dobijenog pokaziva ca promeni promenljivu pozivaju ce funkcije direktno. Na primer: funkcija_za_promenu(&pok, ...); Funkcija koja se poziva je po pravilu tipa void. Prednost drugog metoda je jednostavnije pozivanje, dok je prednost prvog metoda jednostavnija sintaksa unutar pozvane funkcije. U prethodnom primeru je demonstriran prvi pristup. Naredni primer ilustruje drugi pristup. Primer 10.1 Osnovne funkcije za rad sa listama. #include <stdio.h> #include <stdlib.h> /* Struktura koja predstavlja cvor liste */ typedef struct cvor { int vrednost; /* podatak koji cvor sadrzi */ struct cvor *sledeci; /* pokazivac na sledeci cvor */ } Cvor; /* Pomocna funkcija koja kreira cvor. Funkcija vrednost novog cvora inicijalizuje na broj, dok pokazivac na sledeci cvor u novom cvoru postavlja na NULL. Funkcija ispisuje poruku o gresci i prekida program u slucaju da malloc() funkcija ne uspe da alocira prostor. Funkcija vraca pokazivac na novokreirani cvor */ Cvor* napravi_cvor(int broj) { Cvor *novi = NULL; if((novi = (Cvor *) malloc(sizeof(Cvor))) == NULL) { fprintf(stderr,"malloc() greska!\n"); exit(1); } novi->vrednost = broj; novi->sledeci = NULL;
119

Milena Vujo sevi cJani ci c

10.1 Implementacija

return novi; } /* Funkcija dodaje novi cvor na pocetak liste. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na novu glavu liste */ Cvor* dodaj_na_pocetak_liste(Cvor *glava, int broj) { Cvor * novi = napravi_cvor(broj); novi->sledeci = glava; return novi; } /*void dodaj_na_pocetak_liste(Cvor **glava, int broj) { Cvor *novi = napravi_cvor(broj); novi->sledeci = *glava; *glava = novi; } */ /* Funkcija dodaje novi cvor na kraj liste. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na glavu liste (koji moze biti promenjen u slucaju da je lista inicijalno bila prazna). */ Cvor* dodaj_na_kraj_liste(Cvor *glava, int broj) { Cvor * novi = napravi_cvor(broj); Cvor *tekuci = glava; /* slucaj prazne liste. U tom slucaju je glava nove liste upravo novi cvor. */ if(glava == NULL) return novi; /* Ako lista nije prazna, tada se krecemo duz liste sve dok ne dodjemo do poslednjeg cvora (tj. do cvora ciji pokazivac na sledeci pokazuje na NULL) */ while(tekuci->sledeci != NULL) tekuci = tekuci->sledeci; /* Dodajemo novi element na kraj preusmeravanjem pokazivaca */ tekuci->sledeci = novi;

120

Milena Vujo sevi cJani ci c

10.1 Implementacija

/* Vracamo pokazivac na glavu liste */ return glava; } /*void dodaj_na_kraj_liste(Cvor **glava, int broj) { Cvor *novi = napravi_cvor(broj); Cvor *tekuci = *glava; /* slucaj prazne liste. U tom slucaju je glava nove liste upravo novi cvor. */ if(*glava == NULL) { *glava = novi; return; } /* Ako lista nije prazna, tada se krecemo duz liste sve dok ne dodjemo do poslednjeg cvora (tj. do cvora ciji pokazivac na sledeci pokazuje na NULL) */ while(tekuci->sledeci != NULL) tekuci = tekuci->sledeci; /* Dodajemo novi element na kraj preusmeravanjem pokazivaca */ tekuci->sledeci = novi; } */ /* Funkcija trazi u listi element cija je vrednost jednaka datom broju. Funkcija vraca pokazivac na cvor liste u kome je sadrzan trazeni broj ili NULL u slucaju da takav element ne postoji u listi */ Cvor* pretrazi_listu(Cvor *glava, int broj) { for(; glava != NULL ; glava = glava->sledeci) if(glava->vrednost == broj) return glava; return NULL; } /* Funkcija prikazuje elemente liste pocev od glave ka kraju liste */ void prikazi_listu(Cvor *glava)

121

Milena Vujo sevi cJani ci c

10.1 Implementacija

{ putchar([); for(;glava != NULL; glava = glava->sledeci) printf("%d ", glava->vrednost); putchar(]); putchar(\n); } /* Funkcija oslobadja dinamicku memoriju zauzetu od strane liste. Funkcija vraca NULL, tj. vrednost koju treba dodeliti pokazivackoj promenljivoj, s obzirom da je sada lista prazna. */ Cvor* oslobodi_listu(Cvor *glava) { Cvor *pomocni; while(glava != NULL) { /* moramo najpre zapamtiti adresu sledeceg elementa, a tek onda osloboditi glavu */ pomocni = glava->sledeci; free(glava); glava = pomocni; } return NULL; } /*void oslobodi_listu(Cvor **glava) { Cvor *pomocni; while(*glava != NULL) { /* moramo najpre zapamtiti adresu sledeceg elementa, a tek onda osloboditi glavu */ pomocni = (*glava)->sledeci; free(*glava); *glava = pomocni; } } */ /* Funkcija dodaje novi element u sortiranu listu tako da i nova

122

Milena Vujo sevi cJani ci c

10.1 Implementacija

lista ostane sortirana. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na glavu liste (koji moze biti promenjen u slucaju da je novi element dodat na pocetak liste) */ Cvor* dodaj_sortirano(Cvor *glava, int broj) { Cvor *novi = napravi_cvor(broj); Cvor *tekuci = glava; /* u slucaju prazne liste glava nove liste je upravo novi element */ if(glava == NULL) return novi; /* ako je novi element manji ili jednak od glave, tada novi element mora da bude nova glava */ if(glava->vrednost >= novi->vrednost) { novi->sledeci = glava; return novi; } /* u slucaju da je glava manja od novog elementa, tada se krecemo kroz listu sve dok se ne dodje do elementa ciji je sledeci element veci ili jednak od novog elementa, ili dok se ne dodje do poslednjeg elementa. */ while(tekuci->sledeci != NULL && tekuci->sledeci->vrednost < novi->vrednost) tekuci = tekuci->sledeci; /* U svakom slucaju novi element dodajemo IZA tekuceg elementa */ novi->sledeci = tekuci->sledeci; tekuci->sledeci = novi; /* vracamo pokazivac na glavu liste*/ return glava; } /* void dodaj_sortirano(Cvor **glava, int broj) { Cvor *novi = napravi_cvor(broj); Cvor *tekuci = *glava; /* u slucaju prazne liste glava nove liste je upravo novi element */ if(*glava == NULL) {

123

Milena Vujo sevi cJani ci c

10.1 Implementacija

*glava = novi; return; } /* ako je novi element manji ili jednak od glave, tada novi element mora da bude nova glava */ if((*glava)->vrednost >= novi->vrednost) { novi->sledeci = *glava; *glava = novi; return; } /* u slucaju da je glava manja od novog elementa, tada se krecemo kroz listu sve dok se ne dodje do elementa ciji je sledeci element veci ili jednak od novog elementa, ili dok se ne dodje do poslednjeg elementa. */ while(tekuci->sledeci != NULL && tekuci->sledeci->vrednost < novi->vrednost) tekuci = tekuci->sledeci; /* U svakom slucaju novi element dodajemo IZA tekuceg elementa */ novi->sledeci = tekuci->sledeci; tekuci->sledeci = novi; } */ /* Funkcija brise iz liste sve cvorove koji sadrze dati broj. Funkcija vraca pokazivac na glavu liste (koji moze biti promenjen u slucaju da se obrise stara glava) */ Cvor* obrisi_element(Cvor *glava, int broj) { Cvor *tekuci; Cvor *pomocni; /* Brisemo sa pocetka liste sve eventualne cvorove koji su jednaki datom broju, i azuriramo pokazivac na glavu */ while(glava != NULL && glava->vrednost == broj) { pomocni = glava->sledeci; free(glava); glava = pomocni; } /* Ako je nakon toga lista ostala prazna vracamo

124

Milena Vujo sevi cJani ci c

10.1 Implementacija

NULL */ if(glava == NULL) return NULL; /* Od ovog trenutka se u svakom koraku nalazimo na tekucem cvoru koji je razlicit od trazenog broja (kao i svi levo od njega). Poredimo vrednost sledeceg cvora (ako postoji) sa trazenim brojem i brisemo ga ako je jednak, a prelazimo na sledeci cvor ako je razlicit. Ovaj postupak ponavljamo dok ne dodjemo do poslednjeg cvora. */ tekuci = glava; while(tekuci->sledeci != NULL) if(tekuci->sledeci->vrednost == broj) { pomocni = tekuci->sledeci; tekuci->sledeci = tekuci->sledeci->sledeci; free(pomocni); } else tekuci = tekuci->sledeci; /* vracamo novu glavu */ return glava; } /* void obrisi_element(Cvor **glava, int broj) { Cvor *tekuci; Cvor *pomocni; /* Brisemo sa pocetka liste sve eventualne cvorove koji su jednaki datom broju, i azuriramo pokazivac na glavu */ while(*glava != NULL && (*glava)->vrednost == broj) { pomocni = (*glava)->sledeci; free(*glava); *glava = pomocni; } /* Ako je nakon toga lista ostala prazna prekidamo funkciju */ if(*glava == NULL) return; /* Od ovog trenutka se u svakom koraku nalazimo

125

Milena Vujo sevi cJani ci c

10.1 Implementacija

na tekucem cvoru koji je razlicit od trazenog broja (kao i svi levo od njega). Poredimo vrednost sledeceg cvora (ako postoji) sa trazenim brojem i brisemo ga ako je jednak, a prelazimo na sledeci cvor ako je razlicit. Ovaj postupak ponavljamo dok ne dodjemo do poslednjeg cvora. */ tekuci = *glava; while(tekuci->sledeci != NULL) if(tekuci->sledeci->vrednost == broj) { pomocni = tekuci->sledeci; tekuci->sledeci = tekuci->sledeci->sledeci; free(pomocni); } else tekuci = tekuci->sledeci; return; } */ /* test program */ int main() { Cvor *glava = NULL; Cvor *pomocni = NULL; int broj;

/* Testiranje dodavanja na pocetak */ printf("-------------------------------------------------------\n"); printf("---------- Testiranje dodavanja na pocetak ------------\n"); do { printf("-------------------------------------------------------\n"); printf("Prikaz trenutnog sadrzaja liste:\n"); prikazi_listu(glava); printf("-------------------------------------------------------\n"); printf("Dodati element na pocetak liste (ctrl-D za kraj unosa):\n"); } while(scanf("%d", &broj) > 0 && (glava = dodaj_na_pocetak_liste(glava, broj)) ); /* dodaj_na_pocetak_liste(&glava, broj);*/ printf("-------------------------------------------------------\n"); putchar(\n); /* Testiranje pretrage elementa */

126

Milena Vujo sevi cJani ci c

10.1 Implementacija

printf("-------------------------------------------------------\n"); printf("---------------- Testiranje pretrage ------------------\n"); printf("-------------------------------------------------------\n"); printf("Prikaz trenutnog sadrzaja liste:\n"); prikazi_listu(glava); printf("-------------------------------------------------------\n"); printf("Uneti broj koji se trazi: "); scanf("%d",&broj); if((pomocni = pretrazi_listu(glava, broj)) == NULL) printf("Trazeni broj nije u listi\n"); else printf("Trazeni element %d je u listi\n", pomocni->vrednost); printf("-------------------------------------------------------\n"); putchar(\n); glava = oslobodi_listu(glava); /*oslobodi_listu(&glava);*/ /* Testiranje dodavanja na kraj */ printf("-------------------------------------------------------\n"); printf("------------ Testiranje dodavanja na kraj -------------\n"); do{ printf("-------------------------------------------------------\n"); printf("Prikaz liste:\n"); prikazi_listu(glava); printf("-------------------------------------------------------\n"); printf("Dodati element na kraj liste (ctrl-D za kraj unosa):\n"); } while(scanf("%d",&broj) > 0 && (glava = dodaj_na_kraj_liste(glava, broj))); /*(dodaj_na_kraj_liste(&glava,broj)));*/ printf("-------------------------------------------------------\n"); putchar(\n);

/* Testiranje brisanja elemenata */ printf("-------------------------------------------------------\n"); printf("--------------- Testiranje brisanja -------------------\n"); printf("-------------------------------------------------------\n"); printf("Prikaz trenutnog sadrzaja liste:\n"); prikazi_listu(glava); printf("-------------------------------------------------------\n"); printf("Uneti broj koji se brise: "); scanf("%d", &broj);

127

Milena Vujo sevi cJani ci c

10.1 Implementacija

glava = obrisi_element(glava, broj); /* obrisi_element(&glava, broj); */ printf("-------------------------------------------------------\n"); printf("Lista nakon izbacivanja:\n"); prikazi_listu(glava); printf("-------------------------------------------------------\n"); putchar(\n); glava = oslobodi_listu(glava); /*oslobodi_listu(&glava);*/ /* Testiranje sortiranog dodavanja */ printf("-------------------------------------------------------\n"); printf("----------- Testiranje sortiranog dodavanja -----------\n"); do{ printf("-------------------------------------------------------\n"); printf("Prikaz liste:\n"); prikazi_listu(glava); printf("-------------------------------------------------------\n"); printf("Dodati element sortirano (ctrl-D za kraj unosa):\n"); } while(scanf("%d",&broj) > 0 && (glava = dodaj_sortirano(glava, broj))); /*(dodaj_sortirano(&glava, broj)));*/ printf("-------------------------------------------------------\n"); putchar(\n); glava = oslobodi_listu(glava); /*oslobodi_listu(&glava);*/ return 0; } Primer 10.2 Sve funkcije iz prethodnih primera, za koje to ima smisla, denisane su rekurzivno. Primetno je da su ove rekurzivne varijante mnogo jednostavnije. Razlog je to sto su liste po svojoj prirodi rekurzivne strukture. Naime, liste se mogu rekurzivno denisati na slede ci na cin: objekat ozna cen sa [] je lista (prazna lista) ako je L lista, i G neki element, tada je ured eni par (G,L) takod e lista. Element G nazivamo glavom liste, a listu L repom liste. Na primer, uredjeni par (2, []) je lista, i po dogovoru cemo je zapisivati kao [2]. Takodje par (3,[2]) je lista koju mo zemo zapisati kao [3 2], itd. U opstem slucaju, ako je L=[a1 a2 a3 ... an] lista, i ako je b neki element, tada je uredjeni par (b,L) takod e lista, koju mo zemo zapisati kao [b a1 a2 ... an].
128

Milena Vujo sevi cJani ci c

10.1 Implementacija

Glava liste je b, a rep liste je lista L. Sada se obrada liste mo ze razdvojiti na dva dela: Izvr siti operaciju nad glavom liste Izvr siti rekurzivni postupak nad repom liste (koji je takod e lista). Za testiranje narednog programa mo ze se koristiti main funkcija iz primera 10.1. #include <stdio.h> #include <stdlib.h> /* Struktura koja predstavlja cvor liste */ typedef struct cvor { int vrednost; /* podatak koji cvor sadrzi */ struct cvor *sledeci; /* pokazivac na sledeci cvor */ } Cvor;

/* Pomocna funkcija koja kreira cvor. Funkcija vrednost novog cvora inicijalizuje na broj, dok pokazivac na sledeci cvor u novom cvoru postavlja na NULL. Funkcija ispisuje poruku o gresci i prekida program u slucaju da malloc() funkcija ne uspe da alocira prostor. Funkcija vraca pokazivac na novokreirani cvor */ Cvor* napravi_cvor(int broj) { Cvor *novi = NULL; if((novi = (Cvor *) malloc(sizeof(Cvor))) == NULL) { fprintf(stderr,"malloc() greska!\n"); exit(1); } novi->vrednost = broj; novi->sledeci = NULL; return novi; } /* Funkcija dodaje novi cvor na pocetak liste. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na novu glavu liste */ Cvor* dodaj_na_pocetak_liste(Cvor *glava, int broj) { Cvor * novi = napravi_cvor(broj); novi->sledeci = glava; return novi;
129

Milena Vujo sevi cJani ci c

10.1 Implementacija

} /* void dodaj_na_pocetak_liste(Cvor **glava, int broj) { Cvor * novi = napravi_cvor(broj); novi->sledeci = *glava; *glava = novi; } */ /* Funkcija dodaje novi cvor na kraj liste. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na glavu liste (koji moze biti promenjen u slucaju da je lista inicijalno bila prazna). Funkcija koristi rekurziju. */ Cvor* dodaj_na_kraj_liste(Cvor *glava, int broj) { /* Izlaz iz rekurzije: slucaj prazne liste. U tom slucaju je glava nove liste upravo novi cvor. */ if(glava == NULL) return napravi_cvor(broj); /* U slucaju da je lista neprazna, tada ona ima glavu i rep, pri cemu je rep opet lista. Tada je dodavanje na kraj liste ekvivalentno dodavanju na kraj repa liste, sto radimo rekurzivnim pozivom. Povratna vrednost rekurzivnog poziva je adresa glave modifikovanog repa liste koja se treba sacuvati u pokazivacu na sledeci u glavi */ glava->sledeci = dodaj_na_kraj_liste(glava->sledeci, broj); /* Vracamo glavu liste, koja je mozda izmenjena */ return glava; } /* void dodaj_na_kraj_liste(Cvor **glava, int broj) { /* Izlaz iz rekurzije: slucaj prazne liste. U tom slucaju je glava nove liste upravo novi cvor. */ if(*glava == NULL) *glava = napravi_cvor(broj); else /* U slucaju da je lista neprazna, tada ona ima glavu i rep, pri cemu je rep opet lista. Tada je dodavanje na kraj liste ekvivalentno dodavanju na kraj repa liste, sto

130

Milena Vujo sevi cJani ci c

10.1 Implementacija

radimo rekurzivnim pozivom. */ dodaj_na_kraj_liste(&(*glava->sledeci), broj); } */ /* Funkcija dodaje novi element u sortiranu listu tako da i nova lista ostane sortirana. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na glavu liste (koji moze biti promenjen u slucaju da je novi element dodat na pocetak liste). Funkcija koristi rekurziju. */ Cvor* dodaj_sortirano(Cvor *glava, int broj) { /* u slucaju da je lista prazna, ili da je vrednost glave veca ili jednaka od vrednosti broja koji umecemo, tada se novi cvor umece na pocetak liste. */ if(glava == NULL || glava->vrednost >= broj) { Cvor *novi = napravi_cvor(broj); novi->sledeci = glava; return novi; } /* U slucaju da je lista neprazna, i da je vrednost glave manja od vrednosti broja koji umecemo u listu, tada je sigurno da se novi element umece DESNO od glave, tj. u rep liste. Dakle, mozemo rekurzivno pozvati istu funkciju za rep liste. S obzirom da po induktivnoj pretpostavci rep nakon toga ostaje sortiran, a svi elementi u repu ukljucujuci i element koji je dodat su jednaki ili veci od glave, tada ce i cela lista biti sortirana. Povratna vrednost rekurzivnog poziva predstavlja adresu glave modifikovanog repa liste, pa se ta adresa mora sacuvati u pokazivacu na sledeci u glavi liste. */ glava->sledeci = dodaj_sortirano(glava->sledeci, broj); /* vracamo staru - novu glavu */ return glava; } /* void dodaj_sortirano(Cvor **glava, int broj) {

131

Milena Vujo sevi cJani ci c

10.1 Implementacija

/* u slucaju da je lista prazna, ili da je vrednost glave veca ili jednaka od vrednosti broja koji umecemo, tada se novi cvor umece na pocetak liste. */ if(*glava == NULL || *glava->vrednost >= broj) { Cvor *novi = napravi_cvor(broj); novi->sledeci = *glava; *glava = novi; } else /* U slucaju da je lista neprazna, i da je vrednost glave manja od vrednosti broja koji umecemo u listu, tada je sigurno da se novi element umece DESNO od glave, tj. u rep liste. Dakle, mozemo rekurzivno pozvati istu funkciju za rep liste. S obzirom da po induktivnoj pretpostavci rep nakon toga ostaje sortiran, a svi elementi u repu ukljucujuci i element koji je dodat su jednaki ili veci od glave, tada ce i cela lista biti sortirana.*/ dodaj_sortirano(&(*glava->sledeci), broj); } */ /* Funkcija trazi u listi element cija je vrednost jednaka datom broju. Funkcija vraca pokazivac na cvor liste u kome je sadrzan trazeni broj ili NULL u slucaju da takav element ne postoji u listi. Funkcija koristi rekurziju. */ Cvor* pretrazi_listu(Cvor *glava, int broj) { /* Izlaz iz rekurzije: prazna lista */ if(glava == NULL) return NULL; /* ako je glava jednaka datom broju, vracamo adresu glave */ if(glava->vrednost == broj) return glava; /* u suprotnom pretrazujemo rep rekurzivnim pozivom i vracamo ono sto taj rekurzivni poziv vrati */ else return pretrazi_listu(glava->sledeci, broj); } /* Funkcija brise iz liste sve cvorove koji sadrze dati broj. Funkcija vraca pokazivac na glavu liste (koji moze biti

132

Milena Vujo sevi cJani ci c

10.1 Implementacija

promenjen u slucaju da se obrise stara glava). Funkcija koristi rekurziju. */ Cvor* obrisi_element(Cvor *glava, int broj) { /* Izlaz iz rekurzije: prazna lista ostaje prazna nakon brisanja elemenata koji su jednaki sa brojem */ if(glava == NULL) return NULL; /* Rekurzivno iz repa uklanjamo sve pojave datog broja. Rekurzivni poziv vraca adresu glave tako modifikovanog repa, koja se cuva u pokazivacu na sledeci u okviru glave */ glava->sledeci = obrisi_element(glava->sledeci, broj);

/* Nakon ovoga znamo da u repu nema pojava datog broja. Jedino ostaje da proverimo jos i glavu, i da je po potrebi obrisemo */ if(glava->vrednost == broj) { Cvor *pomocni = glava; glava = glava->sledeci; free(pomocni); } /* vracamo novu glavu */ return glava; } /* void obrisi_element(Cvor **glava, int broj) { /* Izlaz iz rekurzije: prazna lista ostaje prazna nakon brisanja elemenata koji su jednaki sa brojem */ if(*glava == NULL) return; /* Rekurzivno iz repa uklanjamo sve pojave datog broja. Rekurzivni poziv vraca adresu glave tako modifikovanog repa, koja se cuva u pokazivacu na sledeci u okviru glave */ obrisi_element(&(*glava->sledeci), broj);

/* Nakon ovoga znamo da u repu nema pojava datog broja. Jedino ostaje da proverimo jos i glavu, i da je po potrebi obrisemo */

133

Milena Vujo sevi cJani ci c

10.1 Implementacija

if(*glava->vrednost == broj) { Cvor *pomocni = *glava; *glava = *glava->sledeci; free(pomocni); } } */ /* Pomocna rekurzivna funkcija koja prikazuje listu. Definisana je kao static, cime se sprecava njeno koriscenje od strane funkcija koje nisu definisane u ovom fajlu */ static void prikazi_listu_r(Cvor *glava) { /* Izlaz iz rekurzije */ if(glava == NULL) return; /* Prikaz glave */ printf("%d ", glava->vrednost); /* Prikaz repa (rekurzivnim pozivom) */ prikazi_listu_r(glava->sledeci); } /* Funkcija prikazuje elemente liste pocev od glave ka kraju liste. Koristi rekurzivnu funkciju prikazi_listu_r(). Ova funkcija prosto dodaje uglaste zagrade i novi red na kraju ispisa. */ void prikazi_listu(Cvor *glava) { putchar([); prikazi_listu_r(glava); putchar(]); putchar(\n); } /* Funkcija oslobadja dinamicku memoriju zauzetu od strane liste. Funkcija koristi rekurziju. */

134

Milena Vujo sevi cJani ci c

10.1 Implementacija

Cvor* oslobodi_listu(Cvor *glava) { /* Izlaz iz rekurzije */ if(glava == NULL) return NULL; /* Oslobadjamo rep liste */ oslobodi_listu(glava->sledeci); /* Oslobadjamo glavu liste */ free(glava); return NULL; } /* void oslobodi_listu(Cvor **glava) { /* Izlaz iz rekurzije */ if(*glava == NULL) return; /* Oslobadjamo rep liste */ oslobodi_listu(&(*glava->sledeci)); /* Oslobadjamo glavu liste */ free(*glava); *glava = NULL; } */ Primer 10.3 Program ispisuje broj pojavljivanja za svaku od re ci koja se pojavila u tekstu unetom sa standardnog ulaza. Verzija sa (sortiranom) listom. #include <stdlib.h> #include <stdio.h> /* Definicija cvora liste */ typedef struct _cvor { char ime[80]; int br_pojavljivanja; struct _cvor* sledeci; } cvor; /* Pomocna funkcija koja kreira cvor. Funkcija vraca pokazivac na novokreirani cvor */
135

Milena Vujo sevi cJani ci c

10.1 Implementacija

cvor* napravi_cvor(char* rec) { cvor *novi = NULL; if((novi = (cvor *) malloc(sizeof(cvor))) == NULL) { fprintf(stderr,"malloc() greska!\n"); exit(1); } strcpy(novi->ime, rec); novi->br_pojavljivanja = 1; novi->sledeci = NULL; return novi; } /* Funkcija ispisuje listu rekurzivno, pocevsi od poslednjeg elementa */ void ispisi_listu(cvor* pocetak) { if (pocetak!=NULL) { ispisi_listu(pocetak->sledeci); printf("%s %d\n",pocetak->ime,pocetak->br_pojavljivanja); } } /* Funkcija koja brise listu */ void obrisi_listu(cvor* pocetak) { if (pocetak!=NULL) { obrisi_listu(pocetak->sledeci); free(pocetak); } } /* Funkcija ubacuje rekurzivno datu rec u listu koja je leksikografski sortirana, na odgovarajuce mesto i vraca pokazivac na novi pocetak liste */ cvor* ubaci_sortirano(cvor* pocetak, char* rec) { int cmp; /* Ukoliko je lista prazna ubacujemo na pocetak liste*/ if (pocetak==NULL)

136

Milena Vujo sevi cJani ci c

10.1 Implementacija

return napravi_cvor(rec); /* Ukoliko lista nije prazna poredimo rec sa elementom u glavi */ cmp=strcmp(pocetak->ime,rec); /* Ukoliko je rec pronadjena samo uvecavamo njen broj pojavljivanja */ if (cmp==0) { pocetak->br_pojavljivanja++; return pocetak; } /* Ukoliko je rec koju ubacujemo veca od tekuce reci, ubacujemo je rekurzivno u rep */ else if (cmp>0) { pocetak->sledeci=ubaci_sortirano(pocetak->sledeci,rec); return pocetak; } /* Ukoliko je rec koju ubacujemo manja od tekuce reci, gradimo novi cvor i ubacujemo ga ispred pocetka */ else { cvor* novi=napravi_cvor(rec); novi->sledeci=pocetak; return novi; } } /* Pomocna funkcija koja cita rec sa standardnog ulaza i vraca njenu duzinu, odnosno -1 ukoliko se naidje na EOF */ int getword(char word[], int lim) { int c, i=0; while (!isalpha(c=getchar()) && c!=EOF) ; if (c==EOF) return -1; do { word[i++]=c; }while (i<lim-1 && isalpha(c=getchar())); word[i]=\0; return i; } /* Funkcija koja rekurzivno pronalazi datu rec u datoj listi.

137

Milena Vujo sevi cJani ci c

10.1 Implementacija

Funkcija vraca pokazivac na cvor u kome je nadjena rec, ili NULL ukoliko rec nije nadjena. */ cvor* nadji_rec(cvor* lista, char rec[]) { if (lista==NULL) return NULL; if (strcmp(lista->ime,rec)==0) return lista; /* Da bi pretrazivanje u sortiranoj listi bilo efikasnije: if (strcmp(lista->ime,rec)<0) return NULL; */ return nadji_rec(lista->sledeci,rec); } main() { cvor* lista=NULL; char procitana_rec[80]; cvor* pronadjen; while(getword(procitana_rec,80)!=-1) lista=ubaci_sortirano(lista,procitana_rec); pronadjen=nadji_rec(lista,"programiranje"); if (pronadjen!=NULL) printf("Rec programiranje se javlja u listi!\n"); else printf("Rec programiranje se ne javlja u listi!\n"); ispisi_listu(lista); obrisi_listu(lista); } Primer 10.4 Primer ilustruje sortiranje liste. Lista moze biti sortirana na dva na cina. Prvi na cin je da se preme staju cvorovi u listi ( sto zahteva prevezivanje pokaziva ca), a drugi je da se preme staju vrednosti koje cvorovi sadr ze. Prvi na cin je komplikovaniji, ali je cesto ekasniji, naro cito ako su objekti koji se cuvaju kao podaci u cvorovima veliki. U oba slu caja se mo ze koristiti rekurzija, a mo ze se realizovati i iterativno. U ovom primeru ilustrujemo oba na cina sortiranja lista (prvi rekurzivno, a drugi iterativno). #include <stdio.h> #include <stdlib.h>
138

Milena Vujo sevi cJani ci c

10.1 Implementacija

#include <time.h> /* Struktura koja predstavlja cvor liste */ typedef struct cvor { int vrednost; /* podatak koji cvor sadrzi */ struct cvor *sledeci; /* pokazivac na sledeci cvor */ } Cvor;

/* Pomocna funkcija koja kreira cvor. Funkcija vrednost novog cvora inicijalizuje na broj, dok pokazivac na sledeci cvor u novom cvoru postavlja na NULL. Funkcija ispisuje poruku o gresci i prekida program u slucaju da malloc() funkcija ne uspe da alocira prostor. Funkcija vraca pokazivac na novokreirani cvor */ Cvor* napravi_cvor(int broj) { Cvor *novi = NULL; if((novi = (Cvor *) malloc(sizeof(Cvor))) == NULL) { fprintf(stderr,"malloc() greska!\n"); exit(1); } novi->vrednost = broj; novi->sledeci = NULL; return novi; } /* Funkcija dodaje novi cvor na pocetak liste. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na novu glavu liste */ Cvor * dodaj_na_pocetak_liste(Cvor *glava, int broj) { Cvor * novi = napravi_cvor(broj); novi->sledeci = glava; return novi; } /* Pomocna rekurzivna funkcija koja prikazuje listu. Definisana je kao static, cime se sprecava njeno koriscenje od strane funkcija koje nisu definisane u ovom fajlu */ static void prikazi_listu_r(Cvor *glava)

139

Milena Vujo sevi cJani ci c

10.1 Implementacija

{ /* Izlaz iz rekurzije */ if(glava == NULL) return; /* Prikaz glave */ printf("%d ", glava->vrednost); /* Prikaz repa (rekurzivnim pozivom) */ prikazi_listu_r(glava->sledeci); } /* Funkcija prikazuje elemente liste pocev od glave ka kraju liste. Koristi rekurzivnu funkciju prikazi_listu_r(). Ova funkcija prosto dodaje uglaste zagrade i novi red na kraju ispisa. */ void prikazi_listu(Cvor *glava) { putchar([); prikazi_listu_r(glava); putchar(]); putchar(\n); } /* Funkcija oslobadja dinamicku memoriju zauzetu od strane liste. Funkcija koristi rekurziju. */ Cvor* oslobodi_listu(Cvor *glava) { /* Izlaz iz rekurzije */ if(glava == NULL) return NULL; /* Oslobadjamo rep liste */ oslobodi_listu(glava->sledeci); /* Oslobadjamo glavu liste */ free(glava); return NULL; } Cvor* dodaj_cvor_sortirano(Cvor * glava, Cvor * novi)

140

Milena Vujo sevi cJani ci c

10.1 Implementacija

{ /* u slucaju da je lista prazna, ili da je vrednost glave veca ili jednaka od vrednosti broja koji umecemo, tada se novi cvor umece na pocetak liste. */ if(glava == NULL || glava->vrednost >= novi->vrednost) { novi->sledeci = glava; return novi; } /* U slucaju da je lista neprazna, i da je vrednost glave manja od vrednosti broja koji umecemo u listu, tada je sigurno da se novi element umece DESNO od glave, tj. u rep liste. Dakle, mozemo rekurzivno pozvati istu funkciju za rep liste. S obzirom da po induktivnoj pretpostavci rep nakon toga ostaje sortiran, a svi elementi u repu ukljucujuci i element koji je dodat su jednaki ili veci od glave, tada ce i cela lista biti sortirana. Povratna vrednost rekurzivnog poziva predstavlja adresu glave modifikovanog repa liste, pa se ta adresa mora sacuvati u pokazivacu na sledeci u glavi liste. */ glava->sledeci = dodaj_cvor_sortirano(glava->sledeci, novi); /* vracamo glavu */ return glava; } /* Sortira listu i vraca adresu glave liste. Lista se sortira rekurzivno, prevezivanjem cvorova. */ Cvor* sortiraj_listu(Cvor * glava) { Cvor * pomocni; /* Izlaz iz rekurzije */ if(glava == NULL) return NULL; /* Odvajamo cvor glave iz liste, a za glavu proglasavamo sledeci cvor */ pomocni = glava; glava = glava->sledeci; pomocni->sledeci = NULL;

141

Milena Vujo sevi cJani ci c

10.1 Implementacija

/* Sortiramo ostatak liste */ glava = sortiraj_listu(glava); glava = dodaj_cvor_sortirano(glava, pomocni); /* Vracamo novu glavu */ return glava; } /* Funkcija sortira listu i vraca adresu glave liste. Lista se sortira iterativno, zamenom vrednosti u cvorovima. */ Cvor* sortiraj_listu2(Cvor * glava) { int t; Cvor * pi, * pj, * min; /* Slucaj prazne liste */ if(glava == NULL) return glava; /* Simuliramo selection sort */ for(pi = glava; pi->sledeci != NULL ; pi = pi->sledeci) { min = pi; for(pj = pi->sledeci; pj != NULL; pj = pj->sledeci) { if(pj->vrednost < min->vrednost) min = pj; } t = min->vrednost; min->vrednost = pi->vrednost; pi->vrednost = t; } /* Vracamo glavu */ return glava; } /* test program */ int main() { Cvor *glava = NULL; int n = 10; /* Inicijalizujemo sekvencu slucajnih brojeva. */

142

Milena Vujo sevi cJani ci c

10.2 Kru zna (cikli cna) lista

srand(time(NULL)); /* Generisemo n slucajnih brojeva i dodajemo ih na pocetak liste */ while(n--) glava = dodaj_na_pocetak_liste(glava, (int) (100 * (rand()/(double)RAND_MAX))); /* Prikazujemo generisanu listu */ printf("Generisana lista:\n"); prikazi_listu(glava); /* Sortiramo listu */ glava = sortiraj_listu(glava); /* glava = sortiraj_listu2(glava); */ /* Prikazujemo listu nakon sortiranja */ printf("Lista nakon sortiranja:\n"); prikazi_listu(glava); /* Oslobadjamo listu */ glava = oslobodi_listu(glava); return 0; }

10.2

Kru zna (cikli cna) lista

po cetak cikli cne liste

...

Slika 10.1: Kru zna lista

Primer 10.5 (februar 2006.) Grupa od n plesa ca (na cijim kostimima su u smeru kazaljke na satu redom brojevi od 1 do n) izvodi svoju plesnu ta cku tako sto formiraju krug iz kog najpre izlazi k -ti plesa c (odbrojava se po cev od plesa ca ozna cenog brojem 1 u smeru kretanja kazaljke na satu). Preostali plesa ci obrazuju manji krug iz kog opet izlazi k -ti plesa c (odbrojava se pocev od slede ceg suseda prethodno izba cenog, opet u smeru kazaljke na satu). Izlasci iz kruga se nastavljaju sve dok svi plesa ci
143

Milena Vujo sevi cJani ci c

10.3 Red

ne budu isklju ceni. Celi brojevi n, k (k < n) se u citavaju sa standardnog ulaza. Napisati program koji ce na standardni izlaz ispisati redne brojeve plesa ca u redosledu napu stanja kruga. PRIMER: za n = 5, k = 3 redosled izlaska je 3 1 5 2 4.

10.3

Red

po cetak reda (brisanje get)

kraj reda (dodavanje add)

...

NULL

novi element Y po cetak reda

novi kraj reda

...

Y kraj reda

NULL

po cetak reda nakon brisanja

...

NULL

Slika 10.2: Red

Red (eng. queue) je struktura podataka nad kojom su denisane sledece operacije: Dodavanje elementa kazemo da je element dodat na kraj reda (eng. enqueue() operacija) Uklanjanje elementa koji je prvi dodat kazemo da je element skinut sa pocetka reda (eng. dequeue() operacija) Ocitavanje vrednosti elementa koji je na pocetku reda (eng. front() operacija) Red spada u FIFO strukture (eng. First In First Out). Moze se implementirati na vise nacina. Najjednostavniji nacin je da se denise kao niz. Medjutim, tada je ogranicen max. broj elemenata u redu dimenzijom niza. Zbog toga se obicno pribegava koriscenju lista za implementaciju reda, gde se enqueue() operacija svodi na dodavanje na kraj liste, a dequeue() operacija se svodi na uklanjanje glave liste.
144

Milena Vujo sevi cJani ci c

10.3 Red

Obe operacije se izvode u konstantnom vremenu, pod uslovom da se osim pokazivaca na glavu liste cuva i pokazivac na poslednji element u listi. Primer 10.6 Skupovi brojeva su predstavljeni tekstualnim fajlovima koji sadrze brojeve i imena drugih fajlova. Ako se broj x nalazi u fajlu S, tada broj x pripada skupu S. Ako se ime fajla S1 nalazi u fajlu S2, tada je skup S1 podskup skupa S2. Program za dati skup S i dati broj x proverava da li x pripada S. U ovom zadatku ce red biti koriscen na sledeci nacin: inicijalno se u red stavlja ime fajla koji predstavlja dati skup S. Iz reda se zatim u petlji uzima sledeci fajl i otvara za citanje. Prilikom citanja, svaki put kada se u fajlu koji se trenutno cita naidje na ime drugog fajla, to ime ce biti dodato u red. Kada se zavrsi sa citanjem tekuceg fajla, isti se zatvara, skida se sa reda sledeci fajl i citanje se nastavlja u njemu. Ovaj proces se ponavlja dok se ne naidje na trazeni broj, ili dok se ne isprazni red. #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX 100 /* Struktura koja predstavlja cvor liste */ typedef struct cvor { char ime_fajla[MAX]; /* Sadrzi ime fajla */ struct cvor *sledeci; /* pokazivac na sledeci cvor */ } Cvor;

/* Funkcija kreira novi cvor, upisuje u njega etiketu i vraca njegovu adresu */ Cvor * napravi_cvor(char * ime_fajla) { Cvor *novi = NULL; if((novi = (Cvor *) malloc(sizeof(Cvor))) == NULL) { fprintf(stderr,"malloc() greska!\n"); exit(1); } strcpy(novi->ime_fajla, ime_fajla); novi->sledeci = NULL; return novi; }

/* Funkcija dodaje na kraj reda novi fajl */


145

Milena Vujo sevi cJani ci c

10.3 Red

void dodaj_u_red(Cvor **pocetak, Cvor **kraj, char *ime_fajla) { Cvor * novi = napravi_cvor(ime_fajla); if(*kraj != NULL) { (*kraj)->sledeci = novi; *kraj = novi; } else /* ako je red prazan */ { *pocetak = novi; *kraj = novi; } } /* Funkcija skida sa pocetka reda fajl. Ako je poslednji argument pokazivac razlicit od NULL, tada u niz karaktera na koji on pokazuje upisuje ime fajla koji je upravo skinut sa reda dok u suprotnom ne radi nista. Funkcija vraca 0 ako je red prazan (pa samim tim nije bilo moguce skinuti vrednost sa reda) ili 1 u suprotnom. */ int skini_sa_reda(Cvor **pocetak, Cvor ** kraj, char * ime_fajla) { Cvor * pomocni; if(*pocetak == NULL) return 0; if(ime_fajla != NULL) strcpy(ime_fajla, (*pocetak)->ime_fajla);

pomocni = *pocetak; *pocetak = (*pocetak)->sledeci; free(pomocni); if(*pocetak == NULL) *kraj = NULL; return 1; } /* Funkcija vraca pokazivac na string koji sadrzi ime fajla

146

Milena Vujo sevi cJani ci c

10.3 Red

na pocetku reda. Ukoliko je red prazan, vraca NULL */ char * pocetak_reda(Cvor *pocetak) { if(pocetak == NULL) return NULL; else return pocetak->ime_fajla; } /* Funkcija prazni red */ void oslobodi_red(Cvor **pocetak, Cvor **kraj) { Cvor *pomocni; while(*pocetak != NULL) { pomocni = *pocetak; *pocetak = (*pocetak)->sledeci; free(pomocni); } *kraj = NULL; } /* Glavni program */ int main(int argc, char ** argv) { Cvor * pocetak = NULL, * kraj = NULL; FILE *in = NULL; char ime_fajla[MAX]; int x, y; int pronadjen = 0; /* NAPOMENA: Pretpostavlja se da nema cirkularnih zavisnosti, tj. da ne moze da se desi slucaj: S2 podskup od S1, S3 podskup S2,...,S1 podskup od Sn, u kom slucaju bismo imali beskonacnu petlju. */ /* Ime fajla i broj se zadaju sa komandne linije */ if(argc < 3) { fprintf(stderr, "koriscenje: %s fajl broj\n", argv[0]); exit(1); } /* Dodajemo skup S u red */

147

Milena Vujo sevi cJani ci c

10.3 Red

dodaj_u_red(&pocetak, &kraj, argv[1]); /* Trazeni broj x */ x = atoi(argv[2]); /* Dokle god broj nije pronadjen, a ima jos fajlova u redu */ while(!pronadjen && skini_sa_reda(&pocetak, &kraj, ime_fajla)) { /* Otvaramo sledeci fajl sa reda */ if((in = fopen(ime_fajla, "r")) == NULL) { fprintf(stderr, "Greska prilikom otvaranja fajla: %s\n", ime_fajla); exit(1); } /* Dokle god nismo stigli do kraja fajla i nismo pronasli broj */ while(!feof(in) && !pronadjen) { /* Ako je broj sledeci podatak u fajlu */ if(fscanf(in, "%d", &y) == 1) { /* Ako je nadjen trazeni broj */ if(x == y) { printf("Broj x=%d pripada skupu!\n", x); pronadjen = 1; } } /* Ako je ime fajla sledeci podatak u fajlu */ else if(fscanf(in, "%s", ime_fajla) == 1) { /* Dodajemo ga u red */ dodaj_u_red(&pocetak, &kraj, ime_fajla); } } /* Zatvaramo fajl */ fclose(in); } /* Ako do kraja nije pronadjen */ if(!pronadjen) printf("Broj x=%d ne pripada skupu!\n", x);

148

Milena Vujo sevi cJani ci c

10.3 Red

/* Oslobadjamo red */ oslobodi_red(&pocetak, &kraj); return 0; } Primer 10.7 Program formira celobrojni red i ispisuje sadr zaj reda na standardni izlaz. #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX 100 /* Struktura koja predstavlja cvor liste */ typedef struct cvor { int broj; /* broj */ struct cvor *sledeci; /* pokazivac na sledeci cvor */ } Cvor;

/* Funkcija kreira novi cvor, upisuje u njega broj i vraca njegovu adresu */ Cvor * napravi_cvor(int broj) { Cvor *novi = NULL; if((novi = (Cvor *) malloc(sizeof(Cvor))) == NULL) { fprintf(stderr,"malloc() greska!\n"); exit(1); } novi->broj = broj; novi->sledeci = NULL; return novi; }

/* Funkcija dodaje na kraj reda broj */ void dodaj_u_red(Cvor **pocetak, Cvor **kraj, int broj) { Cvor * novi = napravi_cvor(broj); if(*kraj != NULL) {
149

Milena Vujo sevi cJani ci c

10.3 Red

(*kraj)->sledeci = novi; *kraj = novi; } else /* ako je red prazan */ { *pocetak = novi; *kraj = novi; } } /* Funkcija skida sa pocetka reda broj */ int skini_sa_reda(Cvor **pocetak, Cvor ** kraj, int *broj) { Cvor * pomocni; if(*pocetak == NULL) return 0; if(broj != NULL) *broj = (*pocetak)->broj);

pomocni = *pocetak; *pocetak = (*pocetak)->sledeci; free(pomocni); if(*pocetak == NULL) *kraj = NULL; return 1; } main() { Cvor * pocetak = NULL, * kraj = NULL; int i, broj; /*Formiranje jednostavnog reda*/ for(i=0; i<100; i++) dodaj_u_red(&pocetak, &kraj, i); /*Ispis elemenata reda koristeci operacije nad redom*/ while(skini_sa_reda(&pocetak, &kraj, &broj)) printf("Element uzet sa pocetka reda je %d\n", broj);

150

Milena Vujo sevi cJani ci c

10.4 Stek

/*Nema potrebe da se oslobadja memorija jer je memorija koju je red zauzimao oslobodjena prilikom ispisa elemenata reda - nakon ispisa red je ostao prazan!*/ }

10.4

Stek

Stek (eng. stack) je struktura podataka nad kojom su denisane sledece operacije: Dodavanje elementa kazemo da je element potisnut na vrh steka (eng. push() operacija). Uklanjanje elementa koji je poslednji dodat kazemo da je element skinut sa vrha steka (eng. pop() operacija). Ocitavanje vrednosti elementa koji je poslednji dodat (eng. top() operacija). Stek spada u LIFO strukture (eng. Last In First Out ). Moze se implementirati na vise nacina. Najjednostavniji nacin je da se denise kao niz. Medjutim, tada je ogranicen max. broj elemenata na steku dimenzijom niza. Zbog toga se obicno pribegava koriscenju lista za implementaciju steka, gde se push() operacija svodi na dodavanje na pocetak, a pop() operacija se svodi na uklanjanje glave liste. Obe operacije se izvode u konstantnom vremenu. Primer 10.8 Program proverava da li su zagrade (, [, {, }, ] i ) dobro uparene stati cka implementacija steka (pretpostavka da se na ulazu ne ce na ci vi se od 100 otvorenih zagrada za redom). #include <stdio.h> #include <stdlib.h> #define MAX_ZAGRADA 100 int odgovarajuce(char return (z1 == ( (z1 == { (z1 == [ } z1, char && z2 == && z2 == && z2 == z2) { )) || }) || ]);

main() { int c; char otv_zagrade[MAX_ZAGRADA]; int br_otv = 0; while((c=getchar()) != EOF) { switch(c)


151

Milena Vujo sevi cJani ci c

10.4 Stek

{ case (: case {: case [: { otv_zagrade[br_otv] = c; br_otv++; break; } case ]: case }: case ): if (br_otv>0 && odgovarajuce(otv_zagrade[br_otv-1], c)) { br_otv--; } else { printf("Visak zatvorenih zagrada: %c u liniji %d\n", c, br_linija); exit(1); } } } if (br_otv == 0) printf("Zagrade su u redu\n"); else printf("Visak otvorenih zagrada\n"); } Primer 10.9 Program proverava da li su etikete u datom HTML fajlu dobro uparene. #include #include #include #include <stdio.h> <stdlib.h> <string.h> <ctype.h>

#define MAX 100 #define OTVORENA 1 #define ZATVORENA 2 #define VAN_ETIKETE 0 #define PROCITANO_MANJE 1
152

Milena Vujo sevi cJani ci c

10.4 Stek

vrh steka (i dodavanje i brisanje push i pop)

...

NULL

novi element V

novi vrh steka

V vrh steka nakon brisanja

...

NULL

...

NULL

Slika 10.3: Stek

#define U_ETIKETI 2 /* Struktura koja predstavlja cvor liste */ typedef struct cvor { char etiketa[MAX]; /* Sadrzi ime etikete */ struct cvor *sledeci; /* pokazivac na sledeci cvor */ } Cvor; /* Funkcija kreira novi cvor, upisuje u njega etiketu i vraca njegovu adresu */ Cvor * napravi_cvor(char * etiketa) { Cvor *novi = NULL; if((novi = (Cvor *) malloc(sizeof(Cvor))) == NULL) { fprintf(stderr,"malloc() greska!\n"); exit(1); } strcpy(novi->etiketa, etiketa); novi->sledeci = NULL;
153

Milena Vujo sevi cJani ci c

10.4 Stek

return novi; } /* Funkcija postavlja na vrh steka novu etiketu */ void potisni_na_stek(Cvor **vrh, char *etiketa) { Cvor * novi = napravi_cvor(etiketa); novi->sledeci = *vrh; *vrh = novi; } /* Funkcija skida sa vrha steka etiketu. Ako je drugi argument pokazivac razlicit od NULL, tada u niz karaktera na koji on pokazuje upisuje ime etikete koja je upravo skinuta sa steka dok u suprotnom ne radi nista. Funkcija vraca 0 ako je stek prazan (pa samim tim nije bilo moguce skinuti vrednost sa steka) ili 1 u suprotnom. */ int skini_sa_steka(Cvor **vrh, char * etiketa) { Cvor * pomocni; if(*vrh == NULL) return 0; if(etiketa != NULL) strcpy(etiketa, (*vrh)->etiketa);

pomocni = *vrh; *vrh = (*vrh)->sledeci; free(pomocni); return 1; } /* Funkcija vraca pokazivac na string koji sadrzi etiketu na vrhu steka. Ukoliko je stek prazan, vraca NULL */ char * vrh_steka(Cvor *vrh) { if(vrh == NULL) return NULL; else return vrh->etiketa; }

154

Milena Vujo sevi cJani ci c

10.4 Stek

/* Funkcija prazni stek */ void oslobodi_stek(Cvor **vrh) { Cvor *pomocni; while(*vrh != NULL) { pomocni = *vrh; *vrh = (*vrh)->sledeci; free(pomocni); } } /* Funkcija iz fajla na koji pokazuje f cita sledecu etiketu, i njeno ime upisuje u niz na koji pokazuje pokazivac etiketa. Funkcija vraca EOF u slucaju da se dodje do kraja fajla pre nego sto se procita etiketa, vraca OTVORENA ako je procitana otvorena etiketa, odnosno ZATVORENA ako je procitana zatvorena etiketa. */ int uzmi_etiketu(FILE *f, char * etiketa) { int c; int stanje = VAN_ETIKETE; int i = 0; int tip; while((c = fgetc(f)) != EOF) { switch(stanje) { case VAN_ETIKETE: if(c == <) { stanje = PROCITANO_MANJE; } break; case PROCITANO_MANJE: if(c == /) { tip = ZATVORENA; } else if(isalpha(c))

155

Milena Vujo sevi cJani ci c

10.4 Stek

{ tip = OTVORENA; etiketa[i++] = tolower(c); } stanje = U_ETIKETI; break; case U_ETIKETI: if(isalpha(c) && i < MAX - 1) etiketa[i++] = tolower(c); else { stanje = VAN_ETIKETE; etiketa[i]=\0; return tip; } break; } } return EOF; } /* Test program */ int main(int argc, char **argv) { Cvor * vrh = NULL; char etiketa[MAX]; int tip; int u_redu = 1; FILE * f; /* Ime datoteke zadajemo na komandnoj liniji */ if(argc < 2) { fprintf(stderr, "Koriscenje: %s ime_html_datoteke\n", argv[0]); exit(0); } /* Otvaramo datoteku */ if((f = fopen(argv[1], "r")) == NULL) { fprintf(stderr, "fopen() greska!\n"); exit(1); }

156

Milena Vujo sevi cJani ci c

10.4 Stek

/* Dokle god ima etiketa, uzimamo ih jednu po jednu sa ulaza */ while((tip = uzmi_etiketu(f, etiketa)) != EOF) { /* Ako je otvorena etiketa, dodajemo je na stek. Izuzetak su etikete <br>, <hr> i <meta> koje nemaju sadrzaj, tako da ih nije potrebno zatvoriti. NAPOMENA: U html-u postoje jos neke etikete koje koje nemaju sadzaj (npr link). Pretpostavimo da njih nema u dokumentu, zbog jednostavnosti */ if(tip == OTVORENA) { if(strcmp(etiketa, "br") != 0 && strcmp(etiketa, "hr") != 0 && strcmp(etiketa, "meta") != 0) potisni_na_stek(&vrh, etiketa); } /* Ako je zatvorena etiketa, tada je uslov dobre uparenosti da je u pitanju zatvaranje etikete koja je poslednja otvorena, a jos uvek nije zatvorena. Ova etiketa se mora nalaziti na vrhu steka. Ako je taj uslov ispunjen, tada je skidamo sa steka, jer je zatvorena. U suprotnom, obavestavamo korisnika da etikete nisu pravilno uparene. */ else if(tip == ZATVORENA) { if(vrh_steka(vrh) != NULL && strcmp(vrh_steka(vrh), etiketa) == 0) skini_sa_steka(&vrh, NULL); else { printf(vrh_steka(vrh) != NULL ? "Etikete nisu pravilno uparene\n(nadjena etiketa </%s> a poslednja otvorena etiketa je <%s>)\n" : "Etikete nisu pravilno uparene\n(nadjena etiketa </%s> koja nije otvorena)\n", etiketa, vrh_steka(vrh)); u_redu = 0; break; } } } /* Zatvaramo fajl */ fclose(f); /* Ako nismo pronasli pogresno uparivanje...*/ if(u_redu) {

157

Milena Vujo sevi cJani ci c

10.4 Stek

/* Nakon zavrsetka citanja etiketa uslov je da stek mora biti prazan. Ako nije, tada znaci da postoje jos neke etikete koje su otvorene ali nisu zatvorene. */ if(vrh_steka(vrh) == NULL) printf("Etikete su pravilno uparene!\n"); else printf("Etikete nisu pravilno uparene\n(etiketa <%s> nije zatvorena)\n", vrh_steka(vrh)); } /* Oslobadjamo stek */ oslobodi_stek(&vrh); return 0; } Primer 10.10 Program formira celobrojni stek i ispisuje sadr zaj steks na standardni izlaz. #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX 100 /* Struktura koja predstavlja cvor liste */ typedef struct cvor { int broj; /* broj */ struct cvor *sledeci; /* pokazivac na sledeci cvor */ } Cvor;

/* Funkcija kreira novi cvor, upisuje u njega broj i vraca njegovu adresu */ Cvor * napravi_cvor(int broj) { Cvor *novi = NULL; if((novi = (Cvor *) malloc(sizeof(Cvor))) == NULL) { fprintf(stderr,"malloc() greska!\n"); exit(1); } novi->broj = broj; novi->sledeci = NULL; return novi; }
158

Milena Vujo sevi cJani ci c

10.4 Stek

/* Funkcija dodaje broj na stek*/ void potisni_na_stek(Cvor **vrh, int broj) { Cvor * novi = napravi_cvor(broj); novi->sledeci = *vrh; *vrh = novi; } /* Funkcija skida broj sa steka */ int skini_sa_steka(Cvor **vrh, int *broj) { Cvor * pomocni; if(*vrh == NULL) return 0; if(broj != NULL) *broj = (*vrh)->broj); pomocni = *vrh; *vrh = (*vrh)->sledeci; free(pomocni); return 1; } main() { Cvor * vrh = NULL; int i, broj; /*Formiranje steka*/ for(i=0; i<100; i++) potisni_na_stek(&vrh, i); /*Ispis elemenata steka: 99, 98 ... 0*/ while(skini_sa_steka(&pocetak, &kraj, &broj)) printf("Broj skinut sa vrha steka je %d\n", broj); /*Nema potrebe da se oslobadja memorija jer je memorija koju je stek zauzimao oslobodjena prilikom ispisa elemenata steka - nakon ispisa stek je ostao prazan!*/

159

Milena Vujo sevi cJani ci c

10.5 Dvostruko povezane liste

10.5

Dvostruko povezane liste


po cetak dvostruko povezane liste kraj liste

NULL

... ...

NULL

Slika 10.4: Dvostruko povezana lista

Dvostruko povezana lista je struktura podataka koja se sastoji od sekvence cvorova. Svaki cvor sadr zi podatak (odred enog tipa) i pokaziva ce na prethodni i slede ci cvor u sekvenci. Za razliku od obi cne liste, u svakom cvoru imamo zapisanu i adresu prethodnog elementa, pa se kroz listu mo zemo kretati i unazad. Poslednji element u listi nema slede ci element: u tom slu caju se njegov pokaziva c na slede ci postavlja na NULL. Sli cno, glava liste nema prethodni element, pa je njen pokaziva c na prethodni postavljen na NULL. Takod e, prazna lista se predstavlja NULL pokaziva cem. Prednost dvostruko povezane liste u odnosu na jednostruko povezanu je u tome sto se mo zemo kretati u oba smera kroz listu. Nedostatak je to sto se veli cina memorije potrebne za cuvanje cvora pove cava za veli cinu jednog pokaziva ca. Primer 10.11 Program demonstrira rad sa dvostruko povezanim listama. Za testiranje narednih funkcija mo ze se koristiti main funkcija iz primera 10.1. #include <stdio.h> #include <stdlib.h> /* Struktura koja predstavlja cvor liste */ typedef struct cvor { int vrednost; /* podatak koji cvor sadrzi */ struct cvor *sledeci; /* pokazivac na sledeci cvor */ struct cvor *prethodni; /* pokazivac na prethodni */ } Cvor;

/* Pomocna funkcija koja kreira cvor. Funkcija vrednost novog cvora inicijalizuje na broj, dok pokazivace na prethodni i sledeci cvor u novom cvoru postavlja na NULL. Funkcija ispisuje poruku o gresci i prekida program u slucaju da malloc() funkcija ne uspe da alocira prostor.
160

Milena Vujo sevi cJani ci c

10.5 Dvostruko povezane liste

Funkcija vraca pokazivac na novokreirani cvor */ Cvor* napravi_cvor(int broj) { Cvor *novi = NULL; if((novi = (Cvor *) malloc(sizeof(Cvor))) == NULL) { fprintf(stderr,"malloc() greska!\n"); exit(1); } novi->vrednost = broj; novi->prethodni = NULL; novi->sledeci = NULL; return novi; } /* Funkcija dodaje novi cvor na pocetak liste. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na novu glavu liste */ Cvor* dodaj_na_pocetak_liste(Cvor *glava, int broj) { Cvor *novi = napravi_cvor(broj); novi->sledeci = glava; if(glava != NULL) glava->prethodni = novi; return novi; } /* Funkcija dodaje novi cvor na kraj liste. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na glavu liste (koji moze biti promenjen u slucaju da je lista inicijalno bila prazna). */ Cvor* dodaj_na_kraj_liste(Cvor *glava, int broj) { Cvor *novi = napravi_cvor(broj); Cvor *tekuci = glava; /* slucaj prazne liste. U tom slucaju je glava nove liste upravo novi cvor. */ if(glava == NULL) return novi; /* Ako lista nije prazna, tada se krecemo duz liste sve dok ne dodjemo do poslednjeg cvora (tj. do cvora ciji pokazivac

161

Milena Vujo sevi cJani ci c

10.5 Dvostruko povezane liste

na sledeci pokazuje na NULL) */ while(tekuci->sledeci != NULL) tekuci = tekuci->sledeci; /* Dodajemo novi element na kraj preusmeravanjem pokazivaca */ tekuci->sledeci = novi; novi->prethodni = tekuci; /* Vracamo glavu liste */ return glava; } /* Funkcija dodaje novi element u sortiranu listu tako da i nova lista ostane sortirana. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na glavu liste (koji moze biti promenjen u slucaju da je novi element dodat na pocetak liste) */ Cvor* dodaj_sortirano(Cvor *glava, int broj) { Cvor *novi = napravi_cvor(broj); Cvor *tekuci = glava; /* u slucaju prazne liste glava nove liste je upravo novi element */ if(glava == NULL) return novi; /* ako je novi element manji ili jednak od glave, tada novi element mora da bude nova glava */ if(glava->vrednost >= novi->vrednost) { novi->sledeci = glava; glava->prethodni = novi; return novi; } /* u slucaju da je glava manja od novog elementa, tada se krecemo kroz listu sve dok se ne dodje do elementa ciji je sledeci element veci ili jednak od novog elementa, ili dok se ne dodje do poslednjeg elementa. */ while(tekuci->sledeci != NULL && tekuci->sledeci->vrednost < novi->vrednost) tekuci = tekuci->sledeci; /* U svakom slucaju novi element dodajemo IZA tekuceg elementa */ novi->sledeci = tekuci->sledeci; novi->prethodni = tekuci;

162

Milena Vujo sevi cJani ci c

10.5 Dvostruko povezane liste

if(tekuci->sledeci != NULL) tekuci->sledeci->prethodni = novi; tekuci->sledeci = novi; /* vracamo vrednost glave */ return glava; } /* Funkcija trazi u listi element cija je vrednost jednaka datom broju. Funkcija vraca pokazivac na cvor liste u kome je sadrzan trazeni broj ili NULL u slucaju da takav element ne postoji u listi */ Cvor* pretrazi_listu(Cvor *glava, int broj) { /* Pretrazivanje se vrsi bez pretpostavke o sortiranosti liste.*/ for(; glava != NULL ; glava = glava->sledeci) if(glava->vrednost == broj) return glava; return NULL; } /* Funkcija brise tekuci element liste, tj. element liste na koji pokazuje pokazivac tekuci. Funkcija vraca pokazivac na glavu liste, koja moze biti promenjena ako je upravo glava obrisani cvor */ Cvor* obrisi_tekuci(Cvor * glava, Cvor * tekuci) { if(tekuci == NULL) return glava; /* Preusmeravamo pokazivace prethodnog i sledeceg ako oni postoje */ if(tekuci->prethodni != NULL) tekuci->prethodni->sledeci = tekuci->sledeci; if(tekuci->sledeci != NULL) tekuci->sledeci->prethodni = tekuci->prethodni; /* Ako je cvor koji brisemo glava, tada moramo da promenimo glavu (sledeci element postaje glava) */ if(tekuci == glava) glava = tekuci->sledeci;

163

Milena Vujo sevi cJani ci c

10.5 Dvostruko povezane liste

/* Brisemo tekuci cvor */ free(tekuci); return glava; } /* Funkcija brise iz liste sve cvorove koji sadrze dati broj. Funkcija vraca pokazivac na glavu liste (koji moze biti promenjen u slucaju da se obrise stara glava) */ Cvor* obrisi_element(Cvor *glava, int broj) { Cvor *tekuci = glava; Cvor *pomocni; /* Pretrazujemo listu pocev od tekuceg elementa trazeci datu vrednost. Ako je pronadjemo, brisemo nadjeni cvor i nastavljamo pretragu od elementa koji sledi nakon upravo obrisanog */ while((tekuci = pretrazi_listu(tekuci, broj)) != NULL) { pomocni = tekuci->sledeci; glava = obrisi_tekuci(glava, tekuci); tekuci = pomocni; } /* vracamo novu glavu */ return glava; } /* Funkcija prikazuje elemente liste pocev od glave ka kraju liste */ void prikazi_listu(Cvor *glava) { putchar([); for(;glava != NULL; glava = glava->sledeci) printf("%d ", glava->vrednost); putchar(]); putchar(\n); } /* Funkcija prikazuje elemente liste u obrnutom poretku */ void prikazi_listu_obrnuto(Cvor * glava) { /* Ako je lista prazna... */ if(glava == NULL)

164

Milena Vujo sevi cJani ci c

10.5 Dvostruko povezane liste

{ printf("[]\n"); return; } /* Prolazimo kroz listu dok ne stignemo do poslednjeg elementa. */ while(glava->sledeci != NULL) glava = glava->sledeci; /* Ispisujemo elemente liste iteracijom u suprotnom smeru */ putchar([); for(;glava != NULL; glava = glava->prethodni) printf("%d ", glava->vrednost); putchar(]); putchar(\n); } /* Funkcija oslobadja dinamicku memoriju zauzetu od strane liste. Funkcija vraca NULL, tj. vrednost koju treba dodeliti pokazivackoj promenljivoj, s obzirom da je sada lista prazna. */ Cvor* oslobodi_listu(Cvor *glava) { Cvor *pomocni; while(glava != NULL) { /* moramo najpre zapamtiti adresu sledeceg elementa, a tek onda osloboditi glavu */ pomocni = glava->sledeci; free(glava); glava = pomocni; } return NULL; }

165

Glava 11

Stabla
Binarno stablo1 je skup cvorova koji su povezani na slede ci na cin: 1. Svaki cvor moze imati levog i desnog SINA (pri tom svaki od sinova moze biti i izostavljen, ili oba). Ka zemo da je dati cvor RODITELJ svojim sinovima. Ako je cvor U roditelj cvoru V, tada pisemo da je U < V . Cvor koji nema ni levog ni desnog sina naziva se LIST. 2. Postoji jedan jedinstveni cvor takav da nema roditelja. Ovaj cvor nazivamo KOREN stabla. 3. U stablu nema ciklusa, tj. ne postoji niz cvorova x1,x2,...,xn, takav da je x1 < x2 < ... < xn < x1. Inace, niz cvorova x1 < x2 < ... < xn nazivamo put u stablu. Ka zemo da je cvor U predak cvora V ako u stablu postoji put od U do V. Specijalno, svaki cvor je predak samom sebi. Lako se mo ze pokazati da je koren predak svih cvorova u stablu. Rastojanje od korena do nekog cvora naziva se visina cvora (koren je visine 0). Maksimalna visina cvora u stablu naziva se visina stabla. Stablo koje nema cvorova naziva se prazno stablo. Za svaki cvor V u stablu mo zemo posmatrati stablo koje se sastoji od svih njegovih potomaka (svih cvorova kojima je on predak). Ovo stablo se naziva podstablo sa datim korenom V. Podstablo sa njegovim levim sinom kao korenom nazivamo levim podstablom cvora V, dok podstablo sa njegovim desnim sinom kao korenom nazivamo desnim podstablom cvora V. Ako je neki od njegovih sinova izostavljen, tada ka zemo da je odgovaraju ce podstablo cvora V prazno stablo. Specijalno, ako je V koren, tada njegovo levo i desno podstablo nazivamo levim i desnim podstablom datog ( citavog) stabla. Stablo se mo ze denisati i rekurzivno na slede ci na cin: 1. Prazno stablo je stablo 2. Ako su data dva stabla t1 i t2, i cvor r, tada je i (t1,r,t2) takodje stablo. t1 je tada levo podstablo, t2 je desno podstablo dok je cvor r koren tako formiranog stabla.
166

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

NULL NULL

NULL NULL

NULL

NULL NULL

Slika 11.1: Binarno stablo

U programiranju se stabla obi cno koriste kao strukture podataka, tako sto svaki cvor sadr zi po jedan podatak odred enog tipa. Na cin raspored ivanja podataka u stablu zavisi od konkretne primene stabla. S obzirom na rekurzivnu prirodu stabla, uobi cajeno je da se u programiranju stabla obrad uju rekurzivno.

11.1

Binarno pretra ziva cko stablo

Posebna vrsta stabla su tzv. binarna pretra ziva cka stabla. Ova stabla imaju osobinu da za svaki cvor vazi slede ce: svi cvorovi njegovog levog podstabla sadr ze podatke sa manjom vredno s cu od vrednosti podatka u tom cvoru, dok svi cvorovi njegovog desnog podstabla sadr ze podatke sa ve com vredno s cu od vrednosti podatka u tom cvoru. Ovakva organizacija omogu cava ekasno pretra zivanje. Primer 11.1 Program demonstrira rad sa binarnim pretrazivackim drvetima ciji su podaci celi brojevi. Drvo sadr zi cele brojeve sortirane po veli cini. Za svaki cvor, levo podstabla sadrzi manje elemente, dok desno podstablo sadrzi vece. #include <stdlib.h>
1 Tekst

preuzet sa sajta http://www.matf.bg.ac.rs/~milan

167

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

17, 12, 21, 15, 5, 14 17

12

21

NULL NULL

15

NULL NULL

NULL

inf ix : 5, 12, 14, 15, 17, 21 14 pref ix : 17, 12, 5, 15, 14, 21 postf ix : 5, 14, 15, 12, 21, 17 NULL NULL

Slika 11.2: Ured eno stablo

#include <stdio.h> /* Struktura koja predstavlja cvor drveta */ typedef struct _cvor { int broj; struct _cvor *l, *d; } cvor; /* Pomocna funkcija za kreiranje cvora. */ cvor* napravi_cvor(int b) { cvor* novi = (cvor*)malloc(sizeof(cvor)); if (novi == NULL) { fprintf(stderr, "Greska prilikom alokacije memorije"); exit(1); }
168

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

novi->broj = b; novi->l = NULL; novi->d = NULL; return novi; } /* Funkcija umece broj b u drvo ciji je koren dat preko pokazivaca koren. Funkcija vraca pokazivac na koren novog drveta */ cvor* ubaci_u_drvo(cvor* koren, int b) { if (koren == NULL) return napravi_cvor(b); if (b < koren->broj) koren->l = ubaci_u_drvo(koren->l, b); else koren->d = ubaci_u_drvo(koren->d, b); return koren; } /* Funkcija proverava da li dati broj postoji u drvetu */ int pronadji(cvor* koren, int b) { if (koren == NULL) return 0; if (koren->broj == b) return 1; if (b < koren->broj) return pronadji(koren->l, b); else return pronadji(koren->d, b); } /* Funkcija ispisuje sve cvorove drveta u infiksnom redosledu */ void ispisi_drvo(cvor* koren) { if (koren != NULL) { ispisi_drvo(koren->l); printf("%d ", koren->broj); ispisi_drvo(koren->d);

169

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

} } /* Funkcija oslobadja memoriju koju je drvo zauzimalo */ void obrisi_drvo(cvor* koren) { if (koren != NULL) { /* Oslobadja se memorija za levo poddrvo */ obrisi_drvo(koren->l); /* Oslobadja se memorija za desno poddrvo */ obrisi_drvo(koren->d); /* Oslobadja se memorija koju zauzima koren */ free(koren); } } /* Funkcija sumira sve vrednosti binarnog stabla */ int suma_cvorova(cvor* koren) { if (koren == NULL) return 0; return suma_cvorova(koren->l) + koren->broj + suma_cvorova(koren->d); } /* Funkcija prebrojava broj cvorova binarnog stabla */ int broj_cvorova(cvor* koren) { if (koren == NULL) return 0; return broj_cvorova(koren->l) + 1 + broj_cvorova(koren->d); } /* Funkcija prebrojava broj listova binarnog stabla */ int broj_listova(cvor* koren) { if (koren == NULL) return 0; if (koren->l == NULL && koren->d == NULL) return 1; return broj_listova(koren->l) +

170

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

broj_listova(koren->d); } /* Funkcija izracunava sumu listova binarnog stabla */ int suma_listova(cvor* koren) { if (koren == NULL) return 0; if (koren->l == NULL && koren->d == NULL) return koren->broj; return suma_listova(koren->l) + suma_listova(koren->d); } /* Funkcija ispisuje sadrzaj listova binarnog stabla */ void ispisi_listove(cvor* koren) { if (koren == NULL) return; ispisi_listove(koren->l); if (koren->l == NULL && koren->d == NULL) printf("%d ", koren->broj); ispisi_listove(koren->d); } /* Funkcija pronalazi maksimalnu vrednost u drvetu Koristi se cinjenica da je ova vrednost smestena u najdesnjem listu */ int max_vrednost(cvor* koren) { if (koren==NULL) return 0; if (koren->d==NULL) return koren->broj; return max_vrednost(koren->d); }

171

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

/* Iterativna funkcija za pronalazenje maksimalne vrednosti. */ int max_vrednost_nerekurzivno(cvor* koren) { if (koren==NULL) return 0; else { cvor* tekuci; for (tekuci=koren; tekuci->d!=NULL; tekuci=tekuci->d) ; return tekuci->broj; } }

#define max(a,b) (((a)>(b))?(a):(b)) /* Funkcija racuna "dubinu" binarnog stabla */ int dubina(cvor* koren) { if (koren==NULL) return 0; else { int dl=dubina(koren->l); int dd=dubina(koren->d); return 1+max(dl,dd); } } /* Program koji testira rad main() { cvor* koren = NULL; koren = ubaci_u_drvo(koren, koren = ubaci_u_drvo(koren, koren = ubaci_u_drvo(koren, koren = ubaci_u_drvo(koren, koren = ubaci_u_drvo(koren, koren = ubaci_u_drvo(koren, koren = ubaci_u_drvo(koren, prethodnih funkcija */

1); 8); 5); 3); 7); 6); 9);

if (pronadji(koren, 3)) printf("Pronadjeno 3\n"); if (pronadji(koren, 2))

172

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

printf("Pronadjeno 2\n"); if (pronadji(koren, 7)) printf("Pronadjeno 7\n"); ispisi_drvo(koren); putchar(\n); printf("Suma cvorova : %d\n", suma_cvorova(koren)); printf("Broj cvorova : %d\n", broj_cvorova(koren)); printf("Broj listova : %d\n", broj_listova(koren)); printf("Suma listova : %d\n", suma_listova(koren)); printf("Dubina drveta : %d\n", dubina(koren)); printf("Maximalna vrednost : %d\n", max_vrednost(koren)); ispisi_listove(koren); obrisi_drvo(koren); } /* Pronadjeno 3 Pronadjeno 7 1 3 5 6 7 8 9 Suma cvorova : 39 Broj cvorova : 7 Broj listova : 3 Suma listova : 18 Dubina drveta : 5 Maximalna vrednost : 9 3 6 9 */ Primer 11.2 Binarno pretra ziva cko drvo - funkcije za izra cunavanje kopije, unije, preseka i razlike dva drveta, funkcija za izbacivanje cvora iz drveta. /* Struktura koja predstavlja cvor drveta */ typedef struct cvor { int vrednost; /* Vrednost koja se cuva */ struct cvor * levi; /* Pokazivac na levo podstablo */ struct cvor * desni; /* Pokazivac na desno podstablo */ } Cvor;

/* NAPOMENA: Prazno stablo se predstavlja NULL pokazivacem. */


173

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

/* Pomocna funkcija za kreiranje cvora. Cvor se kreira dinamicki, funkcijom malloc(). U slucaju greske program se prekida i ispisuje se poruka o gresci. U slucaju uspeha inicijalizuje se vrednost datim brojem, a pokazivaci na podstabla se inicijalizuju na NULL. Funkcija vraca adresu novokreiranog cvora */ Cvor * napravi_cvor (int broj) { /* dinamicki kreiramo cvor */ Cvor * novi = (Cvor *) malloc (sizeof(Cvor)); /* u slucaju greske ... */ if (novi == NULL) { fprintf (stderr,"malloc() greska\n"); exit (1); } /* inicijalizacija */ novi->vrednost = broj; novi->levi = NULL; novi->desni = NULL; /* vracamo adresu novog cvora */ return novi; } /* Funkcija dodaje novi cvor u stablo sa datim korenom. Ukoliko broj vec postoji u stablu, ne radi nista. Cvor se kreira funkcijom napravi_cvor(). Funkcija vraca koren stabla nakon ubacivanja novog cvora. */ Cvor * dodaj_u_stablo (Cvor *koren, int broj) { /* izlaz iz rekurzije: ako je stablo bilo prazno, novi koren je upravo novi cvor */ if (koren == NULL) return napravi_cvor (broj); /* Ako je stablo neprazno, i koren sadrzi manju vrednost od datog broja, broj se umece u desno podstablo, rekurzivnim pozivom */

174

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

if (koren->vrednost < broj) koren->desni = dodaj_u_stablo (koren->desni, broj); /* Ako je stablo neprazno, i koren sadrzi vecu vrednost od datog broja, broj se umece u levo podstablo, rekurzivnim pozivom */ else if (koren->vrednost > broj) koren->levi = dodaj_u_stablo (koren->levi, broj); /* U slucaju da je koren jednak datom broju, tada broj vec postoji u stablu, i ne radimo nista */ /* Vracamo koren stabla */ return koren; } /* Funkcija pretrazuje binarno stablo. Ukoliko pronadje cvor sa vrednoscu koja je jednaka datom broju, vraca adresu tog cvora. U suprotnom vraca NULL */ Cvor * pretrazi_stablo (Cvor * koren, int broj) { /* Izlaz iz rekurzije: ako je stablo prazno, tada trazeni broj nije u stablu */ if (koren == NULL) return NULL; /* Ako je stablo neprazno, tada se pretrazivanje nastavlja u levom ili desnom podstablu, u zavisnosti od toga da li je trazeni broj respektivno manji ili veci od vrednosti korena. Ukoliko je pak trazeni broj jednak korenu, tada se vraca adresa korena. */ if (koren->vrednost < broj) return pretrazi_stablo (koren->desni, broj); else if (koren->vrednost > broj) return pretrazi_stablo (koren->levi, broj); else return koren; } /* Funkcija vraca adresu cvora sa najmanjom vrednoscu u stablu, ili NULL ako je stablo prazno */

175

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

Cvor * pronadji_najmanji (Cvor * koren) { /* Slucaj praznog stabla */ if (koren == NULL) return NULL; /* Izlaz iz rekurzije: ako koren nema levog sina, tada je upravo koren po vrednosti najmanji cvor.*/ if (koren->levi == NULL) return koren; /* U suprotnom je najmanja vrednost u stablu upravo najmanja vrednost u levom podstablu, pa se pretraga nastavlja rekurzivno u levom podstablu. */ else return pronadji_najmanji (koren->levi); } /* Funkcija vraca adresu cvora sa najvecom vrednoscu u stablu, ili NULL ako je stablo prazno */ Cvor * pronadji_najveci (Cvor * koren) { /* Slucaj praznog stabla */ if (koren == NULL) return NULL; /* Izlaz iz rekurzije: ako koren nema desnog sina, tada je upravo koren po vrednosti najveci cvor. */ if (koren->desni == NULL) return koren; /* U suprotnom je najveca vrednost u stablu upravo najveca vrednost u desnom podstablu, pa se pretraga nastavlja rekurzivno u desnom podstablu. */ else return pronadji_najveci (koren->desni); } /* Funkcija brise cvor sa datom vrednoscu iz stabla, ukoliko postoji. U suprotnom ne radi nista. Funkcija vraca koren stabla (koji moze biti promenjen nakon brisanja) */ Cvor * obrisi_element (Cvor * koren, int broj)

176

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

{ Cvor * pomocni=NULL; /* Izlaz iz rekurzije: ako je stablo prazno, ono ostaje prazno i nakon brisanja */ if (koren == NULL) return NULL; /* Ako je vrednost broja veca od vrednosti korena, tada se broj eventualno nalazi u desnom podstablu, pa treba rekurzivno primeniti postupak na desno podstablo. Koren ovako modifikovanog stabla je nepromenjen, pa vracamo stari koren */ if (koren->vrednost < broj) { koren->desni = obrisi_element (koren->desni, broj); return koren; } /* Ako je vrednost broja manja od vrednosti korena, tada se broj eventualno nalazi u levom podstablu, pa treba rekurzivno primeniti postupak na levo podstablo. Koren ovako modifikovanog stabla je nepromenjen, pa vracamo stari koren */ if (koren->vrednost > broj) { koren->levi = obrisi_element (koren->levi, broj); return koren; } /* Slede podslucajevi vezani za slucaj kada je vrednost korena jednaka broju koji se brise (tj. slucaj kada treba obrisati koren) */ /* Ako koren nema sinova, tada se on prosto brise, i rezultat je prazno stablo (vracamo NULL) */ if (koren->levi == NULL && koren->desni == NULL) { free (koren); return NULL; } /* Ako koren ima samo levog sina, tada se brisanje

177

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

vrsi tako sto obrisemo koren, a novi koren postaje levi sin */ if (koren->levi != NULL && koren->desni == NULL) { pomocni = koren->levi; free (koren); return pomocni; } /* Ako koren ima samo desnog sina, tada se brisanje vrsi tako sto obrisemo koren, a novi koren postaje desni sin */ if (koren->desni != NULL && koren->levi == NULL) { pomocni = koren->desni; free (koren); return pomocni; }

/* Slucaj kada koren ima oba sina. U tom slucaju se brisanje vrsi na sledeci nacin: najpre se potrazi sledbenik korena (u smislu poretka) u stablu. To je upravo po vrednosti najmanji cvor u desnom podstablu. On se moze pronaci npr. funkcijom pronadji_najmanji(). Nakon toga se u koren smesti vrednost tog cvora, a u taj cvor se smesti vrednost korena (tj. broj koji se brise). Onda se prosto rekurzivno pozove funkcija za brisanje na desno podstablo. S obzirom da u njemu treba obrisati najmanji element, a on definitivno ima najvise jednog potomka, jasno je da ce to brisanje biti obavljeno na jedan od nacina koji je gore opisan. */ pomocni = pronadji_najmanji (koren->desni); koren->vrednost = pomocni->vrednost; pomocni->vrednost = broj; koren->desni = obrisi_element (koren->desni, broj); return koren; } /* Funkcija prikazuje stablo s leva u desno (tj. prikazuje elemente u rastucem poretku) */ void prikazi_stablo (Cvor * koren) { /* izlaz iz rekurzije */

178

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

if(koren == NULL) return; prikazi_stablo (koren->levi); printf("%d ", koren->vrednost); prikazi_stablo (koren->desni); } /* Funkcija oslobadja prostor koji je alociran za cvorove stabla. Funkcija vraca NULL, zato sto je nakon oslobadjanja stablo prazno. */ Cvor * oslobodi_stablo (Cvor *koren) { /* Izlaz iz rekurzije */ if(koren == NULL) return NULL; koren->levi = oslobodi_stablo (koren->levi); koren->desni = oslobodi_stablo (koren->desni); free(koren); return NULL; } /* Funkcija kreira novo stablo identicno stablu koje je dato korenom. Funkcija vraca pokazivac na koren novog stabla. */ Cvor * kopiraj_stablo (Cvor * koren) { Cvor * duplikat = NULL; /* Izlaz iz rekurzije: ako je stablo prazno, vracamo NULL */ if(koren == NULL) return NULL; /* Dupliramo koren stabla i postavljamo ga da bude koren novog stabla */ duplikat = napravi_cvor (koren->vrednost); /* Rekurzivno dupliramo levo podstablo i njegovu adresu cuvamo u pokazivacu na levo podstablo korena duplikata. */

179

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

duplikat->levi = kopiraj_stablo (koren->levi); /* Rekurzivno dupliramo desno podstablo i njegovu adresu cuvamo u pokazivacu na desno podstablo korena duplikata. */ duplikat->desni = kopiraj_stablo (koren->desni); /* Vracamo adresu korena duplikata */ return duplikat; } /* Funkcija modifikuje stablo dato korenom koren_1 tako da sadrzi i sve elemente drugog stabla datog korenom koren_2 (drugim recima funkcija kreira uniju dva stabla, i rezultat se smesta u prvo stablo). Funkcija vraca pokazivac na koren tako modifikovanog prvog stabla. */ Cvor * kreiraj_uniju (Cvor * koren_1, Cvor * koren_2) { /* Ako je drugo stablo neprazno */ if(koren_2 != NULL) { /* dodajemo koren drugog stabla u prvo stablo */ koren_1 = dodaj_u_stablo (koren_1, koren_2->vrednost); /* rekurzivno racunamo uniju levog i desnog podstabla drugog stabla sa prvim stablom */ koren_1 = kreiraj_uniju (koren_1, koren_2->levi); koren_1 = kreiraj_uniju (koren_1, koren_2->desni); } /* vracamo pokazivac na modifikovano prvo stablo */ return koren_1; } /* Funkcija modifikuje stablo dato korenom koren_1 tako da sadrzi samo one elemente koji su i elementi stabla datog korenom koren_2 (drugim recima funkcija kreira presek dva stabla, i rezultat se smesta u prvo stablo). Funkcija vraca pokazivac na koren tako modifikovanog prvog stabla. */ Cvor * kreiraj_presek (Cvor * koren_1, Cvor * koren_2)

180

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

{ /* Ako je prvo stablo prazno, tada je i rezultat prazno stablo */ if(koren_1 == NULL) return NULL; /* Kreiramo presek levog i desnog podstabla sa drugim stablom, tj. iz levog i desnog podstabla prvog stabla brisemo sve one elemente koji ne postoje u drugom stablu */ koren_1->levi = kreiraj_presek (koren_1->levi, koren_2); koren_1->desni = kreiraj_presek (koren_1->desni, koren_2); /* Ako se koren prvog stabla ne nalazi u drugom stablu tada ga uklanjamo iz prvog stabla */ if(pretrazi_stablo (koren_2, koren_1->vrednost) == NULL) koren_1 = obrisi_element (koren_1, koren_1->vrednost); /* Vracamo koren tako modifikovanog prvog stabla */ return koren_1; } /* Funkcija modifikuje stablo dato korenom koren_1 tako da sadrzi samo one elemente koji nisu i elementi stabla datog korenom koren_2 (drugim recima funkcija kreira razliku dva stabla, i rezultat se smesta u prvo stablo). Funkcija vraca pokazivac na koren tako modifikovanog prvog stabla. */ Cvor * kreiraj_razliku (Cvor * koren_1, Cvor * koren_2) { /* Ako je prvo stablo prazno, tada je i rezultat prazno stablo */ if(koren_1 == NULL) return NULL; /* Kreiramo razliku levog i desnog podstabla sa drugim stablom, tj. iz levog i desnog podstabla prvog stabla brisemo sve one elemente koji postoje i u drugom stablu */ koren_1->levi = kreiraj_razliku (koren_1->levi, koren_2); koren_1->desni = kreiraj_razliku (koren_1->desni, koren_2);

181

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

/* Ako se koren prvog stabla nalazi i u drugom stablu tada ga uklanjamo iz prvog stabla */ if(pretrazi_stablo (koren_2, koren_1->vrednost) != NULL) koren_1 = obrisi_element (koren_1, koren_1->vrednost); /* Vracamo koren tako modifikovanog prvog stabla */ return koren_1; } /* test program */ int main() { Cvor * koren = NULL, * koren_2 = NULL; Cvor * pomocni = NULL; int broj;

/* Testiranje dodavanja u stablo */ printf("-------------------------------------------------------\n"); printf("---------- Testiranje dodavanja u stablo --------------\n"); do { printf("-------------------------------------------------------\n"); printf("Prikaz trenutnog sadrzaja stabla:\n"); prikazi_stablo (koren); putchar(\n); printf("-------------------------------------------------------\n"); printf("Dodati element u stablo (ctrl-D za kraj unosa):\n"); } while(scanf("%d", &broj) > 0 && (koren = dodaj_u_stablo (koren, broj)) ); printf("-------------------------------------------------------\n"); putchar(\n); /* Testiranje pretrage elementa */ printf("-------------------------------------------------------\n"); printf("---------------- Testiranje pretrage ------------------\n"); printf("-------------------------------------------------------\n"); printf("Prikaz trenutnog sadrzaja stabla:\n"); prikazi_stablo (koren); putchar(\n); printf("-------------------------------------------------------\n");

182

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

printf("Uneti broj koji se trazi: "); scanf("%d", &broj); if((pomocni = pretrazi_stablo (koren, broj)) == NULL) printf("Trazeni broj nije u stablu\n"); else printf("Trazeni element %d je u stablu\n", pomocni->vrednost); printf("-------------------------------------------------------\n"); putchar(\n);

/* Testiranje brisanja elemenata */ printf("-------------------------------------------------------\n"); printf("--------------- Testiranje brisanja -------------------\n"); printf("-------------------------------------------------------\n"); printf("Prikaz trenutnog sadrzaja stabla:\n"); prikazi_stablo (koren); putchar(\n); printf("-------------------------------------------------------\n"); printf("Uneti broj koji se brise: "); scanf("%d", &broj); koren = obrisi_element (koren, broj); printf("-------------------------------------------------------\n"); printf("Stablo nakon izbacivanja:\n"); prikazi_stablo (koren); putchar(\n); printf("-------------------------------------------------------\n"); putchar(\n);

/* Testiranje dupliranja, unije, preseka i razlike */ printf("-------------------------------------------------------\n"); printf("----- Testiranje dupliranja i skupovnih operacija -----\n"); do { printf("-------------------------------------------------------\n"); printf("Prikaz trenutnog sadrzaja 2. stabla:\n"); prikazi_stablo (koren_2); putchar(\n); printf("-------------------------------------------------------\n"); printf("Dodati element u 2. stablo (ctrl-D za kraj unosa):\n"); } while(scanf("%d", &broj) > 0 && (koren_2 = dodaj_u_stablo (koren_2, broj)) );

183

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

printf("-------------------------------------------------------\n"); putchar(\n);

pomocni = kopiraj_stablo(koren); pomocni = kreiraj_uniju(pomocni, koren_2); printf("-------------------------------------------------------\n"); printf("Prikaz 1. stabla:\n"); prikazi_stablo (koren); putchar(\n); printf("-------------------------------------------------------\n"); printf("Prikaz 2. stabla:\n"); prikazi_stablo (koren_2); putchar(\n); printf("-------------------------------------------------------\n"); printf("Unija ova dva stabla:\n"); prikazi_stablo (pomocni); putchar(\n); printf("-------------------------------------------------------\n"); pomocni = oslobodi_stablo(pomocni); pomocni = kopiraj_stablo(koren); pomocni = kreiraj_presek(pomocni, koren_2); printf("Presek ova dva stabla:\n"); prikazi_stablo (pomocni); putchar(\n); printf("-------------------------------------------------------\n"); pomocni = oslobodi_stablo(pomocni); pomocni = kopiraj_stablo(koren); pomocni = kreiraj_razliku(pomocni, koren_2); printf("Razlika ova dva stabla:\n"); prikazi_stablo (pomocni); putchar(\n); printf("-------------------------------------------------------\n"); pomocni = oslobodi_stablo (pomocni); koren_2 = oslobodi_stablo (koren_2); koren = oslobodi_stablo (koren);

184

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

return 0; } Primer 11.3 Primer demonstrira sortiranje niza pomo cu binarnog stabla. Elementi niza se redom prvo smeste u binarno stablo pretrage, a zatim se elementi pokupe iz stabla obilaze ci ga sa leva u desno (tj. u rastu cem poretku). Vremenska slo zenost ovakvog algoritma je u ekvivalentna quick_sort()-u. Prostorna slozenost je nesto ve ca, jer sortiranje nije u mestu, ve c se koristi pomo cna struktura binarno stablo. #include <stdio.h> #include <stdlib.h> #define MAX 1000 /* Struktura koja predstavlja cvor stabla */ typedef struct cvor { int vrednost; struct cvor * levi; struct cvor * desni; } Cvor; /* Funkcija kreira novi cvor */ Cvor * napravi_cvor (int broj) { Cvor * novi = (Cvor *) malloc (sizeof(Cvor)); if (novi == NULL) { fprintf (stderr,"malloc() greska\n"); exit (1); } novi->vrednost = broj; novi->levi = NULL; novi->desni = NULL; return novi; }

185

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

/* Funkcija dodaje novu vrednost u stablo. Dozvoljava ponavljanje vrednosti u stablu -- levo se dodaju manje vrednosti, a desno vece ili jednake. */ Cvor * dodaj_u_stablo (Cvor * koren, int broj) { if (koren == NULL) return napravi_cvor (broj);

if (koren->vrednost <= broj) koren->desni = dodaj_u_stablo (koren->desni, broj); else if (koren->vrednost > broj) koren->levi = dodaj_u_stablo (koren->levi, broj); return koren; } /* Funkcija oslobadja stablo */ void oslobodi_stablo (Cvor *koren) { if(koren == NULL) return; oslobodi_stablo (koren->levi); oslobodi_stablo (koren->desni); free(koren); } /* Funkcija obilazi stablo sa leva u desno i vrednosti cvorova smesta u niz. Funkcija vraca broj vrednosti koje su smestene u niz. */ int kreiraj_niz(Cvor * koren, int a[]) { int r, s; if(koren == NULL) return 0; r = kreiraj_niz(koren->levi, a);

186

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

a[r] = koren->vrednost; s = kreiraj_niz(koren->desni, a + r + 1); return r + s + 1; } /* Funkcija sortira niz tako sto najpre elemente niza smesti u stablo, a zatim kreira novi niz prolazeci kroz stablo sa leva u desno. */ void sortiraj(int a[], int n) { int i; Cvor * koren = NULL; for(i = 0; i < n; i++) koren = dodaj_u_stablo(koren, a[i]); kreiraj_niz(koren, a); oslobodi_stablo(koren); } /* Test program */ int main() { int a[MAX]; int n, i; printf("Uneti dimenziju niza manju od %d: ", MAX); scanf("%d", &n); printf("Uneti elemente niza: "); for(i = 0; i < n; i++) scanf("%d", &a[i]); printf("Niz pre sortiranja:\n"); for(i = 0; i < n; i++) printf("%d ", a[i]); printf("\n");

187

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

sortiraj(a, n); printf("Niz nakon sortiranja:\n"); for(i = 0; i < n; i++) printf("%d ", a[i]); printf("\n");

return 0; } Primer 11.4 Program sa ulaza cita tekst i ispisuje broj pojavljivanja svake od re ci koje su se javljale u tekstu. Radi pobolj sanja ekasnosti, prilikom brojanja re ci koristi se struktura podataka pogodna za leksikografsku pretragu u ovom slu caju binarno pretra ziva cko drvo. #include #include #include #include <stdio.h> <stdlib.h> <string.h> <ctype.h>

#define MAX 1024 typedef struct cvor { char rec[MAX]; /* rec */ int brojac; /* broj pojavljivanja reci */ struct cvor * levi; struct cvor * desni; } Cvor; /* Pomocna funkcija za kreiranje cvora. Funkcija vraca adresu novokreiranog cvora */ Cvor * napravi_cvor (char * rec) { /* dinamicki kreiramo cvor */ Cvor * novi = (Cvor *) malloc (sizeof(Cvor)); /* u slucaju greske ... */ if (novi == NULL) { fprintf (stderr,"malloc() greska\n"); exit (1); } /* inicijalizacija */
188

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

strcpy(novi->rec, rec); novi->brojac = 1; novi->levi = NULL; novi->desni = NULL; /* vracamo adresu novog cvora */ return novi; } /* Funkcija dodaje novi cvor u stablo sa datim korenom. Ukoliko rec vec postoji u stablu, uvecava dati brojac. Cvor se kreira funkcijom napravi_cvor(). Funkcija vraca koren stabla nakon ubacivanja novog cvora. */ Cvor * dodaj_u_stablo (Cvor * koren, char *rec) { /* izlaz iz rekurzije: ako je stablo bilo prazno, novi koren je upravo novi cvor */ if (koren == NULL) return napravi_cvor (rec); /* Ako je stablo neprazno, i koren sadrzi leksikografski manju rec od date reci, broj se umece u desno podstablo, rekurzivnim pozivom */ if (strcmp(koren->rec, rec) < 0) koren->desni = dodaj_u_stablo (koren->desni, rec); /* Ako je stablo neprazno, i koren sadrzi vecu vrednost od datog broja, broj se umece u levo podstablo, rekurzivnim pozivom */ else if (strcmp(koren->rec, rec) > 0) koren->levi = dodaj_u_stablo (koren->levi, rec); else /* Ako je data rec vec u stablu, samo uvecavamo brojac. */ koren->brojac++; /* Vracamo koren stabla */ return koren; } /* Funkcija pronalazi najfrekventniju rec, tj. cvor ciji brojac ima najvecu vrednost. Funkcija vraca NULL, ako je stablo prazno, odnosno adresu cvora koji sadrzi najfrekventniju rec u suprotnom. */

189

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

Cvor * nadji_najfrekventniju (Cvor * koren) { Cvor *max, *max_levi, *max_desni; /* Izlaz iz rekurzije */ if (koren == NULL) return NULL; /* Odredjujemo najfrekventnije reci u levom i desnom podstablu */ max_levi = nadji_najfrekventniju(koren->levi); max_desni = nadji_najfrekventniju(koren->desni); /* Odredjujemo MAX(koren, max_levi, max_desni) */ max = koren; if(max_levi != NULL && max_levi->brojac > max->brojac) max = max_levi; if(max_desni != NULL && max_desni->brojac > max->brojac) max = max_desni; /* Vracamo adresu cvora sa najvecim brojacem */ return max; } /* Prikazuje reci u leksikografskom poretku, kao i broj pojava svake od reci */ void prikazi_stablo(Cvor *koren) { if(koren == NULL) return; prikazi_stablo(koren->levi); printf("%s: %d\n", koren->rec, koren->brojac); prikazi_stablo(koren->desni); }

/* Funkcija oslobadja prostor koji je alociran za cvorove stabla. Funkcija vraca NULL, zato sto je nakon oslobadjanja stablo prazno. */ void oslobodi_stablo (Cvor *koren) { /* Izlaz iz rekurzije */

190

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

if(koren == NULL) return; oslobodi_stablo (koren->levi); oslobodi_stablo (koren->desni); free(koren); } /* Funkcija ucitava sledecu rec iz fajla i upisuje je u niz na koji pokazuje rec, maksimalne duzine max. Funkcija vraca EOF ako nema vise reci, 0 u suprotnom. Rec je niz alfabetskih karaktera.*/ int sledeca_rec(FILE *f, char * rec, int max) { int c; int i = 0; /* Dokle god ima mesta za jos jedan karakter u stringu, i dokle god nismo stigli do kraja fajla... */ while(i < max - 1 && (c = fgetc(f)) != EOF) { /* Ako je slovo, ubacujemo ga u rec */ if(isalpha(c)) rec[i++] = tolower(c); /* U suprotnom, ako smo procitali bar jedno slovo prethodno, onda imamo rec. Inace idemo na sledecu iteraciju */ else if(i > 0) break; } /* Zatvaramo string */ rec[i] = \0; /* Vracamo 0 ako imamo rec, EOF u suprotnom */ return i > 0 ? 0 : EOF; }

/* test program */ int main(int argc, char **argv) {

191

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

Cvor *koren = NULL, *max; FILE *f; char rec[MAX]; /* Proveravamo da li je navedeno ime datoteke */ if(argc < 2) { fprintf(stderr, "Morate uneti ime datoteke sa tekstom!\n"); exit(0); } /* Otvaramo datoteku */ if((f = fopen(argv[1], "r")) == NULL) { fprintf(stderr, "fopen() greska\n"); exit(1); } /* Ucitavamo reci iz datoteke. Rec je niz alfabetskih karaktera. */ while(sledeca_rec(f, rec, MAX) != EOF) { koren = dodaj_u_stablo(koren, rec); } /* Zatvaramo datoteku */ fclose(f); /* Prikazujemo sve reci i brojeve njihovih pojavljivanja */ prikazi_stablo(koren); /* Pronalazimo najfrekventniju rec */ if((max = nadji_najfrekventniju(koren)) == NULL) printf("U tekstu nema reci!\n"); else printf("Najcesca rec %s (pojavljuje se %d puta)\n", max->rec, max->brojac); /* Oslobadjamo stablo */ oslobodi_stablo(koren); return 0; } Primer 11.5 Mapa je apstraktna struktura podataka koja u sebi sadrzi parove oblika (kljuc, vrednost). Pri tom su i klju c i vrednost unapred odred enog (ne obavezno istog) tipa (na primer, klju c nam mo ze biti string koji predstavlja ime studenta, a vrednost je npr. broj koji predstavlja njegov prosek). Operacije koje mapa mora
192

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

ekasno da podr zava su: dodavanje para (kljuc, vrednost) u mapu brisanje para (kljuc, vrednost) iz mape pronalazenje vrednosti za dati klju c promena vrednosti za dati klju c Jedan od naj ce s cih na cina implementacije mape je preko binarnog stabla pretrage. Svaki cvor ovog stabla sadr za ce odgovaraju ci par (kljuc, vrednost), tj. svaki cvor ce sadr zati dva podatka. Pri tom ce poredak u stablu biti odred en poretkom koji je denisan nad klju cevima. Ovim se posti ze da se pretraga po klju cu mo ze obaviti na uobi cajen na cin, kao i sve ostale navedene operacije. Jasno je da ovako implementirana mapa ne ce ekasno podr zavati operaciju pretrage po vrednosti (npr. na ci sve klju ceve koji imaju datu vrednost), zato sto poredak u stablu nije ni na koji na cin u vezi sa poretkom med u vrednostima. Med utim, u mapi se naj ce s ce i ne zahteva takva operacija. Sledi primer koji demonstrira implementaciju mapa pomo cu binarnog stabla. #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX 1024 /* Struktura koja predstavlja cvor stabla */ typedef struct cvor { char naziv[MAX]; int cena; struct cvor * levi; struct cvor * desni; } Cvor; /* Funkcija kreira novi cvor i vraca njegovu adresu */ Cvor * napravi_cvor (char * naziv, int cena) { /* dinamicki kreiramo cvor */ Cvor * novi = (Cvor *) malloc (sizeof(Cvor)); /* u slucaju greske ... */ if (novi == NULL) { fprintf (stderr,"malloc() greska\n"); exit (1); }
193

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

/* inicijalizacija */ strcpy(novi->naziv, naziv); novi->cena = cena; novi->levi = NULL; novi->desni = NULL; /* vracamo adresu novog cvora */ return novi; } /* Funkcija dodaje novi cvor u stablo sa datim korenom. U cvor se upisuje vrednost (naziv, cena). Ukoliko naziv vec postoji u stablu, tada se azurira njegova cena. Cvor se kreira funkcijom napravi_cvor(). Funkcija vraca koren stabla nakon ubacivanja novog cvora. */ Cvor * dodaj_u_stablo (Cvor * koren, char * naziv, int cena) { /* izlaz iz rekurzije: ako je stablo bilo prazno, novi koren je upravo novi cvor */ if (koren == NULL) return napravi_cvor (naziv, cena); /* Ako je stablo neprazno, i koren sadrzi naziv koji je leksikografski manji od datog naziva, vrednost se umece u desno podstablo, rekurzivnim pozivom */ if (strcmp(koren->naziv, naziv) < 0) koren->desni = dodaj_u_stablo (koren->desni, naziv, cena); /* Ako je stablo neprazno, i koren sadrzi naziv koji je leksikografski veci od datog naziva, vrednost se umece u levo podstablo, rekurzivnim pozivom */ else if (strcmp(koren->naziv, naziv) > 0) koren->levi = dodaj_u_stablo (koren->levi, naziv, cena); /* Ako je naziv korena jednak nazivu koja se umece, tada se samo azurira cena. */ else koren->cena = cena; /* Vracamo koren stabla */ return koren; }

194

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

/* Funkcija pretrazuje binarno stablo. Ukoliko pronadje cvor sa vrednoscu naziva koja je jednaka datom nazivu, vraca adresu tog cvora. U suprotnom vraca NULL */ Cvor * pretrazi_stablo (Cvor * koren, char * naziv) { /* Izlaz iz rekurzije: ako je stablo prazno, tada trazeni broj nije u stablu */ if (koren == NULL) return NULL; /* Ako je stablo neprazno, tada se pretrazivanje nastavlja u levom ili desnom podstablu, u zavisnosti od toga da li je trazeni naziv respektivno manji ili veci od vrednosti naziva korena. Ukoliko je pak trazeni naziv jednak nazivu korena, tada se vraca adresa korena. */ if (strcmp(koren->naziv, naziv) < 0) return pretrazi_stablo (koren->desni, naziv); else if (strcmp(koren->naziv, naziv) > 0) return pretrazi_stablo (koren->levi, naziv); else return koren; } /* cvor liste */ typedef struct cvor_liste { char naziv[MAX]; int cena; struct cvor_liste * sledeci; } Cvor_liste; /* Pomocna funkcija koja kreira cvor liste */ Cvor_liste * napravi_cvor_liste(char * naziv, int cena) { Cvor_liste * novi; if((novi = malloc(sizeof(Cvor_liste))) == NULL) { fprintf(stderr, "malloc() greska\n"); exit(1);

195

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

} strcpy(novi->naziv, naziv); novi->cena = cena; novi->sledeci = NULL; return novi; } /* Funkcija pronalazi sve nazive cija je cena manja ili jednaka od date, i formira listu koja sadrzi nadjene parove (naziv, cena) u leksikografskom poretku po nazivima. Prilikom pocetnog poziva, treci argument treba da bude NULL. Funkcija obilazi stablo sa desna u levo, kako bi se prilikom dodavanja na pocetak liste poslednji dodao onaj koji je leksikografski najmanji (tj. on ce biti na pocetku). */ Cvor_liste * pronadji_manje (Cvor * koren, int cena, Cvor_liste * glava) { if(koren == NULL) return glava; /* Dodajemo na pocetak liste sve cvorove desnog podstabla cija je cena manja od date. */ glava = pronadji_manje(koren->desni, cena, glava); /* Dodajemo koren u listu, ako mu je cena manja od date */ if(koren->cena <= cena) { Cvor_liste * novi = napravi_cvor_liste(koren->naziv, koren->cena); novi->sledeci = glava; glava = novi; } /* Dodajemo na pocetak liste sve cvorove levog podstabla cija je cena manja od date */ glava = pronadji_manje(koren->levi, cena, glava); /* Vracamo glavu liste nakon svih modifikacija */ return glava; } /* Funkcija prikazuje listu */ void prikazi_listu(Cvor_liste * glava) { if(glava == NULL)

196

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

return; printf("%s = %d\n", glava->naziv, glava->cena); prikazi_listu(glava->sledeci); }

/* Funkcija oslobadja listu */ Cvor_liste * oslobodi_listu(Cvor_liste * glava) { if(glava == NULL) return NULL; glava->sledeci = oslobodi_listu(glava->sledeci); free(glava); return NULL; } /* Funkcija oslobadja stablo. */ Cvor * oslobodi_stablo (Cvor *koren) { if(koren == NULL) return NULL; koren->levi = oslobodi_stablo (koren->levi); koren->desni = oslobodi_stablo (koren->desni); free(koren); return NULL; } /* test program */ int main(int argc, char ** argv) { Cvor * koren = NULL, * pomocni; Cvor_liste * glava = NULL; FILE *f; char naziv[MAX]; int cena; /* Proveravamo da li je navedeno ime datoteke */

197

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

if(argc < 2) { fprintf(stderr, "Morate navesti ime datoteke!\n"); exit(0); } /* Otvaramo datoteku */ if((f = fopen(argv[1], "r")) == NULL) { fprintf(stderr, "fopen() greska\n"); exit(1); } /* Ubacujemo proizvode u stablo */ while(fscanf(f, "%s%d", naziv, &cena) == 2) { koren = dodaj_u_stablo(koren, naziv, cena); } fclose(f); /* Testiranje pretrage po nazivu (efikasna operacija) */ printf("Uneti naziv proizvoda koji vas zanima: "); scanf("%s", naziv); if((pomocni = pretrazi_stablo(koren, naziv)) == NULL) printf("Trazeni proizvod ne postoji\n"); else printf("Cena trazenog proizvoda je: %d\n", pomocni->cena); /* Testiranje pretrage po ceni (neefikasno) */ printf("Unesite maksimalnu cenu: "); scanf("%d", &cena); glava = pronadji_manje(koren, cena, NULL); prikazi_listu(glava); /* Oslobadjanje memorije */ glava = oslobodi_listu(glava); koren = oslobodi_stablo(koren); return 0; } Primer 11.6 Program broji pojavljivanje svake etikete u tekstu i ispisuje etikete i njihove frekvencije u opadaju cem poretku po frekvencijama. Radi pobolj sanja ekasnosti, prilikom brojanja pojavljivanja etiketa koristi se
198

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

struktura podataka pogodna za leksikografsku pretragu - u ovom slu caju binarno pretra ziva cko drvo. Na kraju rada, cvorovi drveta se presortiraju na osnovu broja pojavljivanja. #include #include #include #include <stdio.h> <string.h> <stdlib.h> <ctype.h>

/* Makimalna duzina imena etikete */ #define MAX_ETIKETA 32 /* Definisemo stanja prilikom citanja html datoteke */ #define VAN_ETIKETE 1 #define U_ETIKETI 2 /* Struktura koja predstavlja cvor stabla */ typedef struct cvor { char etiketa[MAX_ETIKETA]; /* Ime etikete */ int brojac; /* Brojac pojavljivanja etkete u tekstu */ struct cvor *levi; /* Pokazivaci na levo */ struct cvor *desni; /* i desno podstablo */ } Cvor;

/* Funkcija dodaje ime html etikete u binarno stablo, u rastucem leksikografskom poretku. Funkcija vraca koren stabla nakon modifikacije. U slucaju da je etiketa sa istim imenom vec u stablu, uvecava se brojac pojavljivanja. */ Cvor * dodaj_leksikografski(Cvor *koren, char *etiketa) { int cmp; /* Izlaz iz rekurzije */ if(koren == NULL) { if((koren = malloc(sizeof(Cvor))) == NULL) { fprintf(stderr,"malloc() greska\n"); exit(1); } /* Ime etikete se kopira u novi cvor, a brojac se inicijalizuje na 1 (prvo pojavljivanje) */
199

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

strcpy(koren->etiketa, etiketa); koren->brojac = 1; koren->levi = NULL; koren->desni = NULL; return koren; } /* Rekurzivni pozivi */ if((cmp = strcmp(koren->etiketa,etiketa)) < 0) koren->desni = dodaj_leksikografski(koren->desni, etiketa); else if(cmp > 0) koren->levi = dodaj_leksikografski(koren->levi, etiketa); else koren->brojac++; /* uvecanje brojaca za vec prisutne etikete */ return koren; } /* Funkcija cita ulazni fajl i iz njega izdvaja sve otvorene html etikete (npr. <html>, <br> itd, ali ne i </html>, </body> itd.) i njihova imena (bez znakova (< i > kao i bez eventualnih atributa) ubacuje u stablo u leksikografskom poretku. Tom prilikom ce se prebrojati i pojavljivanja svake od etiketa. Funkcija vraca pokazivac na koren kreiranog stabla. */ Cvor * ucitaj(FILE *f) { Cvor * koren = NULL; int c; int stanje = VAN_ETIKETE; int i; char etiketa[MAX_ETIKETA]; /* NAPOMENA: prilikom izdvajanja etiketa pretpostavlja se da je html datoteka sintaksno ispravna, kao i da nakon znaka < nema belina. Zato se otvorene etikete mogu lako prepoznati tako sto pronadjemo znak < i nakon toga izdvojimo sva slova koja slede. Ako postoji neprazan niz slova nakon znaka <, tada je to upravo ime etikete. Ako ne, tada je verovatno u pitanju zatvorena etiketa, npr. </html>, pa je ignorisemo. */ while((c = fgetc(f)) != EOF) { switch(stanje)

200

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

{ /* u ovom stanju trazimo prvu sledecu pojavu znaka < */ case VAN_ETIKETE: if(c == <) { stanje = U_ETIKETI; /* sada smo u etiketi */ i = 0; } break; /* u ovom stanju citamo slova koja slede, i nakon toga ubacujemo procitanu etiketu u stablo */ case U_ETIKETI: /* Ako je slovo, i nismo prekoracili duzinu niza dodajemo slovo u niz */ if(isalpha(c) && i < MAX_ETIKETA - 1) etiketa[i++] = c; /* u suprotnom se vracamo u stanje VAN_ETIKETE */ else { stanje = VAN_ETIKETE; /* Ako je niz slova nakon < bio neprazan... */ if(i > 0) { etiketa[i]=\0; /* ubacujemo procitanu etiketu u stablo */ koren = dodaj_leksikografski(koren, etiketa); } } break; } } return koren; } /* Funkcija dodaje u stablo etiketu ciji je broj pojavljivanja poznat (broj), i to u opadajucem poretku po broju pojavljivanja. Funkcija vraca pokazivac na koren tako modifikovanog stabla */ Cvor * dodaj_po_broju(Cvor * koren, char * etiketa, int broj) { /* Izlaz iz rekurzije */ if(koren == NULL)

201

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

{ if((koren = malloc(sizeof(Cvor))) == NULL) { fprintf(stderr,"malloc() greska\n"); exit(1); } /* Kopiramo u novi cvor ime i broj pojavljivanja etikete */ strcpy(koren->etiketa, etiketa); koren->brojac = broj; koren->levi = NULL; koren->desni = NULL; return koren; } /* NAPOMENA: s obzirom da dve ili vise etiketa mogu imati isti broj pojavljivanja, etiketa se mora dodavati u stablo, cak i ako ima isti broj pojavljivanja sa korenom. Zato cemo u levo podstablo dodavati etikete koje imaju veci broj pojavljivanja od korena, dok cemo u desno podstablo dodavati etikete koje imaju manji ili jednak broj pojavljivanja od korena. */ /* Rekurzivni pozivi */ if(koren->brojac >= broj) koren->desni = dodaj_po_broju(koren->desni, etiketa, broj); else if(koren->brojac < broj) koren->levi = dodaj_po_broju(koren->levi, etiketa, broj); return koren; } /* Funkcija pretpostavlja da je novo stablo ili prazno, ili uredjeno prema opadajucem poretku broja pojavljavanja etiketa. Funkcija u takvo stablo rekurzivno dodaje sve etikete iz starog stabla, i vraca koren na tako modifikovano novo stablo. */ Cvor * resortiraj_stablo(Cvor * staro, Cvor * novo) { /* Izlaz iz rekurzije - nemamo sta da dodamo u novo stablo */ if(staro == NULL) return novo;

202

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

/* Dodajemo etiketu iz korena starog stabla u novo stablo, sortirano opadajuce prema broju pojavljivanja */ novo = dodaj_po_broju(novo, staro->etiketa, staro->brojac); /* Rekurzivno dodajemo u novo stablo sve cvorove iz levog i desnog podstabla starog stabla. */ novo = resortiraj_stablo(staro->levi, novo); novo = resortiraj_stablo(staro->desni, novo); return novo; } /* Ispis stabla - s leva na desno */ void ispisi_stablo(Cvor * koren) { if(koren == NULL) return; ispisi_stablo(koren->levi); printf("%s: %d\n", koren->etiketa, koren->brojac); ispisi_stablo(koren->desni); } /* Oslobadjanje dinamicki alociranog prostora */ void oslobodi_stablo(Cvor *koren) { if(koren == NULL) return; oslobodi_stablo(koren->levi); oslobodi_stablo(koren->desni); free(koren); }

/* glavni program */ int main(int argc, char **argv) { FILE *in = NULL; /* Ulazni fajl */ Cvor * koren = NULL; /* Stablo u leksikografskom poretku */ Cvor * resortirano = NULL; /* stablo u poretku po broju pojavljivanja */

203

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

/* Mora se zadati makar ime ulaznog fajla na komandnoj liniji. Opciono se kao drugi argument moze zadati opcija "-b" sto dovodi do ispisa po broju pojavljivanja (umesto leksikografski) */ if(argc < 2) { fprintf(stderr, "koriscenje: %s ime_fajla [-b]\n", argv[0]); exit(1); } /* Otvaramo fajl */ if((in = fopen(argv[1], "r")) == NULL) { fprintf(stderr, "fopen() greska\n"); exit(1); } /* Formiramo leksikografsko stablo etiketa */ koren = ucitaj(in); /* Ako je korisnok zadao "-b" kao drugi argument, tada se vrsi resortiranje, i ispisuje izvestaj sortiran opadajuce po broju pojavljivanja */ if(argc == 3 && strcmp(argv[2], "-b") == 0) { resortirano = resortiraj_stablo(koren, resortirano); ispisi_stablo(resortirano); } /* U suprotnom se samo ispisuje izvestaj u leksikografskom poretku */ else ispisi_stablo(koren);

/* Oslobadjamo stabla */ oslobodi_stablo(koren); oslobodi_stablo(resortirano); return 0; } Primer 11.7 Ekasnije sortiranje corova stabla formira se niz pokaziva ca na cvorove stabla koji se u skladu sa funkcijom pored enja sortiraju pomo cu bibliote cke qsort funkcije. #include <stdlib.h> #include <stdio.h>
204

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

/* Cvor drveta sadrzi ime reci i broj njenih pojavljivanja */ typedef struct _cvor { char ime[80]; int br_pojavljivanja; struct _cvor* levo, *desno; } cvor; /* Gradimo niz pokazivaca na cvorove drveta koji ce nam sluziti da po prihvatanju svih reci izvrsimo sortiranje po broju pojavljivanja */ #define MAX_BROJ_RAZLICITIH_RECI 1000 cvor* razlicite_reci[MAX_BROJ_RAZLICITIH_RECI]; /* Tekuci broj cvorova drveta */ int broj_razlicitih_reci=0; /* Funkcija uklanja binarno drvo iz memorije */ void obrisi_drvo(cvor* drvo) { if (drvo!=NULL) { obrisi_drvo(drvo->levo); obrisi_drvo(drvo->desno); free(drvo); } } cvor* napravi_cvor(char rec[]) { cvor* novi_cvor=(cvor*)malloc(sizeof(cvor)); if (novi_cvor==NULL) { printf("Greska prilikom alokacije memorije\n"); exit(1); } strcpy(novi_cvor->ime, rec); novi_cvor->br_pojavljivanja=1; return novi_cvor; }

205

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

/* Funkcija ubacuje datu rec u dato drvo i vraca pokazivac na koren drveta */ cvor* ubaci(cvor* drvo, char rec[]) { /* Ukoliko je drvo prazno gradimo novi cvor */ if (drvo==NULL) { cvor* novi_cvor = napravi_cvor(rec); /* pokazivac na novo napravljeni cvor smestamo u niz pokazivaca na sve cvorove drveta */ razlicite_reci[broj_razlicitih_reci++] = novi_cvor; return novi_cvor; } int cmp = strcmp(rec, drvo->ime); /* Ukoliko rec vec postoji u drvetu uvecavamo njen broj pojavljivanja */ if (cmp==0) { drvo->br_pojavljivanja++; return drvo; } /* Ukoliko je rec koju ubacujemo leksikografski ispred reci koja je u korenu drveta, rec ubacujemo u levo podstablo */ if (cmp<0) { drvo->levo=ubaci(drvo->levo, rec); return drvo; } /* Ukoliko je rec koju ubacujemo leksikografski iza reci koja je u korenu drveta, rec ubacujemo u desno podstablo */ if (cmp>0) { drvo->desno=ubaci(drvo->desno, rec); return drvo; } } /* Pomocna funkcija koja cita rec iz date datoteke i vraca

206

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

njenu duzinu, odnosno -1 ukoliko se naidje na EOF */ int getword(char word[], int lim, FILE* ulaz) { int c, i=0; /* Umesto funkcije getchar koristimo fgetc za rad sa datotekama */ while (!isalpha(c=fgetc(ulaz)) && c!=EOF) ; if (c==EOF) return -1; do { word[i++]=c; }while (i<lim-1 && isalpha(c=fgetc(ulaz))); word[i]=\0; return i; } /* Funkcija poredjenja za funkciju qsort. */ int poredi_br_pojavljivanja(const void* a, const void* b) { return /* Konverzija pokazivaca b iz pokazivaca na tip void u pokazivac na cvor jer nam je svaki element niza koji sortiramo tipa pokazivaca na cvor */ (*(cvor**)b)->br_pojavljivanja (*(cvor**)a)->br_pojavljivanja; }

main(int argc, char* argv[]) { int i; /* Drvo je na pocetku prazno */ cvor* drvo=NULL; char procitana_rec[80]; FILE* ulaz; if (argc!=2) { fprintf(stderr,"Greska : Ocekivano ime datoteke\n");

207

Milena Vujo sevi cJani ci c

11.1 Binarno pretra ziva cko stablo

exit(1); } if ((ulaz=fopen(argv[1],"r"))==NULL) { fprintf(stderr,"Greska : nisam uspeo da otvorim datoteku %s\n"); exit(1); } /* Citamo rec po rec dok ne naidjemo na kraj datoteke i ubacujemo ih u drvo */ while(getword(procitana_rec,80,ulaz)!=-1) drvo=ubaci(drvo,procitana_rec); /* Sortiramo niz pokazivaca na cvorove drveta po broju pojavljivanja */ qsort(razlicite_reci, broj_razlicitih_reci, sizeof(cvor*), poredi_br_pojavljivanja); /* Ispisujemo prvih 10 (ukoliko ih ima) reci i njihov broj pojavljivanja */ for (i=0; i<10 && i<broj_razlicitih_reci; i++) printf("%s - %d\n",razlicite_reci[i]->ime, razlicite_reci[i]->br_pojavljivanja); /* Uklanjamo drvo iz memorije */ obrisi_drvo(drvo); fclose(ulaz); }

208

Glava 12

Grafovi
Graf1 G=(V,E) sastoji se od skupa V cvorova i skupa E grana. Grane predstavljaju relacije izmed u cvorova i odgovara paru cvorova. Graf mo ze biti usmeren (orijentisan), ako su mu grane ured eni parovi i neusmeren (neorjentisan) ako su grane neured eni parovi. Put u grafu je niz cvorova v1,v2,...,vn, pri cemu u grafu postoje grane (v1,v2), (v2,v3), ..., (vn-1, vn). Ako jo s va zi da je v1 = vn, tada se ovakav put naziva ciklus. Iako su liste i stabla specijalni oblici grafova, u programiranju postoji bitna razlika: liste i stabala se prevashodno koriste kao kontejneri za podatke (u svaki cvor se sme sta neki podatak odred enog tipa), grafovi se pre svega koriste za modeliranje nekih problema iz prakse, kako bi se ti problemi predstavili na apstraktan na cin i re sili primenom algoritama nad odgovaraju cim grafom. Na primer, cvorovima u grafu se mogu predstaviti raskrsnice u gradu, a granama ulice koji ih povezuju. Tada se zadatak nala zenja najkra ceg puta od tacke A do tacke B u gradu mo ze svesti na nala zenje najkra ceg puta od cvora u do cvora v u grafu. Sli cno se mogu re savati problemi sa ra cunarskim mre zama (ra cunari se predstave cvorovima, a veze med u njima granama), itd. Grafovi se mogu predstavljati na vi se na cina. Uobi cajena su dva na cina predstavljanja grafova. To su matrica povezanosti grafa i lista povezanosti. Matrica povezanosti je kvadratna matrica dimenzije n, pri cemu je n broj cvorova u grafu, takva da je element na preseku i-te vrste i j-te kolone jednak jedinici ukoliko postoji grana u grafu od i-tog do j-tog cvora, ina ce je nula. Jasno je da ce neusmerenim grafovima odgovarati simetri cne matrice. Prednost ovakvog predstavljanja je jednostavnost. Nedostatak ovakvog pristupa je neracionalno kori s cenje memorije u slu caju retkih grafova (grafova sa malo grana), gde ce najve ci broj elemenata matrice biti 0. Umesto da se i sve nepostoje ce grane eksplicitno predstavljaju u matrici povezanosti, mogu se formirati povezane liste od jedinica iz i-te vrste za i=1,2,...,n. To je lista povezanosti. Svakom cvoru se pridru zuje povezana lista, koja sadr zi sve grane susedne tom cvoru. Graf je predstavljen vektorom lista. Svaki elemenat vektora
1 Tekst www.matf.bg.ac.yu/~jtomasevic, i Milana i primeri preuzeti od Jelene Toma sevi c, Bankovi ca www.matf.bg.ac.yu/~milan zasnovano na materijalu Algoritmi, Miodrag Zivkovi c i http://www.matf.bg.ac.yu/~filip

209

Milena Vujo sevi cJani ci c

Grafovi

sadr zi ime (indeks) cvora i pokaziva c na njegovu listu cvorova. Ovakav pristup stedi memoriju, a omogucava i ekasno dodavanje novih cvorova i grana. Osnovni problem koji se javlja kod grafova jeste kako polazeci od nekog cvora, kretanjem kroz puteva grafa posetiti sve cvorove. Ovakav postupak se naziva obilazak grafa. Postoje dva osnovna algoritma za obilazak grafa: pretraga u dubinu (DFS, skra cenica od depth-rst-search) i pretraga u sirinu (BFS, skra cenica od breadthrst-search). Kod DFS algoritma, obilazak zapo cinje iz proizvoljnog zadatog cvora r koji se naziva koren pretrage u dubinu. Koren se ozna cava kao pose cen. Zatim se bira proizvoljan neozna cen cvor r1, susedan sa r, pa se iz cvora r1 rekurzivno startuje pretraga u dubinu. Iz nekog nivoa rekurzije izlazi se kad se naid e na cvor v kome su svi susedi ve c ozna ceni. Primer 12.1 Primer reprezentovanja grafa preko matrice povezanosti. U programu se unosi neorijentisan graf i DFS algoritmom se utvrd uju cvrovi koji su dosti zni iz cvora 0. #include <stdlib.h> #include <stdio.h> int** alociraj_matricu(int n) { int **matrica; int i; matrica=malloc(n*sizeof(int*)); if (matrica==NULL) { printf("Greska prilikom alokacije memorije\n"); exit(1); } for (i=0; i<n; i++) { /* Funkcija calloc popunjava rezervisan prostor u memoriji nulama. */ matrica[i]=calloc(n,sizeof(int)); if (matrica[i]==NULL) { printf("Greska prilikom alokacije memorije\n"); exit(1); } } return matrica; } void oslobodi_matricu(int** matrica, int n) { int i;
210

Milena Vujo sevi cJani ci c

Grafovi

for (i=0; i<n; i++) free(matrica[i]); free(matrica); } int* alociraj_niz(int n) { int* niz; niz=calloc(n,sizeof(int)); if (niz==NULL) { printf("Greska prilikom alokacije memorije\n"); exit(1); } return niz; } void oslobodi_niz(int* niz) { free(niz); } void unesi_graf(int** graf, int n) { int i,j; for (i=0; i<n; i++) for (j=i; j<n; j++) { printf("Da li su element %d i %d povezani : ",i,j); do { scanf("%d",&graf[i][j]); /* Radimo sa neusmerenim grafom */ graf[j][i]=graf[i][j]; } /* Obezbedjujemo ispravan unos */ while (graf[i][j]!=0 && graf[i][j]!=1); } } void ispisi_graf(int** graf, int n) { int i,j; for (i=0; i<n; i++) { for (j=0; j<n; j++) printf("%d",graf[i][j]); printf("\n"); } }

211

Milena Vujo sevi cJani ci c

Grafovi

/* Rekurzivna implementacija DFS algoritma */ void poseti(int i, int** graf, int n, int* posecen) { int j; posecen[i]=1; printf("Posecujem cvor %d\n",i); for (j=0; j<n; j++) if (graf[i][j] && !posecen[j]) poseti(j); } main() { /* Broj cvorova grafa (dimenzija matrice) */ int n; /* Matrica povezanosti */ int **graf; /* Pomocni vektor koji govori o tome koji su cvorovi posecivani tokom DFS obilaska */ int *posecen; int i, j; printf("Unesi broj cvorova : "); scanf("%d",&n); graf=alociraj_matricu(n); unesi_graf(graf,n); ispisi_graf(graf,n); posecen=alociraj_niz(n); poseti(0, graf, n); oslobodi_niz(posecen); oslobodi_matricu(graf,n); } Primer 12.2 Primer demonstrira osnovne algoritme nad grafovima. U ovom primer grafovi se predstavljaju matricama povezanosti. Zbog jednostavnosti, pretpostavlja se da je broj cvorova ogranicen nekom ksiranom konstantom, tako da se odgovarajuce matrice alociraju staticki. Takod e, da bi se pojednostavile liste argumenata pojedinih funkcija, ve cina promenljivih u ovom primeru se deklari su kao globalne, kako bi kao takve bile dostupne svim funkcijama. #include <stdio.h> #include <stdlib.h>
212

Milena Vujo sevi cJani ci c

Grafovi

#define MAX_CVOROVA 100 /* Matrica povezanosti grafa. Pretpostavka je da broj cvorova grafa nece biti veca od MAX_CVOROVA */ int graf[MAX_CVOROVA][MAX_CVOROVA]; int broj_cvorova; /* Niz 0/1 vrednosti kojim se obelezavaju poseceni cvorovi */ int posecen[MAX_CVOROVA]; /* Pomocna funkcija koja utvrdjuje da li postoji neposecen cvor. Funkcija vraca indeks prvog takvog cvora, ili -1 u slucaju da su svi cvorovi vec poseceni */ int postoji_neposecen() { int i; for(i = 0; i < broj_cvorova; i++) if(!posecen[i]) return i; return -1; } /* Pomocna funkcija koja sluzi za inicijalizaciju raznih nizova (poput posecen[], dolazna_numeracija[] itd.) Za sve nizove se pretpostavlja da su duzine broj_cvorova. */ void inicijalizuj(int niz[]) { int i; for(i = 0 ; i < broj_cvorova ; i++) niz[i] = 0; } /* NAPOMENA: za potrebu demonstracije obilaska grafa, obrada koja se prilikom obilaska vrsi bice numerisanje cvorova grafa u poretku posecivanja. */ /* Staticki podaci koji se koriste za smestanje rezultata prilikom numeracije cvorova (i za DFS i za BFS). */ int dolazna_numeracija[MAX_CVOROVA];

213

Milena Vujo sevi cJani ci c

Grafovi

int odlazna_numeracija[MAX_CVOROVA]; /* Donji brojaci treba da se postave na nulu pre pozivanja funkcija za numeraciju */ int brojac_dolazna=0; int brojac_odlazna=0; /* Funkcija obilazi graf DFS algoritmom, i tom prilikom vrsi dolaznu i odlaznu numeraciju cvorova. Pre poziva ove funkcije obavezno je postaviti brojace dolazne i odlazne numeracije na 0 */ void DFS_numeracija(int polazni_cvor) { int i; /* Obelezavanje */ posecen[polazni_cvor] = 1; /* Ulazna obrada */ dolazna_numeracija[polazni_cvor] = ++brojac_dolazna; /* Rekurzija (za sve cvorove ka kojima postoji grana i koji jos nisu obelezeni) */ for(i = 0 ; i < broj_cvorova; i++) if(graf[polazni_cvor][i] && !posecen[i]) DFS_numeracija(i); /* Izlazna obrada */ odlazna_numeracija[polazni_cvor] = ++brojac_odlazna; } /* Funkcija vrsi BFS obilazak i numerise cvorove u poretku obilaska. Pre poziva ove funkcije treba inicijalizovati posecen[] i brojac_dolazna */ void BFS_numeracija(int polazni_cvor) { /* Improvizovani red koji koristimo za smestanje cvorova koji cekaju da budu obradjeni. Cvorovi se smestaju na kraj reda (kraj niza), a uzimaju sa pocetka reda (niza). Zato imamo dva indeksa koji pokazuju na pocetak i kraj reda. Duzina ovog reda je MAX_CVOROVA sto je dovoljno jer ce svaki cvor na red biti postavljen tacno jednom */

214

Milena Vujo sevi cJani ci c

Grafovi

int int int int

red[MAX_CVOROVA]; sledeci_na_redu = 0; poslednji_na_redu = -1; i;

/* Inicijalno se na redu nalazi samo polazni cvor */ posecen[polazni_cvor] = 1; red[++poslednji_na_redu] = polazni_cvor; /* Dokle god imamo cvorove u redu...*/ while(sledeci_na_redu <= poslednji_na_redu) { /* Vrsimo obradu cvora (numeracija) */ dolazna_numeracija[red[sledeci_na_redu]] = ++brojac_dolazna; /* Za sve cvorove ka kojima postoji grana i koji nisu poseceni vrsimo obelezavanje i dodavanje u red */ for(i = 0; i < broj_cvorova; i++) if(graf[red[sledeci_na_redu]][i] && !posecen[i]) { red[++poslednji_na_redu] = i; posecen[i] = 1; } /* Prelazimo na sledeci u redu */ sledeci_na_redu++; } } /* Test program */ int main() { int i,j; int aciklican; /* Unos matrice povezanosti grafa */ printf("Unesite broj cvorova grafa (<=%d): ", MAX_CVOROVA); scanf("%d",&broj_cvorova); for(i = 0; i < broj_cvorova; i++) for(j = 0; j < broj_cvorova; j++) { if(i == j) continue; printf("Da li postoji grana (%d,%d) (0/1): ", i, j);

215

Milena Vujo sevi cJani ci c

Grafovi

scanf("%d", &graf[i][j]); } /* Testiramo DFS numeraciju */ inicijalizuj(posecen); brojac_dolazna = 0; brojac_odlazna = 0; while((i = postoji_neposecen()) >= 0) DFS_numeracija(i); printf("Dolazna DFS numeracija: "); for(i = 0; i < broj_cvorova; i++) printf("%d ",dolazna_numeracija[i]); putchar(\n); printf("Odlazna DFS numeracija: "); for(i = 0; i < broj_cvorova; i++) printf("%d ", odlazna_numeracija[i]); putchar(\n);

/* Testiramo BFS numeraciju */ inicijalizuj(posecen); brojac_dolazna = 0; while((i = postoji_neposecen()) >= 0) BFS_numeracija(i); printf("Dolazna BFS numeracija: "); for(i = 0; i < broj_cvorova; i++) printf("%d ",dolazna_numeracija[i]); putchar(\n); return 0; } Primer 12.3 Primer predstavljanja grafa preko niza listi suseda svakog od cvorova grafa. U programu se unosi graf i DFS algoritmom se utvrdjuje koji su cvorovi dosti zni iz cvora 0. #include <stdlib.h> #include <stdio.h> /* Cvor liste suseda */
216

Milena Vujo sevi cJani ci c

Grafovi

typedef struct _cvor_liste { int broj; /* Indeks suseda */ struct _cvor_liste* sledeci; } cvor_liste; /* Ubacivanje na pocetak liste */ cvor_liste* ubaci_u_listu(cvor_liste* lista, int broj) { cvor_liste* novi=malloc(sizeof(cvor_liste)); if (novi==NULL) { printf("Greska prilikom alokacije memorije\n"); exit(1); } novi->broj=broj; novi->sledeci=lista; return novi; } /* Rekurzivno brisanje liste */ void obrisi_listu(cvor_liste* lista) { if (lista) { obrisi_listu(lista->sledeci); free(lista); } } /* Ispis liste */ void ispisi_listu(cvor_liste* lista) { if (lista) { printf("%d ",lista->broj); ispisi_listu(lista->sledeci); } } #define MAX_BROJ_CVOROVA 100 /* Graf predstavlja niz pokazivaca na pocetke listi suseda */ cvor_liste* graf[MAX_BROJ_CVOROVA]; int broj_cvorova; int posecen[MAX_BROJ_CVOROVA]; /* Rekurzivna implementacija DFS algoritma */ void poseti(int i)

217

Milena Vujo sevi cJani ci c

Grafovi

cvor_liste* sused; printf("Posecujem cvor %d\n",i); posecen[i]=1; for( sused=graf[i]; sused!=NULL; sused=sused->sledeci) if (!posecen[sused->broj]) poseti(sused->broj);

} void oslobodi_graf(cvor_liste* graf[], int broj_cvorova) { int i; for(i=0; i<broj_cvorova; i++) obrisi_listu(graf[i]); } main() { int i; printf("Unesi broj cvorova grafa : "); scanf("%d",&broj_cvorova); /*Unos grafa*/ for (i=0; i<broj_cvorova; i++) { int br_suseda,j; graf[i]=NULL; printf("Koliko cvor %d ima suseda : ",i); scanf("%d",&br_suseda); for (j=0; j<br_suseda; j++) { int sused; do { printf("Unesi broj %d.-tog suseda cvora %d : ",j,i); scanf("%d",&sused); } while (sused<1 && sused>broj_cvorova); graf[i]=ubaci_u_listu(graf[i],sused-1); } } /*Ispis grafa*/ for (i=0; i<broj_cvorova; i++) { printf("%d - ",i); ispisi_listu(graf[i]); printf("\n");

218

Milena Vujo sevi cJani ci c

Grafovi

} /*Koji su sve cvorovi dostupni iz cvora 0*/ poseti(0); /*Oslobadjanje memorije*/ oslobodi_graf(graf, broj_cvorova); } Primer 12.4 MINESWEEPER - primer jednostavne igrice. #include <stdlib.h> #include <stdio.h> #include <time.h> /* Dimenzija table */ int n; /* Tabla koja sadrzi 0 i 1 u zavisnosti od toga da li na polju postoji bomba */ int** bombe; #define PRAZNO (-1) #define ZATVORENO 0 #define ZASTAVICA 9 /* Tabla koja opisuje tekuce stanje igre. Moze da sadrzi sledece vrednosti : ZATVORENO - opisuje polje koje jos nije bilo otvarano PRAZNO - polje na kome ne postoji ni jedna bomba BROJ od 1-8 - polje koje je otvoreno i na kome pise koliko bombi postoji u okolini ZASTAVICA - polje koje je korisnik oznacio zastavicom */ int** stanje; /* Ukupan broj bombi */ int broj_bombi; /* Ukupan broj postavljenih zastavica */ int broj_zastavica = 0; /* Pomocne funkcije za rad sa matricama */
219

Milena Vujo sevi cJani ci c

Grafovi

int** alociraj(int n) { int i; int** m=(int**)malloc(n*sizeof(int*)); if(m==NULL) {printf("Greska prilikom alokacije memorije\n"); exit(1);} for (i=0; i<n; i++) { m[i]=(int *)calloc(n,sizeof(int)); if(m[i]==NULL) {printf("Greska prilikom alokacije memorije\n"); exit(1);} } return m; } void obrisi(int** m, int n) { int i; for (i=0; i<n; i++) free(m[i]); free(m); } /* Funkcija postavlja bombe */ void postavi_bombe() { broj_bombi=(n*n)/6; int kolona; int vrsta; int i; /* Inicijalizujemo generator slucajnih brojeva */ srand(time(NULL)); for (i=0; i<broj_bombi; i++) { /* Racunamo slucajni polozaj bombe */ kolona=rand()%n; vrsta=rand()%n;

/* Ukoliko bomba vec postoji tu, opet idemo u istu iteraciju */ if (bombe[vrsta][kolona]==1) { i--; continue;

220

Milena Vujo sevi cJani ci c

Grafovi

} /* Postavljamo bombu */ bombe[vrsta][kolona]=1; } } /* Funkcija ispisuje tablu sa bombama */ void ispisi_bombe() { int i,j; for (i=0; i<n; i++) { for (j=0; j<n; j++) printf("%d",bombe[i][j]); printf("\n"); } } /* Funkcija ispisuje tekuce stanje */ void ispisi_stanje() { int i,j; for (i=0; i<n; i++) { for (j=0; j<n; j++) { if (stanje[i][j]==ZATVORENO) printf("."); else if (stanje[i][j]==PRAZNO) printf(" "); else if (stanje[i][j]==ZASTAVICA) printf("*"); else printf("%d",stanje[i][j]); } printf("\n"); } } /* Funkcija postavlja zastavicu na dato polje ili je uklanja ukoliko vec postoji */ void postavi_zastavicu(int i, int j) { if (stanje[i][j]==ZATVORENO) { stanje[i][j]=ZASTAVICA; broj_zastavica++;

221

Milena Vujo sevi cJani ci c

Grafovi

} else if (stanje[i][j]==ZASTAVICA) { stanje[i][j]=ZATVORENO; broj_zastavica--; } } /* Funkcija izracunava koliko bombi postoji u okolini date bombe */ int broj_bombi_u_okolini(int v, int k) { int i, j; int br=0; /* Prolazimo kroz sva okolna polja */ for (i=-1; i<=1; i++) for(j=-1; j<=1; j++) { /* preskacemo centralno polje */ if (i==0 && j==0) continue; /* preskacemo polja "van table" */ if (v+i<0 || k+j<0 || v+i>=n || k+j>=n) continue; if (bombe[v+i][k+j]==1) br++; } return br; } /* Centralna funkcija koja vrsi otvaranje polja i pritom se otvaranje "siri" i na polja koja su oko datog */ void otvori_polje(int v, int k) { /* Ukoliko smo "nagazili" bombu zavrsavamo program */ if (bombe[v][k]==1) { printf("BOOOOOOOOOOOOOOOOM!!!!\n"); ispisi_bombe(); exit(1); } else { /* Brojimo bombe u okolini */ int br=broj_bombi_u_okolini(v,k);

222

Milena Vujo sevi cJani ci c

Grafovi

/* Azuriramo stanje ovog polja */ stanje[v][k]=(br==0)?PRAZNO:br; /* Ukoliko u okolini nema bombi, rekurzivno otvaramo sva polja u okolini koja su zatvorena */ if (br==0) { /* Petlje indeksiraju sva okolna polja */ int i,j; for (i=-1; i<=1; i++) for (j=-1; j<=1; j++) { /* Preskacemo centralno polje */ /* if (i==0 && j==0) continue; */ /* Preskacemo polja van table */ if (v+i<0 || v+i>=n || k+j<0 || k+j>=n) continue; /* Ukoliko je okolno polje zatvoreno, otvaramo ga */ if (stanje[v+i][k+j]==ZATVORENO) otvori_polje(v+i, k+j); } } } } /* Funkcija utrdjuje da li je partija gotova Partija je gotova u trenutku kada su sve bombe pokrivene zastavicama i kada nijedno drugo polje nije pokriveno zastavicom */ int gotova_partija() { int i,j; for (i=0; i<n; i++) for (j=0; j<n; j++) { /* Ukoliko postoji nepokrivena bomba, partija nije zavrsena */ if (bombe[i][j]==1 && stanje[i][j]!=ZASTAVICA) return 0; } /* Partija je zavrsena samo ukoliko je

223

Milena Vujo sevi cJani ci c

Grafovi

broj zastavica jednak broj bombi */ return broj_zastavica==broj_bombi; } main() { /* Unosimo dimenziju table */ printf("Unesite dimenziju table : "); scanf("%d",&n); /* Alociramo table */ bombe=alociraj(n); stanje=alociraj(n); /* Postavljamo bombe */ postavi_bombe(); /* Sve dok partija nije gotova */ while(!gotova_partija()) { int v,k; char akcija; /* Ispisujemo tekuce stanje */ ispisi_stanje(); /* Sve dok korisnik ne unese o ili z trazimo od njega da upise odgovarajucu akciju */ do { getchar(); printf("Unesi akciju (o - otvaranje polja, z - postavljanje zastavice) : "); scanf("%c",&akcija); } while (akcija!=o && akcija!=z); /* Trazimo od korisnika da unese koordinate polja sve dok ih ne unese ispravno Korisnicke koordinate krecu od 1, a interne od 0 */ do { printf("Unesi koordinate polja : "); scanf("%d",&v);

224

Milena Vujo sevi cJani ci c

Grafovi

scanf("%d",&k); } while(v<1 || v>n || k<1 || k>n); /* Reagujemo na akciju */ switch(akcija) { case o: otvori_polje(v-1,k-1); break; case z: postavi_zastavicu(v-1,k-1); } } /* Konstatujemo pobedu */ ispisi_stanje(); printf ("Cestitam! Pobedili ste\n"); obrisi(stanje,n); obrisi(bombe,n); }

225

You might also like