You are on page 1of 226

Programiranje 2

Programski jezik C
— Zadaci sa vežbi —

Milena Vujošević - Janičić 2008/2009


Predgovor

Ovo je prateći materijal za vežbe koje držim iz predmenta Programiranje 2. On ne


može zameniti pohad̄anje vežbi niti korišćenje druge preporučene literature.
Deo materijala čine zadaci i rešenja mr Filipa Marića (raspoloživi na
www.matf.bg.ac.yu/~filip/pp/0405/index.pl).
Takod̄e korišćen je i materijal sa sajta koleginice Jelene Grmuše
www.matf.bg.ac.yu/~jelenagr
kolege Miroslava Marića
www.matf.bg.ac.yu/~maricm
i kolege Milana Bankovića
www.matf.bg.ac.yu/~milan.
Tekstovi i objašnjenja su uglavnom zasnovani na knjizi Programski jezik C, autora
Kerninghan & Ritchie.
Zahvaljujem svojim studentima na aktivnom učešću u nastavi čime su mi pomogli
u uobličavanju ovog materijala.
Svi komentari i sugestije vezane za ovaj materijal biće veoma dobrodošli.

Milena Vujošević-Janičić
www.matf.bg.ac.yu/~milena

1
Sadržaj

1 Argumenti komandne linije 4

2 Datoteke 7

3 Bitski operatori 19

4 Vežbanje 30
4.1 Polinomi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.2 Veliki brojevi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

5 Sortiranje 40
5.1 Vremenska složenost . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.2 Selection sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.3 Bubble sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.4 Insertion sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.5 Razni zadaci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

6 Pokazivači 58
6.1 Pokazivačka aritmetika — primeri . . . . . . . . . . . . . . . . . . . . 59
6.2 Niska karaktera i pokazivač na konstantnu nisku . . . . . . . . . . . . 64
6.3 Pokazivači na pokazivače . . . . . . . . . . . . . . . . . . . . . . . . . 65
6.4 Nizovi pokazivača . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

7 Rekurzija 69
7.1 Osnovni primeri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
7.2 Binarna pretraga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
7.3 MergeSort algoritam . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
7.4 QuickSort algoritam . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

8 Pokazivači na funkcije 86
8.1 Funkcija qsort iz standardne biblioteke . . . . . . . . . . . . . . . . . 89
8.2 Funkcija bsearch iz standardne biblioteke . . . . . . . . . . . . . . . 93
8.3 Funkcije lsearch i lfind . . . . . . . . . . . . . . . . . . . . . . . . 97
8.4 Pokazivači . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

2
SADRŽAJ SADRŽAJ

9 Dinamička alokacija memorije 99


9.1 Matrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
9.2 Realokacija memorije . . . . . . . . . . . . . . . . . . . . . . . . . . . 114

10 Liste 118
10.1 Implementacija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
10.2 Kružna (ciklična) lista . . . . . . . . . . . . . . . . . . . . . . . . . . 143
10.3 Red . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
10.4 Stek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
10.5 Dvostruko povezane liste . . . . . . . . . . . . . . . . . . . . . . . . . 160

11 Stabla 166
11.1 Binarno pretraživačko stablo . . . . . . . . . . . . . . . . . . . . . . . 167

12 Grafovi 209

3
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šević–Janičić 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šenje.
#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šević–Janičić Argumenti komandne linije

/* Dok god ne dodjemo do kraja tekuceg stringa */


while (c=*++argv[0])
printf("Prisutna opcija : %c\n",c);
}

6
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šević–Janičić 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či stdin, stdout i stderr su definisani u okviru stdio.h i mogu se
koristiti za pisanje na standardni ulaz, izlaz i izlaz za grešku.
FILE* stdin;
FILE* stdout;
FILE* stderr;
Primer 2.2 Program demonstrira ”a” - append mod datoteka - nadovezivanje.
#include <stdio.h>

8
Milena Vujošević–Janičić 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šević–Janičić 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šević–Janičić Datoteke

}
/* Zatvaramo datoteke */
fclose(in);
fclose(out);
}
Primer 2.5 Program ilustruje rad sa datotekama. Program kopira datoteku čije se
ime zadaje kao prvi argument komandne linije u datoteku čije 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šević–Janičić 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 čitanje 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šević–Janičić 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šević–Janičić 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šević–Janičić 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 čije se ime zadaje kao prvi argument komandne
linije upisuje prvih N prirodnih brojeva (N se zadaje sa standardnog ulaza) i zatim
izračunava dužinu 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šević–Janičić 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 čije se ime zadaje kao prvi argument

16
Milena Vujošević–Janičić Datoteke

komandne linije zamenjuje sva pojavljivanja niske koja se zadaje kao drugi argument
komandne linije niskom koja se zadaje kao treći argument komandne linije (pret-
postaviti da ce obe niske iste dužine 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či 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šević–Janičić 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žno je razlikovati ih od


logičkih 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", 255 & 15 );
printf( "255 | 15 = %d\n", 255 | 15 );
printf( "255 ^ 15 = %d\n", 255 ^ 15 );
printf( "2 << 2 = %d\n", 2 << 2 );
printf( "16 >> 2 = %d\n", 16 >> 2 );
}

Izlaz iz programa je:


377 17
255 & 15 = 15
255 | 15 = 255
255 ^ 15 = 240
2 << 2 = 8
16 >> 2 = 4
Primer 3.2 print bits - stampa bitove u zapisu datog celog broja x.
#include <stdio.h>

19
Milena Vujošević–Janičić 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šević–Janičić 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ši se pomeranje sadržaja operanda a predstavljenog u binarnom
obliku za b mesta u desno. Popunjavanje upražnjenih mesta na levoj strani zavisi od
tipa podataka i vrste računara. Ako se pomeranje primenjuje nad operandom tipa
unsigned popunjavanje je nulama. Ako se radi o označenom operandu popunjavanje
je jedinicama kada je u krajnjem levom bitu jedinica, a nulama kada je u krajnjem
levom bitu nula.

21
Milena Vujošević–Janičić Bitski operatori

Da li važi sledeća jednakost (za prirodan broj k):


((~0) >> k) == (~0)?
Pošto je 0 konstanta celobrojnog tipa, to je ~0 takodje tipa int, pri čemu je to
broj koji je zapisan svim jedinicama. Prema tome, njegovim šiftovanjem u desno
dobijamo isti taj broj, jer se upražnjen prostor sa desne strane popunjava jedinicama.
VAŽNO:
Vrednost izraza a<<b je jednaka vrednosti a · 2b .
Vrednost izraza a>>b je jednaka vrednosti a/2b .
Bitovski operatori su efikasniji od operacija * i / tako da svaki put kad se javi
potreba za množenjem ili deljenjem stepenom broja dva, treba koristiti operacije <<
i >>.
Primer 3.5 sum of bits - izračunava sumu bitova datog neoznačenog 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šević–Janičić 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 inver-
tovanje pojedinačnih 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić Bitski operatori

print_bits(mirror(x));
print_bits(right_rotate(x, 2));
}

Izlaz iz programa:
11111010111100001111101011110000
00001111010111110000111101011111
00111110101111000011111010111100
Primer 3.8 Promeljiva c je tipa char. Šta ce biti vrednost promenljive c posle izraza
dodele
• c=(15|3)>>1;?
• c=(15 & 3)<<1; ?
Primer 3.9 Napisati program koji sa standardnog ulaza učcitava pozitivan ceo broj,
a na standardni izlaz ispisuje vrednost tog broja sa razmenjenim vrednostima bitova
na pozicijama i, j. Pozicije i, j se učitavaju kao parametri komandne linije. Sma-
trati da je krajnji desni bit binarne reprezentacije 0-ti bit. Pri rešavanju nije dozvol-
jeno koristiti pomoćni 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šević–Janičić 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čuna i štampa 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šević–Janičić Bitski operatori

int br1_par = 0; /* int rezultat=0;*/


int br1_nepar = 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žbanje

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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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že biti sortiran ili ured̄jen u


• opadajućem poretku:
niz[0] > niz[1] > ... > niz[i] > niz[i + 1] > ... > niz[n − 1]

• rastućem poretku:
niz[0] < niz[1] < ... < niz[i] < niz[i + 1] < ... < niz[n − 1]

• neopadajućem poretku:
niz[0] ≤ niz[1] ≤ ... ≤ niz[i] ≤ niz[i + 1] ≤ ... ≤ niz[n − 1]

• nerastućem poretku:
niz[0] ≥ niz[1] ≥ ... ≥ niz[i] ≥ niz[i + 1] ≥ ... ≥ niz[n − 1]

U nastavku su dati primeri algoritama za sortiranje u nerastućem poretku celobro-


jnog niza. Jednostavnim modifikacijama svakim od ovih algoritama niz se može
sortirati i u opadajućem, rastućem ili neopadajućem poretku.

5.1 Vremenska složenost


Pogledati slike koje oslikavaju vremensku složenost za različite algoritme i različite
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šević–Janičić 5.2 Selection sort

5.2 Selection sort


Primer 5.1 (Selection sort) U prvom prolazu se razmenjuju vrednosti a[0] sa
onim članovima ostatka niza koji su veći od njega. Na taj način će se posle prvog
prolaza kroz niz a[0] postaviti na najveći element niza. U svakom sledećem prolazu,
postavlja se na i-to mesto najveci element niza a[i], a[i+1] ... a[n-1].
Primer sortiranja:
n = 6 a: 1 3 5 2 6 8
-------------
i = 0 j = 1 => a: 3 1 5 2 6 8
i = 0 j = 2 => a: 5 1 3 2 6 8
i = 0 j = 3 => a: 5 1 3 2 6 8
i = 0 j = 4 => a: 6 1 3 2 5 8
i = 0 j = 5 => a: 8 1 3 2 5 6
-------------
i = 1 j = 2 => a: 8 3 1 2 5 6
i = 1 j = 3 => a: 8 3 1 2 5 6
i = 1 j = 4 => a: 8 5 1 2 3 6
i = 1 j = 5 => a: 8 6 1 2 3 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
Milena Vujošević–Janičić 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) Modifikacija prethodnog rešenja radi dobijanja na
efikasnosti. Ne vrše se zamene svaki put već samo jednom, kada se pronad̄e odgo-
varajući element u nizu sa kojim treba izvršiti zamenu tako da u nizu bude postavljen
trenutno najveći element na odgovarajuće mesto.
Primer sortiranja:
n = 6 a: 1 3 5 2 6 8
-------------
i = 0 => a: 8 1 5 2 6 3
-------------
i = 1 => a: 8 6 5 2 1 3
-------------
i = 2 => a: 8 6 5 2 1 3
-------------

42
Milena Vujošević–Janičić 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 el-
ementa niza i ako su pogrešno raspored̄eni zamenjuje im mesta. Posle pored̄enja
svih susednih parova najmanji od njih će isplivati na kraj niza. Zbog toga se ovaj
metod naziva metod mehurića. Da bi se najmanji broj nesortiranog dela niza doveo
na svoje mesto treba ponoviti postupak.
Primer sortiranja:
n = 6 a: 1 3 5 2 6 8
-------------
i = 5 j = 0 => a: 3 1 5 2 6 8
i = 5 j = 1 => a: 3 5 1 2 6 8
i = 5 j = 2 => a: 3 5 2 1 6 8
i = 5 j = 3 => a: 3 5 2 6 1 8
i = 5 j = 4 => a: 3 5 2 6 8 1
-------------

43
Milena Vujošević–Janičić 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še provere onda kada je niz već sortiran nego da se u tom slučaju 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šević–Janičić 5.4 Insertion sort

5.4 Insertion sort


Primer 5.5 (Insertion sort) U svakom trenutku je početak niza sortiran, a sorti-
ranje se vrši tako što se jedan po jedan element niza sa kraja ubacuje na odgovarajuće
mesto.
Primer sortiranja:
n = 6 a: 1 3 5 2 6 8
-------------
i = 1 j = 1 => a: 3 1 5 2 6 8
-------------
i = 2 j = 2 => a: 3 5 1 2 6 8
i = 2 j = 1 => a: 5 3 1 2 6 8
-------------
i = 3 j = 3 => a: 5 3 2 1 6 8
-------------
i = 4 j = 4 => 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čuvan 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šević–Janičić 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ći sortiran niz
(nizovi su sortirani neopadajućie).
#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šević–Janičić 5.5 Razni zadaci

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

for (i = 0; i < nc; i++)


printf("%d ", c[i]);
printf("\n");
}
Primer 5.8 Program učitava 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šević–Janičić 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šević–Janičić 5.5 Razni zadaci

int br_studenata = UcitajPodatke(studenti,100);

SelectionSort(studenti, br_studenata);
IspisiPodatke(studenti, br_studenata);
}
Primer 5.9 Program učitava informacije o studentima iz datoteke čije 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 5.5 Razni zadaci

return 0;
}
Primer 5.11 Interpolaciona pretraga se može porediti sa pretragom rečnika:
ako neko traži reč na slovo B, sigurno neće da otvori rečnik na polovini, već verovatno
negde bliže početku.
/* 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šević–Janičić 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ća niza A i B, napisati funkciju koja formira niz C
koji sadrži:
(a) zajedničke elemente nizova A i B (presek skupova A i B).
(b) elemente koje sadrži niz A a ne sadrži niz B (razlika skupova A i B).
(c) sve elemente koje sadrže 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šević–Janičić 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šević–Janičić 5.5 Razni zadaci

c[k++] = a[i++];

/* Vracamo broj elemenata u razlici */


return k;
}

57
Glava 6

Pokazivači

Pokazivač je promenljiva koja sadrži adresu promenljive.


int x=1, y=1;
int *ip; /* ip je pokazivac na int, odnosno *ip je tipa int */

ip = &x; /* ip cuva adresu promenljive x, tj ip pokazuje na x */


y=*ip; /* 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 */
*ip = 0; /* preko pokazivaca njema se sadrzaj na adresi koju cuva ip,
prema tome, x je sada 0 */

*ip+=10; /* x je sada 10*/


++*ip; /* x je sada 11*/
(*ip)++; /* x je sada 12,
zagrada neophodna zbog prioriteta operatora*/
Pored pokazivača na osnovne tipove, postoji i pokazivač na prazan tip — void.
void *pp;
Njemu može 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č svaki put kada
želimo da koristimo sadržaj adrese koju pokazivač čuva.
Primer 6.1 Upotreba pokazivača 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šević–Janičić 6.1 Pokazivačka 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či da pokazivač ne pokazuje na neko


mesto u memoriji je NULL. Ova konstanta je definisana u biblioteci stdio.h i ima
vrednost 0.

6.1 Pokazivačka aritmetika — primeri


Primer 6.2 Funkcija proverava da li se neka vrednost x nalazi u nizu niz dimenzije
n, bez korišćenja indeksiranja. Funkcija vraća pokazivač 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šević–Janičić 6.1 Pokazivačka 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čunavaju dužinu niske korišćenjem pokazivača.


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šević–Janičić 6.1 Pokazivačka aritmetika — primeri

}
*dest = ’\0’;
*/
Primer 6.5 Nadovezuje string t na kraj stringa s. Pretpostavlja da u s ima do-
voljno 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ši leksikografsko pored̄enje dva stringa. Vraća:
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ća pokazivač na
nju, odnosno NULL ukoliko s ne sadrži c.
char* string_char(char *s, char c)
{

61
Milena Vujošević–Janičić 6.1 Pokazivačka 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ća pokazivač


na nju, odnosno NULL ukoliko s ne sadrži 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ži reč 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šević–Janičić 6.1 Pokazivačka 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šević–Janičić 6.2 Niska karaktera i pokazivač na konstantnu nisku

printf("%s",linija);
}

6.2 Niska karaktera i pokazivač na konstantnu nisku


Prilikom deklaracije treba praviti razliku izmed̄u niza znakova i pokazivača na kon-
stantnu nisku. Ukoliko su date deklaracije:
char poruka[]="danas je lep dan!";
char *pporuka = "danas je lep dan!";
tada važi:
• poruka je niz znakova koji sadrži tekst danas je lep dan!. Pojedine znake
moguće je promeniti, na primer, nakon naredbe
poruka[0] = ’D’;
poruka će sadržati tekst Danas je lep dan!. Niska poruka se uvek odnosi na
novo mesto u memoriji.
• pporuka je pokazivač, koji je inicijalizovan da pokazuje na konstantnu nisku,
on može biti preusmeren da pokazuje na nešto drugo, na primer, moguće je
preusmeriti ovaj pokazivač sledećom naredbom
pporuka = poruka;
i u ovom slučaju će pokazivač pporuka pokazivati na mesto u memoriji na
kojem se nalazi poruka. Pokušaj modifikacije elementa niske karaktera na koju
ukazuje pporuka nije definisan (jer je to konstantna niska). Na primer, rezultat
naredbe pporuka[0]=’D’ je nedefinisan.
Ako deklarišemo
char *pporuka1 = "danas je lep dan!";
char *pporuka2 = "danas je lep dan!";
char *pporuka3 = "danas pada kisa";
tada će pokazivači pporuka1 i pporuka2 pokazivati na isto mesto u memoriji, a
pporuka3 na drugo mesto u memoriji.
Ako uporedimo
(pporuka1==pporuka3)
uporediće se vrednosti pokazivača. Ako uporedimo
(pporuka1 < pporuka2)
uporediće se vrednosti pokazivača. Ako dodelimo
pporuka1=pporuka3
tada će pporuka1 dobiti vrednost pokazivača pporuka3 i pokazivaće na isto mesto
u memoriji. Neće se izvršiti kopiranje sadržaja memorije.
Primer 6.10 Nakon deklaracije
char a[] = "informatika";
char *p = "informatika";

64
Milena Vujošević–Janičić 6.3 Pokazivači na pokazivače

• Loliko elemenata ima niz a?


• Koju vrednost ima a?
• Da li se može promeniti vrednost a?
• Koju vrednost ima a[0]?
• Da li se može promeniti vrednost a[0]?
• Koju vrednost ima p?
• Da li se može promeniti vrednost p?
• Koju vrednost ima p[0]?
• Da li se može promeniti vrednost p[0]?

6.3 Pokazivači na pokazivače


Pokazivačke promenljive su podaci kao i svi drugi, pa samim tim imaju svoju adresu
u memoriji. Zbog toga je moguće govoriti o pokazivačima na pokazivače. Na primer,
ako je definisan pokazivač na int:
int *p;
tada mozemo definisati pokazivač na pokazivač na int:
int **pp;
Ovaj pokazivač moze uzeti adresu pokazivačke promenljive p:
pp = &p;
Nakon toga se pokazivačkoj promenljivoj p moze pristupiti i indirektno, preko pokazivača
na pokazivač pp, zato što je *pp ekvivalentno sa p. Takod̄e, celom broju na koji
pokazuje p moze se pristupiti i sa **pp zato što je **pp ekvivalentno sa *p.

6.4 Nizovi pokazivača


Elementi nizova mogu biti bilo kog tipa, pa i pokazivači. Na primer, niz pokazivača
na karaktere se može definisati na sledeći način:
char * pokazivaci[100];
Ovim se rezerviše prostor za niz od 100 pokazivača na karaktere. Njima se pris-
tupa putem indeksa, kako je i uobičajeno. S obzirom da se ime niza uvek ponaša
kao adresa prvog objekta u nizu, sledi da je izraz pokazivaci tipa ”pokazivač na
pokazivač na char”. Otuda se uobičajena pokazivačka aritmetika moze primenjivati
i na nizove pokazivača.

65
Milena Vujošević–Janičić 6.4 Nizovi pokazivača

Primer 6.11 Program ucitava linije iz fajla ulaz.txt i zatim ih ispisuje na stan-
dardnom izlazu sortirane po dužini, počev od najkraće linije.
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <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šević–Janičić 6.4 Nizovi pokazivača

/* 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šević–Janičić 6.4 Nizovi pokazivača

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 definiciju, tj. kod kojih se problem dimenzije n moze jednos-
tavno 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 efikasno ostvaruje zah-
valjujuci 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, first 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 funkci-
jski 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/m̃ilan

69
Milena Vujošević–Janičić 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 ak-
tivnih 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 jednos-
tavan, ali ne mora uvek da bude tako.

7.1 Osnovni primeri


Primer 7.1 Računanje 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šević–Janičić 7.1 Osnovni primeri

printf("f = %d\n",f);
}

Primer 7.2 Računanje 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čunanja 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šević–Janičić 7.1 Osnovni primeri

return n == 1 ? a[0] : suma_niza(a,n-1) + a[n-1];*/


}
Primer 7.4 Štampanje 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ću, i tako redom do poslednje koja prima 1 kao argument.
Ta funkcija štampa 1 i završava sa radom tako da se vraća na prethodni nivo, na
kome se štampa dva i tako redom.
Primer 7.5 Stepenovanje prirodnog broja
• iterativno
• rekurzivno, koristeći činjenicu da je xk = x · xk−1 i x0 = 1
k k−1
• rekurzivno, koristeći činjenicu da je xk = (x2 ) 2 ako je k parno ili xk = x(x2 ) 2

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
Milena Vujošević–Janičić 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šević–Janičić 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čijevi brojevi se definišu rekurentno na sledeći način:
f(0) = 1, f(1) = 1, f(n) = f(n-1) + f(n-2)
Napisati funkciju koja izračunava n-ti Fibonačijev 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šević–Janičić 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šević–Janičić 7.1 Osnovni primeri

Racunam Fib(1)
Fib(5) = 8

Kako se izvrsava program za vrednost 5:


_______________________________________________________________

Fib(5)?
=> 8 = 5+3
/ \
/ \
/ \
Fib(4)? Fib(3)?
=> 5=3+2 => 3=2+1
/ \ / \
Fib(3)? Fib(2)? Fib(2)? Fib(1)?
=> 3=2+1 => 2=1+1 => 2=1+1 |
/ \ / \ / \ 1
Fib(2)? Fib(1) Fib(1) Fib(0) Fib(1) Fib(0)
=> 2=1+1 | | | | |
/ \ 1 1 1 1 1
Fib(1) Fib(0)
| |
1 1
_______________________________________________________________

*/
Primer 7.7 U slučaju da se rekurzijom problem svodi na više manjih podproblema
koji se mogu preklapati, postoji opasnost da se pojedini podproblemi manjih dimenz-
ija rešavaju veći broj puta. Na primer,
fibonacci(20) = fibonacci(19) + fibonacci(18)
fibonacci(19) = fibonacci(18) + fibonacci(17)
tj. problem fibonacci(18) se resava dva puta. Problemi manjih dimenzija će se
rešavati još veći broj puta. Rešenje za ovaj problem je kombinacija rekurzije sa tzv.
”dinamičkim programiranjem” – podproblemi se rešavaju samo jednom, a njihova
rešenja se pamte u memoriji (obično u nizovima ili matricama), odakle se koriste
ako tokom rešavanja ponovo budu potrebni.
#include <stdio.h>
#include <stdlib.h>

#define MAX 50

/* Niz koji cuva resenja podproblema. */


int f[MAX];

76
Milena Vujošević–Janičić 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šava problem tzv. ”hanojskih kula”: data su tri vertikalna
štapa, na jednom se nalazi n diskova poluprečnika 1,2,3,... do n, tako da se najveći
nalazi na dnu, a najmanji na vrhu. Ostala dva štapa su prazna. Potrebno je pre-
mestiti diskove na drugi štap tako da budu u istom redosledu, premeštajući jedan po
jedan disk, pri čemu se ni u jednom trenutku ne sme staviti veći disk preko manjeg.

77
Milena Vujošević–Janičić 7.1 Osnovni primeri

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

| | |
| === |
| ===== |
| ======= |
| ========= |
----------- ----------- -----------
X Y 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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češljavanjem - 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šević–Janičić 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šević–Janičić 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šević–Janičić 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 sortiramo elemente manje od pivota */


quick_sort(a, l, pivot-1);
/* Rekurzivno sortiramo elemente vece pivota */
quick_sort(a, 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či na funkcije

Binarne instrukcije koje čine funkciju su takod̄e negde u memoriji, i imaju svoju
adresu. Zato je moguće je definisati pokazivač na funkciju. Na primer, deklaracija:
int (*fp)(int);
definiše promenljivu f, koja je tipa ”pokazivač na funkciju koja prihvata argument
tipa int, i vraca vrednost tipa int”. Ova promenljiva sadrži 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ži adresu funkcije f. Funkcija f se sada može pozivati derefer-
enciranjem pokazivača fp:
(*fp)(3);
Zagrade oko izraza *fp su neophodne zbog prioriteta operatora. Dereferenciranjem
pokazivača na funkciju dobija se funkcija, koja se onda poziva na uobičajen način.

Primer 8.1 Program demonstrira upotrebu pokazivača na funkcije.


#include <stdio.h>

int kvadrat(int n)
{
return n*n;
}

int kub(int n)
{
return n*n*n;
}

86
Milena Vujošević–Janičić Pokazivači 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čkih funkcija u

87
Milena Vujošević–Janičić Pokazivači na funkcije

tačkama 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šević–Janičić 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čine velicina_elementa_niza korišćenjem funkcije poredi koja upored̄uje
dva elementa niza.
Pošto je void* tip niza niz to obezbed̄uje mogućnost korišćenja ove funkcije za
nizove različitih tipova podataka. Da bi funkcija qsort znala sa kakvim elementima
barata, potrebno joj je proslediti i veličinu jednog elementa u nizu (ovo je ključno
za korak u kome se vriši razmena elemenata niza — funkcija qsort mora da zna
koliko bajtova zauzima jedan element niza kako bi mogla adekvatno da vrši kopiranje
memorije).
Funkcija poredi vrši pored̄enje dva elementa niza. Kada ova funkcija vraća
pozitivnu vrednost ako je prvi element veći od drugog, 0 ako su jednaki i negativnu
vrednost ako je prvi element manji, tada će se niz sortirati u rastućem poretku.
Modifikacijom ove funkcije niz se može sortirati u opadajućem poretku (ukoliko

89
Milena Vujošević–Janičić 8.1 Funkcija qsort iz standardne biblioteke

vraća pozitivnu vrednost ako je prvi manji, 0 ako su jednaki i negativnu vrednost
ako je prvi veci).

Kvalifikator const
Kljucna rec const u programskom jeziku C sluzi za definisanje konstanti. Ako
napisemo:
const int a = 2;
tada se u daljem toku programa promenljivoj a ne moze dodeljivati vrednost. Za
razliku od konstanti definisanih define direktivom ova konstanta ima jasno definisan
tip koji prevodilac proverava prilikom upotrebe.
Treba naglasiti, medjutim, da ako se napiše:
const int * p = &a;
tada se const ne odnosi na promenljivu p, vec na podatak tipa int na koji p
pokazuje. Dakle, nije pokazivač konstantan, već on pokazuje na konstantan podatak.
Na primer:
int b;
p = &b;
je dozvoljeno, ali:
*p = 4;
nije dozvoljeno, zato sto je p pokazivač na konstantan int (odnosno, kroz pokazivač
p nije moguće menjati vrednost koja se nalazi na adresi na koju on ukazuje). Ovo
se moze razumeti tako sto se deklaracija pročita sa desna u levo:
const int * p; /* p je pokazivac na int koji je konstantan*/
Slično bi bilo i ako napišemo:
int const * p; /* p je pokazivac na konstantan int. */
Med̄utim, ako napišemo:
int * const p; /* p je konstantan pokazivac na int. */
Kombinacija ovoga je:
const int * const p; /* p je konstantan pokazivac na int
koji je konstantan.*/
Dakle, u ovom poslednjem slučaju konstantni su i pokazivač i ono na sta on pokazuje.

90
Milena Vujošević–Janičić 8.1 Funkcija qsort iz standardne biblioteke

Upotreba qsort
Primer 8.3 Upotrebom qsort funkcije iz standardne biblioteke izvršiti 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šević–Janičić 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či. Ako se sortira niz stringova, onda svaki element je
sam po sebi pokazivač 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šević–Janičić 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čine velicina_elementa_niza traži element koji ima krakteristiku
na koju ukazuje pokazivač kljuc.
Funkcija poredi može da se razlikuje od funkcije poredi koju prima funkcija
qsort. Njen prvi argument je ključ pretrage, a drugi argument je element niza. To
znači da ova dva argumenta mogu ali i ne moraju da budu istog tipa. Na primer,
ako u nizu studenata tražimo onog studenta koji ima indeks mm08123 tada je ključ
tipa char* a element niza je tipa strukture student, znači ključ pretrage i element
niza su različitog tipa, pa u skladu sa tim funkcija pored̄enja prima dva argumenta
različitog tipa. S druge strane, ako u nizu studenata tražimo onog studenta koji ima
indeks mm08123, i ime Pera i prezime Peric, tada je ključ istog tipa kao i element
niza, i tada funkcija pored̄enja prima argumente istog tipa (i analogna je funkciji
pored̄enja koja se piše za funkciju qsort). Dakle, pored̄enje se vrši po ključu koji
može biti istog tipa kao i elementi niza ali i ne mora.
Funkcija poredi treba da vrati vrednost 0 ako je prvi argument (ključ pretrage)
veći (u smislu ključa pretrage) od drugog argumenta (koji je element niza), 0 ako su
jednaki (u smislu ključa pretrage) i broj manji od 0 ako je prvi argument manji (u
smislu ključa pretrage) od drugog argumenta, ukoliko su elementi u nizu niz ured̄eni

93
Milena Vujošević–Janičić 8.2 Funkcija bsearch iz standardne biblioteke

u rastućem redosledu u odnosu na dati ključ. Funkcija bsearch vraća pokazivač na


pronad̄eni element ili NULL ukoliko element sa datom vrednošću ključa ne postoji.

Primer 8.5 Binarno pretraživanje - korišćenje 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či. Program broji pojavljivanja svake od ključnih
reči programskog jezika C. Na kraju se reči ispisuju opadajuće 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šević–Janičić 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šević–Janičić 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šević–Janičić 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će 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
što se kao treći argument ne predaje dužina niza već adresa celobrojne promenljive
u kojoj se nalazi dužina niza. Ovo je zato što funkcija lsearch() ukoliko linearnom
pretragom ne pronad̄e element koji se traži, umeće traženi element na kraj niza,
a dužinu niza uvećava za jedan (sto čini tako sto promenljivoj pozivajuće funkcije
pristupa preko pokazivaca i menja je). Takod̄e, pošto postiji mogućnost umetanja
elementa u niz, kao ključ pretrage potrebno je koristiti ključ koji je istog tipa kao što
su to elementi niza. Funkcija za uporedjivanja elemenata na koju pokazuje pokazi-
vac comp treba da zadovoljava ista pravila kao i kod funkcije bsearch. Medjutim,
s obzirom da se kod linearne pretrage koristi isključivo pored̄enje na jednakost, do-
voljno 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še isto kao i lsearch, s tom razlikom sto ne umeće novi element u slučaju
neuspešne pretrage, već vraća NULL (funkcija lsearch u slučaju neuspešne pretrage
umeće traženi element na kraj i vraća njegovu adresu). Za funkciju lfind moguće je
koristiti ključ pretrage koji nije istog tipa kao elementi niza, u tom slučaju potrebno
je adekvatno definisati funkciju pored̄enja.

8.4 Pokazivači
Primer 8.7 Različite deklaracije:
int *p; pokazivac na int
int **p; pokazivac na pokazivac na tip int

int* p[10]; niz od 10 elemenata tipa pokazivaca na int


int *p[10]; niz od 10 elemenata tipa pokazivaca na int

97
Milena Vujošević–Janičić 8.4 Pokazivači

int p[5][10]; pet nizova od po 10 elemenata tipa int


int (*p)[10]; pokazivac na niz od deset celobrojnih vrednosti

int* p(); funkcija bez argumenata koja vraca pokazivac na int


int *p(); funkcija bez argumenata koja vraca pokazivac na int

int (*p)(); pokazivac na funkciju bez argumenata koja vraca int


int *(*p)(); pokazivac na funkciju bez argumenata koja vraca pokazivac na int
int* (*p)(); 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čka alokacija memorije

Do sada smo memoriju za podatke rezervisali na dva načina1 :


Statička alokacija — podaci se kreiraju statički prilikom pokretanja programa
i ostaju u memoriji do kraja njegovog izvršavanja. Ovakvu alokaciju imaju
globalne promenljive (promenljive definisane van svake funkcije ili bloka) kao i
statičke lokalne promenljive (deklarisane ključnom rečju 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šanje imaju
sve lokalne promenljive koje nisu deklarisane kao static. Prostor za ove
promenljive se rezerviše na programskom steku.
Glavni nedostatak oba ova pristupa je to što programer u vreme pisanja programa
mora da predvidi koliko će mu prostora biti potrebno za podatke. Često tako nešto
nije moguće, jer dimenzije problema mogu varirati. Drugi nedostatak je to što je
životni vek svakog podatka jasno definisan gornjim pravilima. Programer može želeti
da nakon kreiranja nekog objekta u memoriji on ostane tu dokle god je potreban,
te da ga kasnije ukloni kada više za njim nema potrebe.
Svi ovi problemi rešavaju se dinamičkom alokacijom memorije. Pored statičke
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čine. Taj prostor
se kasnije može (i treba) osloboditi, kada programer više nema potrebu da koristi
taj prostor.
U programskom jeziku C, ovo se može 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ća adresu početka
tog prostora ili vraća vrednost NULL ukoliko zahtev ne može da se ispuni. Jedini način
da se ovom prostoru pristupi je preko pokazivača (jer ne postoji ime promenljive kao
kod statičkih 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šević–Janičić Dinamička alokacija memorije

void * calloc(int n, int s);


Ova funkcija alocira prostor za n susednih elemenata veličine s bajtova (zapravo
alocira n*s bajtova). Ova funkcija još i inicijalizuje rezervisani prostor na vrednost
0, za razliku od malloc() funkcije koja prostor ostavlja neinicijalizovan. Funkcija
void * realloc(void *p, int n);
vrši realokaciju prostora na koji pokazuje pokazivač p za koji je memorija prethodno
bila dinamički alocirana. Alocira se n bajtova, kopira se stari sadržaj u novi prostor,
a zatim se stari prostor oslobad̄a. Ova funkcija se obično koristi da bi se proširio
postojeći dinamički alocirani prostor.
Prostor alociran prethodnim funkcijama se može 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čima, ne može se proizvoljan prostor oslobad̄ati ovom funkcijom, već
samo prostor na hipu alociran (m|c|re)alloc()-om). Kada se jednom dealocira,
prostor više ne sme da se koristi (vrlo je verovatno da će ga kasnije funkcija koja
vrši alokaciju dodeliti nekom drugom).
Trenutak oslobad̄anja prostora na hipu odred̄uje programer. Nebitno je da li
smo izašli 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že koristiti i u drugim funkcijama sve dok imamo
pokazivač kojim možemo da joj pristupimo.
Tiha greska je ako ”izgubimo” pokazivač na dinamički alocirani prostor, a da ga
nismo prethodno dealocirali. U ovom slučaju taj prostor ostaje rezervisan do kraja
izvršavanja programa, bez mogućnosti da ga koristimo ili da ga kasnije obrišemo
(jer nemamo njegovu adresu). Ovaj fenomen se naziva ”curenje memorije” (eng.
memory leak). Odgovornost je programera kao i odlika dobrog stila da se dinamički
prostor uvek oslobodi kada više nije potreban. Takod̄e, greška je i ukoliko programer
pokuša dva puta da oslobodi istu memoriju ili da pristupi memoriji koja je već
oslobod̄ena (ovo dovodi do greške prilikom izvršavanja programa).
Primer 9.1 Dinamička 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šević–Janičić Dinamička 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 memo-
rije 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šević–Janičić Dinamička 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ćila pozivajućoj funkciji da koristi vrednosti niza
koji je kreirala, neophodno je da memorija kreiranog niza bude dinamički alocirana
kako bi nastavila da postoji i nakon završetka rada funkcije. U tom slučaju, pozi-
vajuća 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šević–Janičić Dinamička 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ča na nizove različitih dužina.
#include <stdio.h>
#include <stdlib.h>
main()
{
/* Niz od tri elemenata tipa int*/
int nizi[3];

103
Milena Vujošević–Janičić Dinamička 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šević–Janičić 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čka 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šević–Janičić 9.1 Matrice

}
}
Ukoliko je potrebna veća fleksibilnost, tj. da se dimenzije matrice mogu uneti kao
parametri programa, tada je neophodno koristiti dinamičku 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šević–Janičić 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ći pokazivač na pokazivač.
1. Definišemo pokazivač na pokazivač na int:
int **p;
Ovaj pokazivač će čuvati adresu prvog u nizu dinamički alociranih pokazivača
na int-ove.
2. Dinamički alociramo niz od m pokazivača na int-ove, i adresu prvog u nizu
smeštamo u promenljivu p:
p = (int**) malloc(sizeof(int*) * m));
Ovi pokazivači služe da pokazuju na prve elemente nizova int-ova koji pred-
stavljaju dinamički alocirane vrste.
3. Svaku vrstu alociramo posebnim malloc-om, i povratnu adresu smeštamo u i-ti
pokazivač u malopre alociranom nizu pokazivača.
for(i = 0; i < m; i++)
p[i] = malloc(sizeof(int) * n);
Primetimo da nizovi int-ova koji se alociraju nisu morali biti jednakih dimen-
zija (ne moraju svi biti duzine n, kao što je ovde rečeno). Ovo može da bude
korisno, ako znamo da će u nekoj vrsti biti korišćeno samo nekoliko prvih ele-
menata, tada možemo alocirati manje za tu vrstu, i tako uštedeti prostor.

107
Milena Vujošević–Janičić 9.1 Matrice

4. U toku rada se elementima ovako alocirane matrice pristupa sa p[i][j] – p[i]


je i-ti pokazivač u nizu, a kako on pokazuje na početni 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 čega se obriše i niz pokazivača
(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šević–Janičić 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ôd 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šević–Janičić 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šević–Janičić 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šević–Janičić 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čunati 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šević–Janičić 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šević–Janičić 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čki niz.
/* Program za svaku rec unetu sa standardnog
ulaza ispisuje broj pojavljivanja.
Verzija sa dinamickim nizom i realokacijom.
*/

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

114
Milena Vujošević–Janičić 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šević–Janičić 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šević–Janičić 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 čvorova.


Svaki čvor sadrži podatak (odred̄enog tipa) i pokazivač na sledeći čvor u sekvenci.
Prvi čvor u sekvenci naziva se glava liste. Ostatak liste (bez glave) je takod̄e lista,
i naziva se rep liste. Lista koja ne sadrži čvorove naziva se prazna lista. Prilikom
baratanja listom mi čuvamo pokazivač na glavu liste. Kada pristupimo glavi liste, u
njoj imamo zapisanu adresu sledećeg elementa, pa mu samim tim mozemo pristupiti.
Kada mu pristupimo, u njemu je sadržana adresa sledećeg elementa, pa preko tog
pokazivača možemo da mu pristupimo, itd. Poslednji element u listi nema sledeći
element: u tom slučaju se njegov pokazivač na sledeći postavlja na NULL. Takod̄e,
prazna lista se predstavlja NULL pokazivačem.
Prednost korišćenja povezanih lista u odnosu na dinamički niz je u tome sto se
elementi mogu efikasno umetati i brisati sa bilo koje pozicije u nizu, bez potrebe
za realokacijom ili premeštanjem elemenata. Nedostatak ovakvog pristupa je to što
ne možemo nasumično pristupiti proizvoljnom elementu, već 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ćnost da glava liste bude promenjena, tj. da to postane
neki drugi čvor (sa drugom adresom). U tom slučaju se pokazivač na glavu liste mora
ažurirati. Kada promenu liste obavljamo u posebnoj funkciji (kao što je bio slučaj u
prethodnom primeru, gde smo za dodavanje i brisanje imali posebne funkcije) onda
je potrebno da se pozivajućoj funkciji vrati informacija o promeni adrese glave,
kako bi pozivajuća funkcija mogla da ažurira svoju pokazivačku promenljivu. Ovo
se može uraditi na dva načina:
1. Pozvana funkcija koja vrši promenu na listi vraća kao povratnu vrednost adresu
glave nakon promene. Ova adresa može biti ista kao i pre promene (ako glava
nije dirana) a može se i razlikovati. U svakom slučaju, ta adresa treba da se
dodeli pokazivačkoj promenljivoj koja čuva adresu glave u pozivajućoj funkciji.
1 Tekst preuzet sa sajta www.matf.bg.ac.rs/ milan

118
Milena Vujošević–Janičić 10.1 Implementacija

Zbog toga se funkcija za promenu liste uvek poziva na sledeći nacin:


pok = funkcija_za_promenu(pok, ...);
tj. promenljivoj čija se vrednost predaje kao adresa glave u pozivu mora se do-
deliti povratna vrednost funkcije, za slučaj da je adresa glave interno promen-
jena.
2. Pozvana funkcija koja vrši promenu na listi prihvata kao argument pokazivač
na pokazivačku promenljivu koja u pozivajućoj funkciji čuva adresu glave i
koju eventalno treba ažurirati. Sada pozvana funkcija može interno da preko
dobijenog pokazivača promeni promenljivu pozivajuće 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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, definisane
su rekurzivno. Primetno je da su ove rekurzivne varijante mnogo jednostavnije.
Razlog je to što su liste po svojoj prirodi rekurzivne strukture. Naime, liste se mogu
rekurzivno definisati na sledeći način:
• objekat označen 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žemo 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žemo zapisati kao [b a1 a2 ... an].

128
Milena Vujošević–Janičić 10.1 Implementacija

Glava liste je b, a rep liste je lista L. Sada se obrada liste može razdvojiti na dva
dela:
• Izvršiti operaciju nad glavom liste
• Izvršiti rekurzivni postupak nad repom liste (koji je takod̄e lista).
Za testiranje narednog programa može 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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či 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šević–Janičić 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šević–Janičić 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šević–Janičić 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čina. Prvi način je da se premeštaju čvorovi u listi (što zahteva prevezivanje
pokazivača), a drugi je da se premeštaju vrednosti koje čvorovi sadrže. Prvi način
je komplikovaniji, ali je često efikasniji, naročito ako su objekti koji se čuvaju kao
podaci u čvorovima veliki.
U oba slučaja se može koristiti rekurzija, a može se realizovati i iterativno. U
ovom primeru ilustrujemo oba načina sortiranja lista (prvi rekurzivno, a drugi iter-
ativno).
#include <stdio.h>
#include <stdlib.h>

138
Milena Vujošević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 10.2 Kružna (ciklična) 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žna (ciklična) lista

početak ciklične liste

A B C ... Z

Slika 10.1: Kružna lista

Primer 10.5 (februar 2006.) Grupa od n plesača (na čijim kostimima su u smeru
kazaljke na satu redom brojevi od 1 do n) izvodi svoju plesnu tačku tako što formiraju
krug iz kog najpre izlazi k-ti plesač (odbrojava se počev od plesača označenog brojem
1 u smeru kretanja kazaljke na satu). Preostali plesači obrazuju manji krug iz kog
opet izlazi k-ti plesač (odbrojava se pocev od sledećeg suseda prethodno izbačenog,
opet u smeru kazaljke na satu). Izlasci iz kruga se nastavljaju sve dok svi plesači

143
Milena Vujošević–Janičić 10.3 Red

ne budu isključeni. Celi brojevi n, k (k < n) se učitavaju sa standardnog ulaza.


Napisati program koji će na standardni izlaz ispisati redne brojeve plesača u redosledu
napuštanja kruga.
PRIMER: za n = 5, k = 3 redosled izlaska je 3 1 5 2 4.

10.3 Red

početak reda (brisanje — get)


kraj reda (dodavanje — add)

A B ... X NULL

novi element

početak reda
novi kraj reda

A B ... X Y NULL

početak reda nakon brisanja kraj reda

B ... X Y NULL

Slika 10.2: Red

Red (eng. queue) je struktura podataka nad kojom su definisane 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 definise 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šević–Janičić 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 bro-
jeve 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 10.3 Red

/* Oslobadjamo red */
oslobodi_red(&pocetak, &kraj);

return 0;
}
Primer 10.7 Program formira celobrojni red i ispisuje sadržaj 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šević–Janičić 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šević–Janičić 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 definisane 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 definise 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čka implementacija steka (pretpostavka da se na ulazu neće naći više od 100
otvorenih zagrada za redom).
#include <stdio.h>
#include <stdlib.h>
#define MAX_ZAGRADA 100

int odgovarajuce(char z1, char z2) {


return (z1 == ’(’ && z2 == ’)’) ||
(z1 == ’{’ && z2 == ’}’) ||
(z1 == ’[’ && z2 == ’]’);
}

main()
{
int c;
char otv_zagrade[MAX_ZAGRADA];
int br_otv = 0;

while((c=getchar()) != EOF)
{
switch(c)

151
Milena Vujošević–Janičić 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 up-
arene.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define MAX 100

#define OTVORENA 1
#define ZATVORENA 2

#define VAN_ETIKETE 0
#define PROCITANO_MANJE 1

152
Milena Vujošević–Janičić 10.4 Stek

vrh steka (i dodavanje i brisanje — push i pop)

X Y ... A NULL

novi element

novi vrh steka

Y X V ... A NULL

vrh steka nakon brisanja

X V ... A 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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žaj 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šević–Janičić 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šević–Janičić 10.5 Dvostruko povezane liste

10.5 Dvostruko povezane liste

početak dvostruko povezane liste kraj liste

...
NULL A B C ... Z NULL

Slika 10.4: Dvostruko povezana lista

Dvostruko povezana lista je struktura podataka koja se sastoji od sekvence čvorova.


Svaki čvor sadrži podatak (odred̄enog tipa) i pokazivače na prethodni i sledeći čvor
u sekvenci. Za razliku od obične liste, u svakom čvoru imamo zapisanu i adresu
prethodnog elementa, pa se kroz listu možemo kretati i unazad. Poslednji element u
listi nema sledeći element: u tom slučaju se njegov pokazivač na sledeći postavlja na
NULL. Slično, glava liste nema prethodni element, pa je njen pokazivač na prethodni
postavljen na NULL. Takod̄e, prazna lista se predstavlja NULL pokazivačem.
Prednost dvostruko povezane liste u odnosu na jednostruko povezanu je u tome
sto se možemo kretati u oba smera kroz listu. Nedostatak je to što se veličina
memorije potrebne za čuvanje čvora povećava za veličinu jednog pokazivača.
Primer 10.11 Program demonstrira rad sa dvostruko povezanim listama. Za testi-
ranje narednih funkcija može 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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 čvorova koji su povezani na sledeći način:


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

166
Milena Vujošević–Janičić 11.1 Binarno pretraživačko stablo

NULL NULL

NULL NULL NULL

NULL NULL

Slika 11.1: Binarno stablo

U programiranju se stabla obično koriste kao strukture podataka, tako sto svaki
čvor sadrži po jedan podatak odred̄enog tipa. Način raspored̄ivanja podataka u
stablu zavisi od konkretne primene stabla. S obzirom na rekurzivnu prirodu stabla,
uobičajeno je da se u programiranju stabla obrad̄uju rekurzivno.

11.1 Binarno pretraživačko stablo


Posebna vrsta stabla su tzv. binarna pretraživačka stabla. Ova stabla imaju osobinu
da za svaki čvor vazi sledeće: svi čvorovi njegovog levog podstabla sadrže podatke
sa manjom vrednošću od vrednosti podatka u tom čvoru, dok svi čvorovi njegovog
desnog podstabla sadrže podatke sa većom vrednošću od vrednosti podatka u tom
čvoru. Ovakva organizacija omogu ’cava efikasno pretraživanje.
Primer 11.1 Program demonstrira rad sa binarnim pretrazivackim drvetima čiji su
podaci celi brojevi. Drvo sadrži cele brojeve sortirane po veličini. 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šević–Janičić 11.1 Binarno pretraživačko stablo

17, 12, 21, 15, 5, 14


17

12 21

NULL NULL

5 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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 prethodnih funkcija */


main()
{
cvor* koren = NULL;
koren = ubaci_u_drvo(koren, 1);
koren = ubaci_u_drvo(koren, 8);
koren = ubaci_u_drvo(koren, 5);
koren = ubaci_u_drvo(koren, 3);
koren = ubaci_u_drvo(koren, 7);
koren = ubaci_u_drvo(koren, 6);
koren = ubaci_u_drvo(koren, 9);

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

172
Milena Vujošević–Janičić 11.1 Binarno pretraživačko 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živačko drvo - funkcije za izračunavanje kopije, unije,
preseka i razlike dva drveta, funkcija za izbacivanje čvora 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko stablo

return 0;

}
Primer 11.3 Primer demonstrira sortiranje niza pomoću binarnog stabla. Elementi
niza se redom prvo smeste u binarno stablo pretrage, a zatim se elementi ”pokupe”
iz stabla obilazeći ga sa leva u desno (tj. u rastućem poretku). Vremenska složenost
ovakvog algoritma je u ekvivalentna quick_sort()-u. Prostorna slozenost je nesto
veća, jer sortiranje nije ”u mestu”, već se koristi pomoćna 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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 čita tekst i ispisuje broj pojavljivanja svake od reči
koje su se javljale u tekstu. Radi poboljšanja efikasnosti, prilikom brojanja reči koristi
se struktura podataka pogodna za leksikografsku pretragu — u ovom slučaju binarno
pretraživačko drvo.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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č i vrednost unapred odred̄enog (ne obavezno
istog) tipa (na primer, ključ nam može biti string koji predstavlja ime studenta, a
vrednost je npr. broj koji predstavlja njegov prosek). Operacije koje mapa mora

192
Milena Vujošević–Janičić 11.1 Binarno pretraživačko stablo

efikasno da podržava su:


• dodavanje para (kljuc, vrednost) u mapu
• brisanje para (kljuc, vrednost) iz mape
• pronalazenje vrednosti za dati ključ
• promena vrednosti za dati ključ
Jedan od najčešćih načina implementacije mape je preko binarnog stabla pretrage.
Svaki čvor ovog stabla sadržaće odgovarajući par (kljuc, vrednost), tj. svaki čvor
ce sadržati dva podatka. Pri tom će poredak u stablu biti odred̄en poretkom koji je
definisan nad ključevima. Ovim se postiže da se pretraga po ključu može obaviti na
uobičajen način, kao i sve ostale navedene operacije.
Jasno je da ovako implementirana mapa neće efikasno podržavati operaciju pre-
trage po vrednosti (npr. naći sve ključeve koji imaju datu vrednost), zato što poredak
u stablu nije ni na koji način u vezi sa poretkom med̄u vrednostima. Med̄utim, u
mapi se najčešće i ne zahteva takva operacija.
Sledi primer koji demonstrira implementaciju mapa pomoću 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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 leksikogra-
fskom 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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ćem poretku po frekvencijama.
Radi poboljšanja efikasnosti, prilikom brojanja pojavljivanja etiketa koristi se

198
Milena Vujošević–Janičić 11.1 Binarno pretraživačko stablo

struktura podataka pogodna za leksikografsku pretragu - u ovom slučaju binarno pre-


traživačko drvo.
Na kraju rada, čvorovi drveta se ”presortiraju” na osnovu broja pojavljivanja.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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 Efikasnije sortiranje čorova stabla — formira se niz pokazivača na
čvorove stabla koji se u skladu sa funkcijom pored̄enja sortiraju pomoću bibliotečke
qsort funkcije.
#include <stdlib.h>
#include <stdio.h>

204
Milena Vujošević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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šević–Janičić 11.1 Binarno pretraživačko 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 čvorova i skupa E grana. Grane predstavljaju


relacije izmed̄u čvorova i odgovara paru čvorova. Graf može biti usmeren (orijen-
tisan), 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š važi da je v1 = vn, tada se ovakav put naziva ciklus.
Iako su liste i stabla specijalni oblici grafova, u programiranju postoji bitna raz-
lika: liste i stabala se prevashodno koriste kao kontejneri za podatke (u svaki cvor
se smešta 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čin
i rešili primenom algoritama nad odgovarajućim grafom. Na primer, čvorovima u
grafu se mogu predstaviti raskrsnice u gradu, a granama ulice koji ih povezuju. Tada
se zadatak nalaženja najkraćeg puta od tacke A do tacke B u gradu može svesti na
nalaženje najkraćeg puta od cvora u do čvora v u grafu. Slično se mogu rešavati
problemi sa računarskim mrežama (računari se predstave čvorovima, a veze med̄u
njima granama), itd.
Grafovi se mogu predstavljati na više načina. Uobičajena su dva načina pred-
stavljanja grafova. To su matrica povezanosti grafa i lista povezanosti.
Matrica povezanosti je kvadratna matrica dimenzije n, pri čemu je n broj čvorova
u grafu, takva da je element na preseku i-te vrste i j-te kolone jednak jedinici uko-
liko postoji grana u grafu od i-tog do j-tog čvora, inače je nula. Jasno je da
ce neusmerenim grafovima odgovarati simetrične matrice. Prednost ovakvog pred-
stavljanja je jednostavnost. Nedostatak ovakvog pristupa je neracionalno korišćenje
memorije u slučaju ”retkih” grafova (grafova sa malo grana), gde ce najveći broj
elemenata matrice biti 0.
Umesto da se i sve nepostojeće 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 čvoru se pridružuje povezana lista, koja sadrži sve grane
susedne tom čvoru. Graf je predstavljen vektorom lista. Svaki elemenat vektora
1 Tekst i primeri preuzeti od Jelene Tomašević, www.matf.bg.ac.yu/~jtomasevic, i Milana
Bankovića www.matf.bg.ac.yu/~milan zasnovano na materijalu Algoritmi, Miodrag Živković i
http://www.matf.bg.ac.yu/~filip

209
Milena Vujošević–Janičić Grafovi

sadrži ime (indeks) čvora i pokazivač na njegovu listu čvorova. Ovakav pristup stedi
memoriju, a omogucava i efikasno dodavanje novih cvorova i grana.
Osnovni problem koji se javlja kod grafova jeste kako polazeci od nekog cvora, kre-
tanjem kroz puteva grafa posetiti sve cvorove. Ovakav postupak se naziva obilazak
grafa. Postoje dva osnovna algoritma za obilazak grafa: pretraga u dubinu (DFS,
skraćenica od depth-first-search) i pretraga u širinu (BFS, skraćenica od breadth-
first-search).
Kod DFS algoritma, obilazak započinje iz proizvoljnog zadatog čvora r koji se
naziva koren pretrage u dubinu. Koren se označava kao posećen. Zatim se bira
proizvoljan neoznačen čvor r1, susedan sa r, pa se iz čvora r1 rekurzivno startuje
pretraga u dubinu. Iz nekog nivoa rekurzije izlazi se kad se naid̄e na čvor v kome su
svi susedi već označeni.
Primer 12.1 Primer reprezentovanja grafa preko matrice povezanosti. U programu
se unosi neorijentisan graf i DFS algoritmom se utvrd̄uju čvrovi koji su dostižni iz
čvora 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šević–Janičić 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šević–Janičić 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 fiksiranom konstantom, tako da se odgovarajuce
matrice alociraju staticki. Takod̄e, da bi se pojednostavile liste argumenata pojedinih
funkcija, većina promenljivih u ovom primeru se deklarišu kao globalne, kako bi kao
takve bile dostupne svim funkcijama.
#include <stdio.h>
#include <stdlib.h>

212
Milena Vujošević–Janičić 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šević–Janičić 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šević–Janičić Grafovi

int red[MAX_CVOROVA];
int sledeci_na_redu = 0;
int poslednji_na_redu = -1;
int 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šević–Janičić 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 čvorova
grafa. U programu se unosi graf i DFS algoritmom se utvrdjuje koji su čvorovi
dostižni iz cvora 0.
#include <stdlib.h>
#include <stdio.h>

/* Cvor liste suseda */

216
Milena Vujošević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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šević–Janičić 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