Upload
dinhkien
View
217
Download
0
Embed Size (px)
Citation preview
Programiranje 2Programski jezik C
— Zadaci sa vezbi —
Milena Vujosevic - Janicic 2008/2009
Predgovor
Ovo je prateci materijal za vezbe koje drzim iz predmenta Programiranje 2. On nemoze zameniti pohadanje vezbi niti koriscenje druge preporucene literature.
Deo materijala cine zadaci i resenja mr Filipa Marica (raspolozivi nawww.matf.bg.ac.yu/~filip/pp/0405/index.pl).Takode koriscen je i materijal sa sajta koleginice Jelene Grmusewww.matf.bg.ac.yu/~jelenagr
kolege Miroslava Maricawww.matf.bg.ac.yu/~maricm
i kolege Milana Bankovicawww.matf.bg.ac.yu/~milan.Tekstovi i objasnjenja su uglavnom zasnovani na knjizi Programski jezik C, autoraKerninghan & Ritchie.
Zahvaljujem svojim studentima na aktivnom ucescu u nastavi cime su mi pomogliu uoblicavanju ovog materijala.
Svi komentari i sugestije vezane za ovaj materijal bice veoma dobrodosli.
Milena Vujosevic-Janicicwww.matf.bg.ac.yu/~milena
1
Sadrzaj
1 Argumenti komandne linije 4
2 Datoteke 7
3 Bitski operatori 19
4 Vezbanje 304.1 Polinomi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304.2 Veliki brojevi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5 Sortiranje 405.1 Vremenska slozenost . . . . . . . . . . . . . . . . . . . . . . . . . . . 405.2 Selection sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415.3 Bubble sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435.4 Insertion sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455.5 Razni zadaci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6 Pokazivaci 586.1 Pokazivacka aritmetika — primeri . . . . . . . . . . . . . . . . . . . . 596.2 Niska karaktera i pokazivac na konstantnu nisku . . . . . . . . . . . . 646.3 Pokazivaci na pokazivace . . . . . . . . . . . . . . . . . . . . . . . . . 656.4 Nizovi pokazivaca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
7 Rekurzija 697.1 Osnovni primeri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 707.2 Binarna pretraga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 817.3 MergeSort algoritam . . . . . . . . . . . . . . . . . . . . . . . . . . . 827.4 QuickSort algoritam . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
8 Pokazivaci na funkcije 868.1 Funkcija qsort iz standardne biblioteke . . . . . . . . . . . . . . . . . 898.2 Funkcija bsearch iz standardne biblioteke . . . . . . . . . . . . . . . 938.3 Funkcije lsearch i lfind . . . . . . . . . . . . . . . . . . . . . . . . 978.4 Pokazivaci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
2
SADRZAJ SADRZAJ
9 Dinamicka alokacija memorije 999.1 Matrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1059.2 Realokacija memorije . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
10 Liste 11810.1 Implementacija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11810.2 Kruzna (ciklicna) lista . . . . . . . . . . . . . . . . . . . . . . . . . . 14310.3 Red . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14410.4 Stek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15110.5 Dvostruko povezane liste . . . . . . . . . . . . . . . . . . . . . . . . . 160
11 Stabla 16611.1 Binarno pretrazivacko 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 Vujosevic–Janicic 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 resenje.
#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 Vujosevic–Janicic 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 Vujosevic–Janicic 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);
}
Pokazivaci stdin, stdout i stderr su definisani u okviru stdio.h i mogu sekoristiti za pisanje na standardni ulaz, izlaz i izlaz za gresku.
FILE* stdin;
FILE* stdout;
FILE* stderr;
Primer 2.2 Program demonstrira ”a” - append mod datoteka - nadovezivanje.
#include <stdio.h>
8
Milena Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic Datoteke
}
/* Zatvaramo datoteke */
fclose(in);
fclose(out);
}
Primer 2.5 Program ilustruje rad sa datotekama. Program kopira datoteku cije seime zadaje kao prvi argument komandne linije u datoteku cije se ime zadaje kaodrugi 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 Vujosevic–Janicic Datoteke
exit(1);
}
if ((in = fopen(argv[1],"r")) == NULL)
{
fprintf(stderr, "Neuspesno otvaranje datoteke %s\n", argv[1]);
exit(1);
}
if ((out = fopen(argv[2],"w")) == NULL)
{
fprintf(stderr, "Neuspesno otvaranje datoteke %s\n", argv[2]);
exit(1);
}
line_num = 1;
/* Citamo liniju po liniju sa ulaza*/
while (fgets(line, MAX_LINE, in) != NULL)
{
/* Ispisujemo broj linije i sadrzaj linije na izlaz */
fprintf(out, "%d :\t", line_num++);
fputs(line, out);
}
/* Zatvaramo datoteke */
fclose(in);
fclose(out);
}
Primer 2.6 Prodavnica - ilustruje citanje niza struktura iz tektsualne datoteke.
/* Datoteka, cije se ime zadaje kao argument komandne linije
ili ako se ne zada onda se ime unosi sa standardnog
ulaza, sadrzi podatke o proizvodima koji se prodaju
u okviru odredjene prodavnice. Svaki
proizvod se odlikuje sledecim podacima:
bar-kod - petocifreni pozitivan broj
ime - niska karaktera
cena - realan broj zaokruzen na dve decimale
pdv - stopa poreza - realan broj zaokruzen na dve decimale
Pretpostavljamo da su podaci u datoteci
korektno zadati.
Pretpostavljamo da se u prodavnici ne
prodaje vise od 1000 razlicitih artikala
Na standardni izlaz ispisujemo podatke o
12
Milena Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic Datoteke
}
br_artikala=0;
/* Ucitavamo artikle */
while (ucitaj_artikal(f, &artikli[br_artikala]))
br_artikala++;
/* Ispisujemo podatke o svim artiklima */
ispisi_artikle(artikli, br_artikala);
/* Zatvara se datoteka*/
fclose(f);
}
Primer 2.7 Program u datoteku cije se ime zadaje kao prvi argument komandnelinije upisuje prvih N prirodnih brojeva (N se zadaje sa standardnog ulaza) i zatimizracunava duzinu 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 Vujosevic–Janicic Datoteke
/* Upisuje se sadrzaj u datoteku*/
for(i=0; i<n; i++)
fprintf(f, "%d ", i);
printf("Velicina datoteke je %ld bajtova\n", filesize(f));
fclose(f);
}
/*Funkcija koja racuna duzinu datoteke*/
long filesize(FILE *f)
{
long trenutna_pozicija, duzina;
/*Funkcija ftell vraca trenutnu poziciju u datoteci*/
/*Pamti se trenutna pozicija da bismo
mogli na nju da se vratimo*/
trenutna_pozicija = ftell(f);
/* Funkcija fseek se pozicionira na odredjeno
mesto u datoteci i ima sledece argumente
1. FILE* f - datoteka sa kojom se radi
2. int offset - rastojanje u bajtovima
od odgovarajuce pozicije
3. int origin - pozicija u odnosu na koju se gleda offset
i moze da ima vrednost jedne od
sledecih konstanti
SEEK_END - kraj
SEEK_SET - pocetak
SEEK_CUR - trenutna pozicija */
/*pozicioniranje na kraj fajla*/
fseek(f, 0, SEEK_END);
/*uzimamo poziciju u broju bajtova od
pocetka fajla, to je duzina*/
duzina = ftell(f);
/*vracamo se nazad na zapamcenu poziciju*/
fseek(f, trenutna_pozicija, SEEK_SET);
return duzina;
}
Primer 2.8 Napisati program koji u datoteci cije se ime zadaje kao prvi argument
16
Milena Vujosevic–Janicic Datoteke
komandne linije zamenjuje sva pojavljivanja niske koja se zadaje kao drugi argumentkomandne linije niskom koja se zadaje kao treci argument komandne linije (pret-postaviti da ce obe niske iste duzine i da nisu duze od 20 karaktera). Na primer, akose program pokrene saa.out datoteka.txt ana Ana
tada se svako pojavljivanje reci 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 Vujosevic–Janicic 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 vazno je razlikovati ih odlogickih 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 Vujosevic–Janicic 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 imavrednost 1.
#include <stdio.h>
/* Pozicije brojimo kao u sledecem primeru:
poz: ... 10 9 8 7 6 5 4 3 2 1 0
20
Milena Vujosevic–Janicic 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 vrsi se pomeranje sadrzaja operanda a predstavljenog u binarnomobliku za b mesta u desno. Popunjavanje upraznjenih mesta na levoj strani zavisi odtipa podataka i vrste racunara. Ako se pomeranje primenjuje nad operandom tipaunsigned popunjavanje je nulama. Ako se radi o oznacenom operandu popunjavanjeje jedinicama kada je u krajnjem levom bitu jedinica, a nulama kada je u krajnjemlevom bitu nula.
21
Milena Vujosevic–Janicic Bitski operatori
Da li vazi sledeca jednakost (za prirodan broj k):((~0) >> k) == (~0)?Posto je 0 konstanta celobrojnog tipa, to je ~0 takodje tipa int, pri cemu je tobroj koji je zapisan svim jedinicama. Prema tome, njegovim siftovanjem u desnodobijamo isti taj broj, jer se upraznjen prostor sa desne strane popunjava jedinicama.
VAZNO:Vrednost izraza a<<b je jednaka vrednosti a · 2b.Vrednost izraza a>>b je jednaka vrednosti a/2b.Bitovski operatori su efikasniji od operacija * i / tako da svaki put kad se javipotreba za mnozenjem ili deljenjem stepenom broja dva, treba koristiti operacije <<i >>.
Primer 3.5 sum of bits - izracunava sumu bitova datog neoznacenog 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 Vujosevic–Janicic 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 pojedinacnih 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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. Sta ce biti vrednost promenljive c posle izrazadodele
• c=(15|3)>>1;?
• c=(15 & 3)<<1; ?
Primer 3.9 Napisati program koji sa standardnog ulaza uccitava pozitivan ceo broj,a na standardni izlaz ispisuje vrednost tog broja sa razmenjenim vrednostima bitovana pozicijama i, j. Pozicije i, j se ucitavaju kao parametri komandne linije. Sma-trati da je krajnji desni bit binarne reprezentacije 0-ti bit. Pri resavanju nije dozvol-jeno koristiti pomocni 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 Vujosevic–Janicic 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 jedinicana parnim i neparnim pozicijama u binarnom zapisu broja n.
PRIMER: za n = 19 = (10011)2 izlaz je 1.Napisati program koji pozivom ove funkcije racuna i stampa razliku broja jedinica
za sve argumente komandne linije (pretpostavka je da su argumenti komandne linijezadati korektno).
#include <stdio.h>
#include <stdlib.h>
int par_nepar(int n)
{
28
Milena Vujosevic–Janicic 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
Vezbanje
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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 4.2 Veliki brojevi
ispisiPolinom(&r);
}
4.2 Veliki brojevi
Primer 4.2 Program ilustruje rad sa velikim celim brojevima. Brojevi se internoreprezentuju 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 moze biti sortiran ili uredjen u
• opadajucem poretku:
niz[0] > niz[1] > ... > niz[i] > niz[i + 1] > ... > niz[n − 1]
• rastucem poretku:
niz[0] < niz[1] < ... < niz[i] < niz[i + 1] < ... < niz[n − 1]
• neopadajucem poretku:
niz[0] ≤ niz[1] ≤ ... ≤ niz[i] ≤ niz[i + 1] ≤ ... ≤ niz[n − 1]
• nerastucem poretku:
niz[0] ≥ niz[1] ≥ ... ≥ niz[i] ≥ niz[i + 1] ≥ ... ≥ niz[n − 1]
U nastavku su dati primeri algoritama za sortiranje u nerastucem poretku celobro-jnog niza. Jednostavnim modifikacijama svakim od ovih algoritama niz se mozesortirati i u opadajucem, rastucem ili neopadajucem poretku.
5.1 Vremenska slozenost
Pogledati slike koje oslikavaju vremensku slozenost za razlicite algoritme i razlicitenizove:
• 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 Vujosevic–Janicic 5.2 Selection sort
5.2 Selection sort
Primer 5.1 (Selection sort) U prvom prolazu se razmenjuju vrednosti a[0] saonim clanovima ostatka niza koji su veci od njega. Na taj nacin ce se posle prvogprolaza kroz niz a[0] postaviti na najveci element niza. U svakom sledecem 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 Vujosevic–Janicic 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 resenja radi dobijanja naefikasnosti. Ne vrse se zamene svaki put vec samo jednom, kada se pronade odgo-varajuci element u nizu sa kojim treba izvrsiti zamenu tako da u nizu bude postavljentrenutno najveci element na odgovarajuce 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 Vujosevic–Janicic 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 pogresno rasporedeni zamenjuje im mesta. Posle poredenjasvih susednih parova najmanji od njih ce isplivati na kraj niza. Zbog toga se ovajmetod naziva metod mehurica. Da bi se najmanji broj nesortiranog dela niza doveona 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 Vujosevic–Janicic 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 obezbedlida se ne vrse provere onda kada je niz vec sortiran nego da se u tom slucaju prekinerad.
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 Vujosevic–Janicic 5.4 Insertion sort
}
5.4 Insertion sort
Primer 5.5 (Insertion sort) U svakom trenutku je pocetak niza sortiran, a sorti-ranje se vrsi tako sto se jedan po jedan element niza sa kraja ubacuje na odgovarajucemesto.
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 umestostalnih zamena koristi dodela privremenoj promenljivoj — elemente niza pomeramoza jedno mesto sve dok ne nademo poziciju na koju treba da postavimo dati element(koji je sacuvan 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 Vujosevic–Janicic 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 treci sortiran niz(nizovi su sortirani neopadajucie).
#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 Vujosevic–Janicic 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 ucitava 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 Vujosevic–Janicic 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 Vujosevic–Janicic 5.5 Razni zadaci
int br_studenata = UcitajPodatke(studenti,100);
SelectionSort(studenti, br_studenata);
IspisiPodatke(studenti, br_studenata);
}
Primer 5.9 Program ucitava informacije o studentima iz datoteke cije se ime zadajeiz 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 njihovipodaci tako sortirani ispisuju na izlaz, ili u izlazni fajl, ako je njegovo ime zadato ukomandnoj 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 5.5 Razni zadaci
for(i = 0; i < n; i++)
ispisi(out, &studenti[i]);
fclose(in);
fclose(out);
return 0;
}
Primer 5.10 Program utvrduje da li su dve niske karaktera anagrami. Dve niskesu 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 Vujosevic–Janicic 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 Vujosevic–Janicic 5.5 Razni zadaci
return 0;
}
Primer 5.11 Interpolaciona pretraga se moze porediti sa pretragom recnika:ako neko trazi rec na slovo B, sigurno nece da otvori recnik na polovini, vec verovatnonegde blize pocetku.
/* 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 Vujosevic–Janicic 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 rastuca niza A i B, napisati funkciju koja formira niz C
koji sadrzi:
(a) zajednicke elemente nizova A i B (presek skupova A i B).
(b) elemente koje sadrzi niz A a ne sadrzi niz B (razlika skupova A i B).
(c) sve elemente koje sadrze nizovi A i B (unija skupova A i B) — ovo je ekvivalentnospajanju dva uredena 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 Vujosevic–Janicic 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 Vujosevic–Janicic 5.5 Razni zadaci
c[k++] = a[i++];
/* Vracamo broj elemenata u razlici */
return k;
}
57
Glava 6
Pokazivaci
Pokazivac je promenljiva koja sadrzi 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 pokazivaca na osnovne tipove, postoji i pokazivac na prazan tip — void.
void *pp;
Njemu moze da se dodeli da pokazuje na int, char, float ili na bilo koji drugi tipali je neophodno eksplicitno naglasiti na koji tip ukazuje pokazivac svaki put kadazelimo da koristimo sadrzaj adrese koju pokazivac cuva.
Primer 6.1 Upotreba pokazivaca 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 Vujosevic–Janicic 6.1 Pokazivacka 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 oznaci da pokazivac ne pokazuje na nekomesto u memoriji je NULL. Ova konstanta je definisana u biblioteci stdio.h i imavrednost 0.
6.1 Pokazivacka aritmetika — primeri
Primer 6.2 Funkcija proverava da li se neka vrednost x nalazi u nizu niz dimenzijen, bez koriscenja indeksiranja. Funkcija vraca pokazivac na poziciju pronadjenogelementa ili NULL ukoliko element nije pronaden.
#include <stdio.h>
int* nadjiint(int* niz, int n, int x)
{
while (--n >= 0 && *niz != x)
niz++;
59
Milena Vujosevic–Janicic 6.1 Pokazivacka 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 izracunavaju duzinu niske koriscenjem pokazivaca.
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 Vujosevic–Janicic 6.1 Pokazivacka 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 vrsi leksikografsko poredenje dva stringa. Vraca: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 vraca pokazivac nanju, odnosno NULL ukoliko s ne sadrzi c.
char* string_char(char *s, char c)
{
61
Milena Vujosevic–Janicic 6.1 Pokazivacka 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 vraca pokazivacna nju, odnosno NULL ukoliko s ne sadrzi 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 sadrzi rec 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 Vujosevic–Janicic 6.1 Pokazivacka 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 Vujosevic–Janicic 6.2 Niska karaktera i pokazivac na konstantnu nisku
printf("%s",linija);
}
6.2 Niska karaktera i pokazivac na konstantnu nisku
Prilikom deklaracije treba praviti razliku izmedu niza znakova i pokazivaca na kon-stantnu nisku. Ukoliko su date deklaracije:
char poruka[]="danas je lep dan!";
char *pporuka = "danas je lep dan!";
tada vazi:
• poruka je niz znakova koji sadrzi tekst danas je lep dan!. Pojedine znakemoguce je promeniti, na primer, nakon naredbeporuka[0] = ’D’;
poruka ce sadrzati tekst Danas je lep dan!. Niska poruka se uvek odnosi nanovo mesto u memoriji.
• pporuka je pokazivac, koji je inicijalizovan da pokazuje na konstantnu nisku,on moze biti preusmeren da pokazuje na nesto drugo, na primer, moguce jepreusmeriti ovaj pokazivac sledecom naredbompporuka = poruka;
i u ovom slucaju ce pokazivac pporuka pokazivati na mesto u memoriji nakojem se nalazi poruka. Pokusaj modifikacije elementa niske karaktera na kojuukazuje pporuka nije definisan (jer je to konstantna niska). Na primer, rezultatnaredbe pporuka[0]=’D’ je nedefinisan.
Ako deklarisemo
char *pporuka1 = "danas je lep dan!";
char *pporuka2 = "danas je lep dan!";
char *pporuka3 = "danas pada kisa";
tada ce pokazivaci pporuka1 i pporuka2 pokazivati na isto mesto u memoriji, apporuka3 na drugo mesto u memoriji.
Ako uporedimo(pporuka1==pporuka3)
uporedice se vrednosti pokazivaca. Ako uporedimo(pporuka1 < pporuka2)
uporedice se vrednosti pokazivaca. Ako dodelimopporuka1=pporuka3
tada ce pporuka1 dobiti vrednost pokazivaca pporuka3 i pokazivace na isto mestou memoriji. Nece se izvrsiti kopiranje sadrzaja memorije.
Primer 6.10 Nakon deklaracije
char a[] = "informatika";
char *p = "informatika";
64
Milena Vujosevic–Janicic 6.3 Pokazivaci na pokazivace
• Loliko elemenata ima niz a?
• Koju vrednost ima a?
• Da li se moze promeniti vrednost a?
• Koju vrednost ima a[0]?
• Da li se moze promeniti vrednost a[0]?
• Koju vrednost ima p?
• Da li se moze promeniti vrednost p?
• Koju vrednost ima p[0]?
• Da li se moze promeniti vrednost p[0]?
6.3 Pokazivaci na pokazivace
Pokazivacke promenljive su podaci kao i svi drugi, pa samim tim imaju svoju adresuu memoriji. Zbog toga je moguce govoriti o pokazivacima na pokazivace. Na primer,ako je definisan pokazivac na int:
int *p;
tada mozemo definisati pokazivac na pokazivac na int:
int **pp;
Ovaj pokazivac moze uzeti adresu pokazivacke promenljive p:
pp = &p;
Nakon toga se pokazivackoj promenljivoj p moze pristupiti i indirektno, preko pokazivacana pokazivac pp, zato sto je *pp ekvivalentno sa p. Takode, celom broju na kojipokazuje p moze se pristupiti i sa **pp zato sto je **pp ekvivalentno sa *p.
6.4 Nizovi pokazivaca
Elementi nizova mogu biti bilo kog tipa, pa i pokazivaci. Na primer, niz pokazivacana karaktere se moze definisati na sledeci nacin:
char * pokazivaci[100];
Ovim se rezervise prostor za niz od 100 pokazivaca na karaktere. Njima se pris-tupa putem indeksa, kako je i uobicajeno. S obzirom da se ime niza uvek ponasakao adresa prvog objekta u nizu, sledi da je izraz pokazivaci tipa ”pokazivac napokazivac na char”. Otuda se uobicajena pokazivacka aritmetika moze primenjivatii na nizove pokazivaca.
65
Milena Vujosevic–Janicic 6.4 Nizovi pokazivaca
Primer 6.11 Program ucitava linije iz fajla ulaz.txt i zatim ih ispisuje na stan-dardnom izlazu sortirane po duzini, pocev od najkrace 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 Vujosevic–Janicic 6.4 Nizovi pokazivaca
/* 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 Vujosevic–Janicic 6.4 Nizovi pokazivaca
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 zoverekurzija. Ovakav pristup se cesto koristi prilikom resavanja problema koji imajuprirodnu rekurzivnu definiciju, tj. kod kojih se problem dimenzije n moze jednos-tavno svesti na problem dimenzije n-1 ili neke druge manje dimenzije. Rekurzija jeanalogna matematickoj indukciji.
Ono sto je potencijalni problem kod razumevanja rekurzije je to sto se u jednomtrenutku mogu izvrsavati vise poziva jedne iste funkcije. Na primer, ako funkcijaf() prilikom svog izvrsavanja pozove samu sebe, tada se zapocinje novi poziv istefunkcije. Prethodni poziv ceka da se zavrsi tekuci, a zatim nastavlja sa radom. Pozivrekurzivne funkcije koji je zapocet, a cije izvrsavanje jos nije zavrseno nazivamoaktivni poziv. Svaki poziv funkcije izvrsava se nezavisno od svih ostalih aktivnihpoziva u tom trenutku, zahvaljujuci cinjenici da svaki od aktivnih poziva ima svojesopostvene kopije formalnih parametara i lokalnih podataka. Kada se neki od pozivazavrsi, njegove kopije nestaju iz memorije, ali kopije ostalih poziva i dalje postoje umemoriji. Posledica ovog pristupa je da kada rekurzivna funkcija promeni vrednostisvojih lokalnih podataka, ove promene ne uticu na ostale aktivne pozive, zato stooni 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 smestenena sistemskom steku — u pitanju je struktura podataka u kojoj se novi podaci uveksmestaju na vrh a prilikom uklanjanja podataka takodje se uklanja podatak sa vrha,tj. podatak koji je poslednji dodat (LIFO struktura — last in, first out). Prilikompozivanja bilo koje funkcije najpre se na stek smeste njeni argumenti (one vrednostikoje 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 nekadruga funkcija (ili ta ista, ako je rekurzija u pitanju) tada se na vrh steka dodajuargumenti 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 iparametri poziva, nakon cega na vrhu steka ostaju lokalne promenljive prethodnogpoziva itd.
1Tekst preuzet sa www.matf.bg.ac.rs/milan
69
Milena Vujosevic–Janicic 7.1 Osnovni primeri
Lokalni staticki podaci (promenljive deklarisane kao static unutar funkcije) sene 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 statickihpromenljivih u rekurzivnim funkcijama (obicno se to i ne radi).
Svaka rekurzivna funkcija mora imati izlaz iz rekurzije kao i rekurzivni pozivkojim 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 Racunanje 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 Vujosevic–Janicic 7.1 Osnovni primeri
printf("f = %d\n",f);
}
Primer 7.2 Racunanje 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 racunanja 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 Vujosevic–Janicic 7.1 Osnovni primeri
return n == 1 ? a[0] : suma_niza(a,n-1) + a[n-1];*/
}
Primer 7.4 Stampanje celog broja.
#include<stdio.h>
void printb(long int n)
{
if(n<0)
{
putchar(’-’);
n=-n;
}
if(n>=10)
printb(n/10);
putchar(n % 10 + ’0’);
}
int main()
{
long int b=-1234;
printb(b);
putchar(’\n’);
return 0;
}
Kad funkcija rekurzivno pozove sebe, svakim pozivom pojavljuje se novi skup svihautomatskih promenljivih, koji je nezavisan od prethodonog skupa. Prva funkcijaprintb kao argument dobija broj -12345, ona prenosi 1234 u drugu printb funkciju,koja dalje prenosi 123 u trecu, i tako redom do poslednje koja prima 1 kao argument.Ta funkcija stampa 1 i zavrsava sa radom tako da se vraca na prethodni nivo, nakome se stampa dva i tako redom.
Primer 7.5 Stepenovanje prirodnog broja
• iterativno
• rekurzivno, koristeci cinjenicu da je xk = x · xk−1 i x0 = 1
• rekurzivno, koristeci cinjenicu da je xk = (x2)k
2 ako je k parno ili xk = x(x2)k−1
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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Fibonacijevi brojevi se definisu rekurentno na sledeci nacin:
f(0) = 1, f(1) = 1, f(n) = f(n-1) + f(n-2)
Napisati funkciju koja izracunava n-ti Fibonacijev 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 slucaju da se rekurzijom problem svodi na vise manjih podproblemakoji se mogu preklapati, postoji opasnost da se pojedini podproblemi manjih dimenz-ija resavaju veci 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 ce seresavati jos veci broj puta. Resenje za ovaj problem je kombinacija rekurzije sa tzv.”dinamickim programiranjem” – podproblemi se resavaju samo jednom, a njihovaresenja se pamte u memoriji (obicno u nizovima ili matricama), odakle se koristeako tokom resavanja ponovo budu potrebni.
#include <stdio.h>
#include <stdlib.h>
#define MAX 50
/* Niz koji cuva resenja podproblema. */
int f[MAX];
76
Milena Vujosevic–Janicic 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 resava problem tzv. ”hanojskih kula”: data su tri vertikalnastapa, na jednom se nalazi n diskova poluprecnika 1,2,3,... do n, tako da se najvecinalazi na dnu, a najmanji na vrhu. Ostala dva stapa su prazna. Potrebno je pre-mestiti diskove na drugi stap tako da budu u istom redosledu, premestajuci jedan pojedan disk, pri cemu se ni u jednom trenutku ne sme staviti veci disk preko manjeg.
77
Milena Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 ucesljavanjem - 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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
Pokazivaci na funkcije
Binarne instrukcije koje cine funkciju su takode negde u memoriji, i imaju svojuadresu. Zato je moguce je definisati pokazivac na funkciju. Na primer, deklaracija:
int (*fp)(int);
definise promenljivu f, koja je tipa ”pokazivac na funkciju koja prihvata argumenttipa int, i vraca vrednost tipa int”. Ova promenljiva sadrzi adresu neke funkcijetog tipa. Na primer, ako je deklarisana funkcija:
int f(int a);
Tada je moguce dodeliti:
fp = &f; /* Operator adrese nije neophodan. */
Promenljiva fp sadrzi adresu funkcije f. Funkcija f se sada moze pozivati derefer-enciranjem pokazivaca fp:
(*fp)(3);
Zagrade oko izraza *fp su neophodne zbog prioriteta operatora. Dereferenciranjempokazivaca na funkciju dobija se funkcija, koja se onda poziva na uobicajen nacin.
Primer 8.1 Program demonstrira upotrebu pokazivaca na funkcije.
#include <stdio.h>
int kvadrat(int n)
{
return n*n;
}
int kub(int n)
{
return n*n*n;
}
86
Milena Vujosevic–Janicic Pokazivaci 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 matematickih funkcija u
87
Milena Vujosevic–Janicic Pokazivaci na funkcije
tackama 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 Vujosevic–Janicic 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 nizniz[0], niz[1], ..., niz[duzina_niza - 1]
elemenata velicine velicina_elementa_niza koriscenjem funkcije poredi koja uporedujedva elementa niza.
Posto je void* tip niza niz to obezbeduje mogucnost koriscenja ove funkcije zanizove razlicitih tipova podataka. Da bi funkcija qsort znala sa kakvim elementimabarata, potrebno joj je proslediti i velicinu jednog elementa u nizu (ovo je kljucnoza korak u kome se vrisi razmena elemenata niza — funkcija qsort mora da znakoliko bajtova zauzima jedan element niza kako bi mogla adekvatno da vrsi kopiranjememorije).
Funkcija poredi vrsi poredenje dva elementa niza. Kada ova funkcija vracapozitivnu vrednost ako je prvi element veci od drugog, 0 ako su jednaki i negativnuvrednost ako je prvi element manji, tada ce se niz sortirati u rastucem poretku.Modifikacijom ove funkcije niz se moze sortirati u opadajucem poretku (ukoliko
89
Milena Vujosevic–Janicic 8.1 Funkcija qsort iz standardne biblioteke
vraca pozitivnu vrednost ako je prvi manji, 0 ako su jednaki i negativnu vrednostako je prvi veci).
Kvalifikator const
Kljucna rec const u programskom jeziku C sluzi za definisanje konstanti. Akonapisemo:
const int a = 2;
tada se u daljem toku programa promenljivoj a ne moze dodeljivati vrednost. Zarazliku od konstanti definisanih define direktivom ova konstanta ima jasno definisantip koji prevodilac proverava prilikom upotrebe.
Treba naglasiti, medjutim, da ako se napise:
const int * p = &a;
tada se const ne odnosi na promenljivu p, vec na podatak tipa int na koji p
pokazuje. Dakle, nije pokazivac konstantan, vec on pokazuje na konstantan podatak.Na primer:
int b;
p = &b;
je dozvoljeno, ali:
*p = 4;
nije dozvoljeno, zato sto je p pokazivac na konstantan int (odnosno, kroz pokazivacp nije moguce menjati vrednost koja se nalazi na adresi na koju on ukazuje). Ovose moze razumeti tako sto se deklaracija procita sa desna u levo:
const int * p; /* p je pokazivac na int koji je konstantan*/
Slicno bi bilo i ako napisemo:
int const * p; /* p je pokazivac na konstantan int. */
Medutim, ako napisemo:
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 slucaju konstantni su i pokazivac i ono na sta on pokazuje.
90
Milena Vujosevic–Janicic 8.1 Funkcija qsort iz standardne biblioteke
Upotreba qsort
Primer 8.3 Upotrebom qsort funkcije iz standardne biblioteke izvrsiti sortiranjeniza 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 Vujosevic–Janicic 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 reci. Ako se sortira niz stringova, onda svaki element jesam po sebi pokazivac tipa char *, te funkcija poredenja tada prima podatke tipachar ** koji se konvertuju u svoj tip i derefenciraju radi dobijanja podataka tipachar *.
#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 Vujosevic–Janicic 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 nizuniz[0], niz[1], ..., niz[duzina_niza - 1]
elemenata velicine velicina_elementa_niza trazi element koji ima krakteristikuna koju ukazuje pokazivac kljuc.
Funkcija poredi moze da se razlikuje od funkcije poredi koju prima funkcijaqsort. Njen prvi argument je kljuc pretrage, a drugi argument je element niza. Toznaci da ova dva argumenta mogu ali i ne moraju da budu istog tipa. Na primer,ako u nizu studenata trazimo onog studenta koji ima indeks mm08123 tada je kljuctipa char* a element niza je tipa strukture student, znaci kljuc pretrage i elementniza su razlicitog tipa, pa u skladu sa tim funkcija poredenja prima dva argumentarazlicitog tipa. S druge strane, ako u nizu studenata trazimo onog studenta koji imaindeks mm08123, i ime Pera i prezime Peric, tada je kljuc istog tipa kao i elementniza, i tada funkcija poredenja prima argumente istog tipa (i analogna je funkcijiporedenja koja se pise za funkciju qsort). Dakle, poredenje se vrsi po kljucu kojimoze biti istog tipa kao i elementi niza ali i ne mora.
Funkcija poredi treba da vrati vrednost 0 ako je prvi argument (kljuc pretrage)veci (u smislu kljuca pretrage) od drugog argumenta (koji je element niza), 0 ako sujednaki (u smislu kljuca pretrage) i broj manji od 0 ako je prvi argument manji (usmislu kljuca pretrage) od drugog argumenta, ukoliko su elementi u nizu niz uredeni
93
Milena Vujosevic–Janicic 8.2 Funkcija bsearch iz standardne biblioteke
u rastucem redosledu u odnosu na dati kljuc. Funkcija bsearch vraca pokazivac napronadeni element ili NULL ukoliko element sa datom vrednoscu kljuca ne postoji.
Primer 8.5 Binarno pretrazivanje - koriscenje ugradene 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 reci. Program broji pojavljivanja svake od kljucnihreci programskog jezika C. Na kraju se reci ispisuju opadajuce 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 sledece 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 tomesto se kao treci argument ne predaje duzina niza vec adresa celobrojne promenljiveu kojoj se nalazi duzina niza. Ovo je zato sto funkcija lsearch() ukoliko linearnompretragom ne pronade element koji se trazi, umece trazeni element na kraj niza,a duzinu niza uvecava za jedan (sto cini tako sto promenljivoj pozivajuce funkcijepristupa preko pokazivaca i menja je). Takode, posto postiji mogucnost umetanjaelementa u niz, kao kljuc pretrage potrebno je koristiti kljuc koji je istog tipa kao stosu 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 iskljucivo poredenje na jednakost, do-voljno je da funkcija za uporedjivanje vraca 0 ako su objekti koji se uporedjujujednaki, 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 *));
funkcionise isto kao i lsearch, s tom razlikom sto ne umece novi element u slucajuneuspesne pretrage, vec vraca NULL (funkcija lsearch u slucaju neuspesne pretrageumece trazeni element na kraj i vraca njegovu adresu). Za funkciju lfind moguce jekoristiti kljuc pretrage koji nije istog tipa kao elementi niza, u tom slucaju potrebnoje adekvatno definisati funkciju poredenja.
8.4 Pokazivaci
Primer 8.7 Razlicite 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 Vujosevic–Janicic 8.4 Pokazivaci
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 izmedu 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
Dinamicka alokacija memorije
Do sada smo memoriju za podatke rezervisali na dva nacina1:
Staticka alokacija — podaci se kreiraju staticki prilikom pokretanja programai ostaju u memoriji do kraja njegovog izvrsavanja. Ovakvu alokaciju imajuglobalne promenljive (promenljive definisane van svake funkcije ili bloka) kao istaticke lokalne promenljive (deklarisane kljucnom recju static unutar funkcijeili bloka).
Automatska alokacija — podaci se kreiraju automatski prilikom ulaska u funkcijuili blok, i nestaju kada se iz funkcije/bloka izade. Ovakvo ponasanje imajusve lokalne promenljive koje nisu deklarisane kao static. Prostor za ovepromenljive se rezervise na programskom steku.
Glavni nedostatak oba ova pristupa je to sto programer u vreme pisanja programamora da predvidi koliko ce mu prostora biti potrebno za podatke. Cesto tako nestonije moguce, jer dimenzije problema mogu varirati. Drugi nedostatak je to sto jezivotni vek svakog podatka jasno definisan gornjim pravilima. Programer moze zeletida nakon kreiranja nekog objekta u memoriji on ostane tu dokle god je potreban,te da ga kasnije ukloni kada vise za njim nema potrebe.
Svi ovi problemi resavaju se dinamickom alokacijom memorije. Pored statickezone i steka, u memoriji postoji i odredeni prostor koji se naziva hip (eng. heap). Uovom prostoru se po potrebi mogu kreirati objekti proizvoljne velicine. Taj prostorse kasnije moze (i treba) osloboditi, kada programer vise nema potrebu da koristitaj prostor.
U programskom jeziku C, ovo se moze ostvariti pozivom funkcije standardnebiblioteke malloc() koja je deklarisana u zaglavlju stdlib.h:
void * malloc(int n);
Ova funkcija alocira na hipu kontinualni prostor od n bajtova, i vraca adresu pocetkatog prostora ili vraca vrednost NULL ukoliko zahtev ne moze da se ispuni. Jedini nacinda se ovom prostoru pristupi je preko pokazivaca (jer ne postoji ime promenljive kaokod statickih i automatskih promenljivih).
Osim funkcije malloc() postoje i druge funkcije za alokaciju, na primer:1Tekst preuzet sa sajta www.matf.bg.ac.rs/~milan
99
Milena Vujosevic–Janicic Dinamicka alokacija memorije
void * calloc(int n, int s);
Ova funkcija alocira prostor za n susednih elemenata velicine s bajtova (zapravoalocira n*s bajtova). Ova funkcija jos i inicijalizuje rezervisani prostor na vrednost0, za razliku od malloc() funkcije koja prostor ostavlja neinicijalizovan. Funkcija
void * realloc(void *p, int n);
vrsi realokaciju prostora na koji pokazuje pokazivac p za koji je memorija prethodnobila dinamicki alocirana. Alocira se n bajtova, kopira se stari sadrzaj u novi prostor,a zatim se stari prostor oslobada. Ova funkcija se obicno koristi da bi se prosiriopostojeci dinamicki alocirani prostor.
Prostor alociran prethodnim funkcijama se moze dealocirati pozivom funkcije:
void free(void *p);
koja je takode deklarisana u zaglavlju stdlib.h, i koja za argument mora imatiadresu koju je funkcija (m|c|re)alloc() vratila u nekom od prethodnih poziva(drugim recima, ne moze se proizvoljan prostor oslobadati ovom funkcijom, vecsamo prostor na hipu alociran (m|c|re)alloc()-om). Kada se jednom dealocira,prostor vise ne sme da se koristi (vrlo je verovatno da ce ga kasnije funkcija kojavrsi alokaciju dodeliti nekom drugom).
Trenutak oslobadanja prostora na hipu odreduje programer. Nebitno je da lismo izasli iz funkcije ili bloka u kome je funkcija za alokaciju memorije pozvana,prostor ostaje rezervisan dokle god ne pozovemo free() sa argumentom adrese togprostora. Ova memorija se moze koristiti i u drugim funkcijama sve dok imamopokazivac kojim mozemo da joj pristupimo.
Tiha greska je ako ”izgubimo” pokazivac na dinamicki alocirani prostor, a da ganismo prethodno dealocirali. U ovom slucaju taj prostor ostaje rezervisan do krajaizvrsavanja programa, bez mogucnosti da ga koristimo ili da ga kasnije obrisemo(jer nemamo njegovu adresu). Ovaj fenomen se naziva ”curenje memorije” (eng.memory leak). Odgovornost je programera kao i odlika dobrog stila da se dinamickiprostor uvek oslobodi kada vise nije potreban. Takode, greska je i ukoliko programerpokusa dva puta da oslobodi istu memoriju ili da pristupi memoriji koja je vecoslobodena (ovo dovodi do greske prilikom izvrsavanja programa).
Primer 9.1 Dinamicka 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 Vujosevic–Janicic Dinamicka 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 Vujosevic–Janicic Dinamicka 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 omogucila pozivajucoj funkciji da koristi vrednosti nizakoji je kreirala, neophodno je da memorija kreiranog niza bude dinamicki alociranakako bi nastavila da postoji i nakon zavrsetka rada funkcije. U tom slucaju, pozi-vajuca 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 Vujosevic–Janicic Dinamicka 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 pokazivaca na nizove razlicitih duzina.
#include <stdio.h>
#include <stdlib.h>
main()
{
/* Niz od tri elemenata tipa int*/
int nizi[3];
103
Milena Vujosevic–Janicic Dinamicka 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 Vujosevic–Janicic 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 Staticka 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 Vujosevic–Janicic 9.1 Matrice
}
}
Ukoliko je potrebna veca fleksibilnost, tj. da se dimenzije matrice mogu uneti kaoparametri programa, tada je neophodno koristiti dinamicku 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 Vujosevic–Janicic 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 koristci pokazivac na pokazivac.
1. Definisemo pokazivac na pokazivac na int:
int **p;
Ovaj pokazivac ce cuvati adresu prvog u nizu dinamicki alociranih pokazivacana int-ove.
2. Dinamicki alociramo niz od m pokazivaca na int-ove, i adresu prvog u nizusmestamo u promenljivu p:
p = (int**) malloc(sizeof(int*) * m));
Ovi pokazivaci sluze da pokazuju na prve elemente nizova int-ova koji pred-stavljaju dinamicki alocirane vrste.
3. Svaku vrstu alociramo posebnim malloc-om, i povratnu adresu smestamo u i-tipokazivac u malopre alociranom nizu pokazivaca.
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 sto je ovde receno). Ovo moze da budekorisno, ako znamo da ce u nekoj vrsti biti korisceno samo nekoliko prvih ele-menata, tada mozemo alocirati manje za tu vrstu, i tako ustedeti prostor.
107
Milena Vujosevic–Janicic 9.1 Matrice
4. U toku rada se elementima ovako alocirane matrice pristupa sa p[i][j] – p[i]
je i-ti pokazivac u nizu, a kako on pokazuje na pocetni element u i-toj vrsti,tada je p[i][j] upravo j-ti element u i-toj vrsti.
5. Dealokacija se sastoji u brisanju svih vrsta, nakon cega se obrise i niz pokazivaca(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 Vujosevic–Janicic 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. Kod zaalokaciju i dealokaciju memorije je izdvojen u funkcije. Element i je u relaciji saelementom 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Izracunati 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Dinamicki 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 9.2 Realokacija memorije
for (i=0; i<duzina; i++)
novi_niz[i]=niz_reci[i];
/* Uklanjamo stari niz */
free(niz_reci);
/* Stari niz postaje novi */
niz_reci=novi_niz;
}
}
/* Upisujemo rec u niz */
niz_reci[duzina]=nova_rec;
duzina++;
}
}
/* Ispisujemo elemente niza */
for(i=0; i<duzina; i++)
printf("%s - %d\n",niz_reci[i].ime, niz_reci[i].br_pojavljivanja);
free(niz_reci);
}
117
Glava 10
Liste
Jednostruko povezana lista1 je struktura podataka koja se sastoji od sekvence cvorova.Svaki cvor sadrzi podatak (odredenog tipa) i pokazivac na sledeci cvor u sekvenci.Prvi cvor u sekvenci naziva se glava liste. Ostatak liste (bez glave) je takode lista,i naziva se rep liste. Lista koja ne sadrzi cvorove naziva se prazna lista. Prilikombaratanja listom mi cuvamo pokazivac na glavu liste. Kada pristupimo glavi liste, unjoj imamo zapisanu adresu sledeceg elementa, pa mu samim tim mozemo pristupiti.Kada mu pristupimo, u njemu je sadrzana adresa sledeceg elementa, pa preko togpokazivaca mozemo da mu pristupimo, itd. Poslednji element u listi nema sledecielement: u tom slucaju se njegov pokazivac na sledeci postavlja na NULL. Takode,prazna lista se predstavlja NULL pokazivacem.
Prednost koriscenja povezanih lista u odnosu na dinamicki niz je u tome sto seelementi mogu efikasno umetati i brisati sa bilo koje pozicije u nizu, bez potrebeza realokacijom ili premestanjem elemenata. Nedostatak ovakvog pristupa je to stone mozemo nasumicno pristupiti proizvoljnom elementu, vec se elementi morajuobradivati redom (iteracijom kroz listu).
10.1 Implementacija
Prilikom promene liste (dodavanje novog elementa, brisanje elementa, premestanjeelemenata, itd.) postoji mogucnost da glava liste bude promenjena, tj. da to postaneneki drugi cvor (sa drugom adresom). U tom slucaju se pokazivac na glavu liste moraazurirati. Kada promenu liste obavljamo u posebnoj funkciji (kao sto je bio slucaj uprethodnom primeru, gde smo za dodavanje i brisanje imali posebne funkcije) ondaje potrebno da se pozivajucoj funkciji vrati informacija o promeni adrese glave,kako bi pozivajuca funkcija mogla da azurira svoju pokazivacku promenljivu. Ovose moze uraditi na dva nacina:
1. Pozvana funkcija koja vrsi promenu na listi vraca kao povratnu vrednost adresuglave nakon promene. Ova adresa moze biti ista kao i pre promene (ako glavanije dirana) a moze se i razlikovati. U svakom slucaju, ta adresa treba da sedodeli pokazivackoj promenljivoj koja cuva adresu glave u pozivajucoj funkciji.
1Tekst preuzet sa sajta www.matf.bg.ac.rs/ milan
118
Milena Vujosevic–Janicic 10.1 Implementacija
Zbog toga se funkcija za promenu liste uvek poziva na sledeci nacin:pok = funkcija_za_promenu(pok, ...);
tj. promenljivoj cija se vrednost predaje kao adresa glave u pozivu mora se do-deliti povratna vrednost funkcije, za slucaj da je adresa glave interno promen-jena.
2. Pozvana funkcija koja vrsi promenu na listi prihvata kao argument pokazivacna pokazivacku promenljivu koja u pozivajucoj funkciji cuva adresu glave ikoju eventalno treba azurirati. Sada pozvana funkcija moze interno da prekodobijenog pokazivaca promeni promenljivu pozivajuce funkcije direktno. Naprimer:funkcija_za_promenu(&pok, ...);
Funkcija koja se poziva je po pravilu tipa void. Prednost drugog metoda jejednostavnije pozivanje, dok je prednost prvog metoda jednostavnija sintaksaunutar pozvane funkcije.
U prethodnom primeru je demonstriran prvi pristup. Naredni primer ilustruje drugipristup.
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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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, definisanesu rekurzivno. Primetno je da su ove rekurzivne varijante mnogo jednostavnije.Razlog je to sto su liste po svojoj prirodi rekurzivne strukture. Naime, liste se mogurekurzivno definisati na sledeci nacin:
• objekat oznacen sa [] je lista (prazna lista)
• ako je L lista, i G neki element, tada je uredeni par (G,L) takode lista. ElementG 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 mozemo 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) takode lista, koju mozemo zapisati kao [b a1 a2 ... an].
128
Milena Vujosevic–Janicic 10.1 Implementacija
Glava liste je b, a rep liste je lista L. Sada se obrada liste moze razdvojiti na dvadela:
• Izvrsiti operaciju nad glavom liste
• Izvrsiti rekurzivni postupak nad repom liste (koji je takode lista).
Za testiranje narednog programa moze 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 reci koja se pojavila utekstu 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 dvanacina. Prvi nacin je da se premestaju cvorovi u listi (sto zahteva prevezivanjepokazivaca), a drugi je da se premestaju vrednosti koje cvorovi sadrze. Prvi nacinje komplikovaniji, ali je cesto efikasniji, narocito ako su objekti koji se cuvaju kaopodaci u cvorovima veliki.
U oba slucaja se moze koristiti rekurzija, a moze se realizovati i iterativno. Uovom primeru ilustrujemo oba nacina sortiranja lista (prvi rekurzivno, a drugi iter-ativno).
#include <stdio.h>
#include <stdlib.h>
138
Milena Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 10.2 Kruzna (ciklicna) 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 Kruzna (ciklicna) lista
A
pocetak ciklicne liste
B C ... Z
Slika 10.1: Kruzna lista
Primer 10.5 (februar 2006.) Grupa od n plesaca (na cijim kostimima su u smerukazaljke na satu redom brojevi od 1 do n) izvodi svoju plesnu tacku tako sto formirajukrug iz kog najpre izlazi k-ti plesac (odbrojava se pocev od plesaca oznacenog brojem1 u smeru kretanja kazaljke na satu). Preostali plesaci obrazuju manji krug iz kogopet izlazi k-ti plesac (odbrojava se pocev od sledeceg suseda prethodno izbacenog,opet u smeru kazaljke na satu). Izlasci iz kruga se nastavljaju sve dok svi plesaci
143
Milena Vujosevic–Janicic 10.3 Red
ne budu iskljuceni. Celi brojevi n, k (k < n) se ucitavaju sa standardnog ulaza.Napisati program koji ce na standardni izlaz ispisati redne brojeve plesaca u redosledunapustanja kruga.
PRIMER: za n = 5, k = 3 redosled izlaska je 3 1 5 2 4.
10.3 Red
A
pocetak reda (brisanje — get)
B ... X NULL
kraj reda (dodavanje — add)
Y
novi element
A
pocetak reda
B ... X Y
novi kraj reda
NULL
B ...
pocetak reda nakon brisanja
X Y
kraj reda
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 pocetkareda (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 implementiratina vise nacina. Najjednostavniji nacin je da se definise kao niz. Medjutim, tadaje ogranicen max. broj elemenata u redu dimenzijom niza. Zbog toga se obicnopribegava koriscenju lista za implementaciju reda, gde se enqueue() operacija svodina dodavanje na kraj liste, a dequeue() operacija se svodi na uklanjanje glave liste.
144
Milena Vujosevic–Janicic 10.3 Red
Obe operacije se izvode u konstantnom vremenu, pod uslovom da se osim pokazivacana 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 skupuS. Ako se ime fajla S1 nalazi u fajlu S2, tada je skup S1 podskup skupa S2. Programza 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 stavljaime fajla koji predstavlja dati skup S. Iz reda se zatim u petlji uzima sledeci fajl iotvara za citanje. Prilikom citanja, svaki put kada se u fajlu koji se trenutno citanaidje na ime drugog fajla, to ime ce biti dodato u red. Kada se zavrsi sa citanjemtekuceg fajla, isti se zatvara, skida se sa reda sledeci fajl i citanje se nastavlja unjemu. Ovaj proces se ponavlja dok se ne naidje na trazeni broj, ili dok se neisprazni 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 10.3 Red
/* Oslobadjamo red */
oslobodi_red(&pocetak, &kraj);
return 0;
}
Primer 10.7 Program formira celobrojni red i ispisuje sadrzaj reda na standardniizlaz.
#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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 savrha 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 implementiratina vise nacina. Najjednostavniji nacin je da se definise kao niz. Medjutim, tadaje ogranicen max. broj elemenata na steku dimenzijom niza. Zbog toga se obicnopribegava koriscenju lista za implementaciju steka, gde se push() operacija svodina dodavanje na pocetak, a pop() operacija se svodi na uklanjanje glave liste. Obeoperacije se izvode u konstantnom vremenu.
Primer 10.8 Program proverava da li su zagrade (, [, {, }, ] i ) dobro uparene —staticka implementacija steka (pretpostavka da se na ulazu nece naci vise od 100otvorenih 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 Vujosevic–Janicic 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 Vujosevic–Janicic 10.4 Stek
X
vrh steka (i dodavanje i brisanje — push i pop)
Y ... A NULL
V
novi element
Y
novi vrh steka
X V ... A NULL
X
vrh steka nakon brisanja
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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 sadrzaj steks na standardniizlaz.
#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 Vujosevic–Janicic 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 Vujosevic–Janicic 10.5 Dvostruko povezane liste
}
10.5 Dvostruko povezane liste
NULL A
pocetak dvostruko povezane liste
B C...... Z NULL
kraj liste
Slika 10.4: Dvostruko povezana lista
Dvostruko povezana lista je struktura podataka koja se sastoji od sekvence cvorova.Svaki cvor sadrzi podatak (odredenog tipa) i pokazivace na prethodni i sledeci cvoru sekvenci. Za razliku od obicne liste, u svakom cvoru imamo zapisanu i adresuprethodnog elementa, pa se kroz listu mozemo kretati i unazad. Poslednji element ulisti nema sledeci element: u tom slucaju se njegov pokazivac na sledeci postavlja naNULL. Slicno, glava liste nema prethodni element, pa je njen pokazivac na prethodnipostavljen na NULL. Takode, prazna lista se predstavlja NULL pokazivacem.
Prednost dvostruko povezane liste u odnosu na jednostruko povezanu je u tomesto se mozemo kretati u oba smera kroz listu. Nedostatak je to sto se velicinamemorije potrebne za cuvanje cvora povecava za velicinu jednog pokazivaca.
Primer 10.11 Program demonstrira rad sa dvostruko povezanim listama. Za testi-ranje narednih funkcija moze 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 10.5 Dvostruko povezane liste
{
printf("[]\n");
return;
}
/* Prolazimo kroz listu dok ne stignemo do poslednjeg
elementa. */
while(glava->sledeci != NULL)
glava = glava->sledeci;
/* Ispisujemo elemente liste iteracijom u suprotnom smeru */
putchar(’[’);
for(;glava != NULL; glava = glava->prethodni)
printf("%d ", glava->vrednost);
putchar(’]’);
putchar(’\n’);
}
/* Funkcija oslobadja dinamicku memoriju zauzetu
od strane liste. Funkcija vraca NULL, tj.
vrednost koju treba dodeliti pokazivackoj
promenljivoj, s obzirom da je sada lista prazna. */
Cvor* oslobodi_listu(Cvor *glava)
{
Cvor *pomocni;
while(glava != NULL)
{
/* moramo najpre zapamtiti adresu sledeceg
elementa, a tek onda osloboditi glavu */
pomocni = glava->sledeci;
free(glava);
glava = pomocni;
}
return NULL;
}
165
Glava 11
Stabla
Binarno stablo1 je skup cvorova koji su povezani na sledeci nacin:
1. Svaki cvor moze imati levog i desnog SINA (pri tom svaki od sinova moze bitii izostavljen, ili oba). Kazemo da je dati cvor RODITELJ svojim sinovima. Akoje cvor U roditelj cvoru V, tada pisemo da je U < V . Cvor koji nema ni levogni desnog sina naziva se LIST.
2. Postoji jedan jedinstveni cvor takav da nema roditelja. Ovaj cvor nazivamoKOREN stabla.
3. U stablu nema ciklusa, tj. ne postoji niz cvorova x1,x2,...,xn, takav da jex1 < x2 < ... < xn < x1.
Inace, niz cvorova x1 < x2 < ... < xn nazivamo put u stablu. Kazemo da jecvor U predak cvora V ako u stablu postoji put od U do V. Specijalno, svaki cvorje predak samom sebi. Lako se moze pokazati da je koren predak svih cvorova ustablu. Rastojanje od korena do nekog cvora naziva se visina cvora (koren je visine0). Maksimalna visina cvora u stablu naziva se visina stabla.
Stablo koje nema cvorova naziva se prazno stablo.Za svaki cvor V u stablu mozemo posmatrati stablo koje se sastoji od svih njegovih
potomaka (svih cvorova kojima je on predak). Ovo stablo se naziva podstablo sadatim korenom V. Podstablo sa njegovim levim sinom kao korenom nazivamo levimpodstablom cvora V, dok podstablo sa njegovim desnim sinom kao korenom nazivamodesnim podstablom cvora V. Ako je neki od njegovih sinova izostavljen, tada kazemoda je odgovarajuce podstablo cvora V prazno stablo. Specijalno, ako je V koren, tadanjegovo levo i desno podstablo nazivamo levim i desnim podstablom datog (citavog)stabla.
Stablo se moze definisati i rekurzivno na sledeci nacin:
1. Prazno stablo je stablo
2. Ako su data dva stabla t1 i t2, i cvor r, tada je i (t1,r,t2) takodje stablo.t1 je tada levo podstablo, t2 je desno podstablo dok je cvor r koren takoformiranog stabla.
166
Milena Vujosevic–Janicic 11.1 Binarno pretrazivacko stablo
NULL NULL
NULL NULL NULL
NULL NULL
Slika 11.1: Binarno stablo
U programiranju se stabla obicno koriste kao strukture podataka, tako sto svakicvor sadrzi po jedan podatak odredenog tipa. Nacin rasporedivanja podataka ustablu zavisi od konkretne primene stabla. S obzirom na rekurzivnu prirodu stabla,uobicajeno je da se u programiranju stabla obraduju rekurzivno.
11.1 Binarno pretrazivacko stablo
Posebna vrsta stabla su tzv. binarna pretrazivacka stabla. Ova stabla imaju osobinuda za svaki cvor vazi sledece: svi cvorovi njegovog levog podstabla sadrze podatkesa manjom vrednoscu od vrednosti podatka u tom cvoru, dok svi cvorovi njegovogdesnog podstabla sadrze podatke sa vecom vrednoscu od vrednosti podatka u tomcvoru. Ovakva organizacija omogu ’cava efikasno pretrazivanje.
Primer 11.1 Program demonstrira rad sa binarnim pretrazivackim drvetima ciji supodaci celi brojevi. Drvo sadrzi cele brojeve sortirane po velicini. Za svaki cvor, levopodstabla sadrzi manje elemente, dok desno podstablo sadrzi vece.
#include <stdlib.h>1Tekst preuzet sa sajta http://www.matf.bg.ac.rs/~milan
167
Milena Vujosevic–Janicic 11.1 Binarno pretrazivacko stablo
17, 12, 21, 15, 5, 14
infix : 5, 12, 14, 15, 17, 21
prefix : 17, 12, 5, 15, 14, 21
postfix : 5, 14, 15, 12, 21, 17
17
21
NULL NULL
12
NULL NULL
5
NULL
15
NULL NULL
14
Slika 11.2: Uredeno 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 pretrazivacko drvo - funkcije za izracunavanje kopije, unije,preseka i razlike dva drveta, funkcija za izbacivanje cvora iz drveta.
/* Struktura koja predstavlja cvor drveta */
typedef struct cvor {
int vrednost; /* Vrednost koja se cuva */
struct cvor * levi; /* Pokazivac na levo podstablo */
struct cvor * desni; /* Pokazivac na desno podstablo */
} Cvor;
/* NAPOMENA: Prazno stablo se predstavlja NULL pokazivacem. */
173
Milena Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko stablo
return 0;
}
Primer 11.3 Primer demonstrira sortiranje niza pomocu binarnog stabla. Elementiniza se redom prvo smeste u binarno stablo pretrage, a zatim se elementi ”pokupe”iz stabla obilazeci ga sa leva u desno (tj. u rastucem poretku). Vremenska slozenostovakvog algoritma je u ekvivalentna quick_sort()-u. Prostorna slozenost je nestoveca, jer sortiranje nije ”u mestu”, vec se koristi pomocna struktura – binarnostablo.
#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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko stablo
sortiraj(a, n);
printf("Niz nakon sortiranja:\n");
for(i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
Primer 11.4 Program sa ulaza cita tekst i ispisuje broj pojavljivanja svake od recikoje su se javljale u tekstu. Radi poboljsanja efikasnosti, prilikom brojanja reci koristise struktura podataka pogodna za leksikografsku pretragu — u ovom slucaju binarnopretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 kljuc i vrednost unapred odredenog (ne obaveznoistog) tipa (na primer, kljuc nam moze biti string koji predstavlja ime studenta, avrednost je npr. broj koji predstavlja njegov prosek). Operacije koje mapa mora
192
Milena Vujosevic–Janicic 11.1 Binarno pretrazivacko stablo
efikasno da podrzava su:
• dodavanje para (kljuc, vrednost) u mapu
• brisanje para (kljuc, vrednost) iz mape
• pronalazenje vrednosti za dati kljuc
• promena vrednosti za dati kljuc
Jedan od najcescih nacina implementacije mape je preko binarnog stabla pretrage.Svaki cvor ovog stabla sadrzace odgovarajuci par (kljuc, vrednost), tj. svaki cvorce sadrzati dva podatka. Pri tom ce poredak u stablu biti odreden poretkom koji jedefinisan nad kljucevima. Ovim se postize da se pretraga po kljucu moze obaviti nauobicajen nacin, kao i sve ostale navedene operacije.
Jasno je da ovako implementirana mapa nece efikasno podrzavati operaciju pre-trage po vrednosti (npr. naci sve kljuceve koji imaju datu vrednost), zato sto poredaku stablu nije ni na koji nacin u vezi sa poretkom medu vrednostima. Medutim, umapi se najcesce i ne zahteva takva operacija.
Sledi primer koji demonstrira implementaciju mapa pomocu 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 injihove frekvencije u opadajucem poretku po frekvencijama.
Radi poboljsanja efikasnosti, prilikom brojanja pojavljivanja etiketa koristi se
198
Milena Vujosevic–Janicic 11.1 Binarno pretrazivacko stablo
struktura podataka pogodna za leksikografsku pretragu - u ovom slucaju binarno pre-trazivacko drvo.
Na kraju rada, cvorovi 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 corova stabla — formira se niz pokazivaca nacvorove stabla koji se u skladu sa funkcijom poredenja sortiraju pomocu biblioteckeqsort funkcije.
#include <stdlib.h>
#include <stdio.h>
204
Milena Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko 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 Vujosevic–Janicic 11.1 Binarno pretrazivacko stablo
exit(1);
}
if ((ulaz=fopen(argv[1],"r"))==NULL)
{
fprintf(stderr,"Greska : nisam uspeo da otvorim datoteku %s\n");
exit(1);
}
/* Citamo rec po rec dok ne naidjemo na kraj datoteke i
ubacujemo ih u drvo */
while(getword(procitana_rec,80,ulaz)!=-1)
drvo=ubaci(drvo,procitana_rec);
/* Sortiramo niz pokazivaca na cvorove
drveta po broju pojavljivanja */
qsort(razlicite_reci,
broj_razlicitih_reci,
sizeof(cvor*),
poredi_br_pojavljivanja);
/* Ispisujemo prvih 10 (ukoliko ih ima)
reci i njihov broj pojavljivanja */
for (i=0; i<10 && i<broj_razlicitih_reci; i++)
printf("%s - %d\n",razlicite_reci[i]->ime,
razlicite_reci[i]->br_pojavljivanja);
/* Uklanjamo drvo iz memorije */
obrisi_drvo(drvo);
fclose(ulaz);
}
208
Glava 12
Grafovi
Graf1 G=(V,E) sastoji se od skupa V cvorova i skupa E grana. Grane predstavljajurelacije izmedu cvorova i odgovara paru cvorova. Graf moze biti usmeren (orijen-tisan), ako su mu grane uredeni parovi i neusmeren (neorjentisan) ako su graneneuredeni parovi.
Put u grafu je niz cvorova v1,v2,...,vn, pri cemu u grafu postoje grane (v1,v2),(v2,v3), ..., (vn-1, vn). Ako jos vazi 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 cvorse smesta neki podatak odredenog tipa), grafovi se pre svega koriste za modeliranjenekih problema iz prakse, kako bi se ti problemi predstavili na apstraktan nacini resili primenom algoritama nad odgovarajucim grafom. Na primer, cvorovima ugrafu se mogu predstaviti raskrsnice u gradu, a granama ulice koji ih povezuju. Tadase zadatak nalazenja najkraceg puta od tacke A do tacke B u gradu moze svesti nanalazenje najkraceg puta od cvora u do cvora v u grafu. Slicno se mogu resavatiproblemi sa racunarskim mrezama (racunari se predstave cvorovima, a veze medunjima granama), itd.
Grafovi se mogu predstavljati na vise nacina. Uobicajena su dva nacina pred-stavljanja grafova. To su matrica povezanosti grafa i lista povezanosti.
Matrica povezanosti je kvadratna matrica dimenzije n, pri cemu je n broj cvorovau 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 cvora, inace je nula. Jasno je dace neusmerenim grafovima odgovarati simetricne matrice. Prednost ovakvog pred-stavljanja je jednostavnost. Nedostatak ovakvog pristupa je neracionalno koriscenjememorije u slucaju ”retkih” grafova (grafova sa malo grana), gde ce najveci brojelemenata matrice biti 0.
Umesto da se i sve nepostojece grane eksplicitno predstavljaju u matrici povezanosti,mogu se formirati povezane liste od jedinica iz i-te vrste za i=1,2,...,n. To jelista povezanosti. Svakom cvoru se pridruzuje povezana lista, koja sadrzi sve granesusedne tom cvoru. Graf je predstavljen vektorom lista. Svaki elemenat vektora
1Tekst i primeri preuzeti od Jelene Tomasevic, www.matf.bg.ac.yu/~jtomasevic, i MilanaBankovica www.matf.bg.ac.yu/~milan zasnovano na materijalu Algoritmi, Miodrag Zivkovic ihttp://www.matf.bg.ac.yu/~filip
209
Milena Vujosevic–Janicic Grafovi
sadrzi ime (indeks) cvora i pokazivac na njegovu listu cvorova. Ovakav pristup stedimemoriju, 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 obilazakgrafa. Postoje dva osnovna algoritma za obilazak grafa: pretraga u dubinu (DFS,skracenica od depth-first-search) i pretraga u sirinu (BFS, skracenica od breadth-first-search).
Kod DFS algoritma, obilazak zapocinje iz proizvoljnog zadatog cvora r koji senaziva koren pretrage u dubinu. Koren se oznacava kao posecen. Zatim se biraproizvoljan neoznacen cvor r1, susedan sa r, pa se iz cvora r1 rekurzivno startujepretraga u dubinu. Iz nekog nivoa rekurzije izlazi se kad se naide na cvor v kome susvi susedi vec oznaceni.
Primer 12.1 Primer reprezentovanja grafa preko matrice povezanosti. U programuse unosi neorijentisan graf i DFS algoritmom se utvrduju cvrovi koji su dostizni izcvora 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 primergrafovi se predstavljaju matricama povezanosti. Zbog jednostavnosti, pretpostavlja seda je broj cvorova ogranicen nekom fiksiranom konstantom, tako da se odgovarajucematrice alociraju staticki. Takode, da bi se pojednostavile liste argumenata pojedinihfunkcija, vecina promenljivih u ovom primeru se deklarisu kao globalne, kako bi kaotakve bile dostupne svim funkcijama.
#include <stdio.h>
#include <stdlib.h>
212
Milena Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 cvorovagrafa. U programu se unosi graf i DFS algoritmom se utvrdjuje koji su cvorovidostizni iz cvora 0.
#include <stdlib.h>
#include <stdio.h>
/* Cvor liste suseda */
216
Milena Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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 Vujosevic–Janicic 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