Zbirka zadataka iz programskog jezika C v1.0 [PDF]

  • 0 0 0
  • Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen! Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau

_________________________________________________________

ZBIRKA ZADATAKA IZ PROGRAMSKOG JEZIKA C V 1.0 05/08/2015 _________________________________________________________ ETF Beograd ETF Podgorica ETF Sarajevo FER Zagreb FSB Split FTN Kosovska Mitrovica MAT FAK Beograd PMF Niš PMF Srbsko Sarajevo FTN Čačak UN Singidunum Beograd VTŠ Čačak VTŠ Niš _____________________________________________________________

Sakupio i obradio: [email protected]

SADRŽAJ _________________________________________________________________ C reference card ANSI Službeni podsetnik FER Zagreb 1.ETF Beograd 2.ETF Podgorica 3.ETF Sarajevo 4.FER Zagreb 5.FSB Split 6.FTN Kosovska Mitrovica 7.MAT FAK Beograd 8.PMF Niš 9.PMF Srbsko Sarajevo 10.FTN Čačak 11.UN Singidunum Beograd 12.VTŠ Čačak 13.VTŠ Niš _________________________________________________________________ Napomena: Zbirka predstavlja skup vežbi iz programskog jezika C Nedostaju vežbe sa FTN NS i ELFAK Niš Za brži pristup pojedinoj stavki u sadržaju kliknuti na bukmark Za bolje razumevanje pogledati predavanja na odgovarajućem fakultetu tj. školi

C Reference Card (ANSI) Program Structure/Functions type fnc(type 1 ,. . . ) type name main() { declarations statements } type fnc(arg 1 ,. . . ) { declarations statements return value; } /* */ main(int argc, char *argv[]) exit(arg )

function declarations external variable declarations main routine local variable declarations function definition local variable declarations

Flow of Control

Constants long (suffix) float (suffix) exponential form octal (prefix zero) hexadecimal (prefix zero-ex) character constant (char, octal, hex) newline, cr, tab, backspace special characters string constant (ends with '\0')

L or l F or f e 0 0x or 0X 'a', '\ooo', '\xhh' \n, \r, \t, \b \\, \?, \', \" "abc. . . de"

Pointers, Arrays & Structures

include library file #include include user file #include "filename" replacement text #define name text replacement macro #define name(var ) text Example. #define max(A,B) ((A)>(B) ? (A) : (B)) undefine #undef name quoted string in replace # concatenate args and rescan ## conditional execution #if, #else, #elif, #endif is name defined, not defined? #ifdef, #ifndef name defined? defined(name ) line continuation char \

declare pointer to type type *name declare function returning pointer to type type *f() declare pointer to function returning type type (*pf)() generic pointer type void * null pointer NULL object pointed to by pointer *pointer address of object name &name array name[dim] multi-dim array name[dim 1 ][dim 2 ]. . . Structures struct tag { structure template declarations declaration of members }; create structure struct tag name member of structure from template name.member member of pointed to structure pointer -> member Example. (*p).x and p->x are the same single value, multiple type structure union bit field with b bits member : b

Data Types/Declarations

Operators (grouped by precedence)

comments main with args terminate execution

C Preprocessor

character (1 byte) integer float (single precision) float (double precision) short (16 bit integer) long (32 bit integer) positive and negative only positive pointer to int, float,. . . enumeration constant constant (unchanging) value declare external variable register variable local to source file no value structure create name by data type size of an object (type is size_t) size of a data type (type is size_t)

char int float double short long signed unsigned *int, *float,. . . enum const extern register static void struct typedef typename sizeof object sizeof(type name)

Initialization initialize variable initialize array initialize char string

type name=value type name[]={value 1 ,. . . } char name[]="string "

structure member operator structure pointer increment, decrement plus, minus, logical not, bitwise not indirection via pointer, address of object cast expression to type size of an object

name.member pointer ->member ++, -+, -, !, ~ *pointer , &name (type) expr sizeof

multiply, divide, modulus (remainder)

*, /, %

add, subtract

+, -

left, right shift [bit ops]

comparisons comparisons bitwise and bitwise exclusive or bitwise or (incl)

>, >=, = == != & ^ | && || ? : = *= /= %= += -= &= ^= |= = ,

D → L D L L L L L L L L L L D

→ → → → → → → → → → → →

L D D D D D D D D D D L

D → L L → D

Izgled konverzijskih specifikacija kod funkcije printf %[znak][širina][.preciznost]tip

x

e ln x log x xy x

x mod y

x  x 

stdlib.h int abs (int x); x long labs (long x); x void exit (int status); void srand (unsigned int seed); int rand (void); void *malloc (size_t size); void free (void *block); void *realloc(void *block, size_t size);

[znak]

Objašnjenje

ništa

desno pozicioniranje tiska - predznak, a umjesto + predznaka je praznina lijevo pozicioniranje rezultat uvijek počinje s + ili ispisuje vodeće nule konverzija na alternativan način: ne utječe na c s d i u ispisuje vodeću 0 za o ispisuje vodeće 0x ili 0X za x ili X ispisuje dec. točku i kad nema decimala za e E F ispisuje prateće 0 za g G

praznina + 0 #

vraća broj iz intervala [0, RAND_MAX] vraća NULL u slučaju pogreške vraća NULL u slučaju pogreške

string.h char *strcpy(char *dest, const char *src); char *strncpy(char *dest, const char *src, size_t maxlen); char *strcat(char *dest, const char *src); char *strncat(char *dest, const char *src, size_t maxlen); size_t strlen(const char *s); int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t maxlen); char *strchr(const char *s, int c);

Programiranje i programsko inženjerstvo – službeni podsjetnik

verzija 2013.b

char *strrchr(const char *s, int c); char *strstr(const char *string, const char *substring); char *strpbrk(const char *string, const char *setofcharacters); ctype.h int toupper(int int tolower(int int isdigit(int int isalpha(int int isalnum(int int isprint(int int iscntrl(int int isspace(int int islower(int int isupper(int

ch); ch); c); c); c); c); c); c); c); c);

provjerava je li znak znamenka (0-9) provjerava je li znak slovo (A-Z ili a-z) provjerava je li znak slovo (A-Z ili a-z) ili znamenka (0-9) provjerava može li se znak ispisati (0x20-0x7E) provjerava je li znak kontrolni (0x7F ili 0x00-0x1F) provjerava je li znak praznina provjerava je li znak malo slovo (a-z) provjerava je li znak veliko slovo (A-Z)

stdio.h int getchar(void); vraća učitani znak ili EOF int putchar(int ch); vraća ispisani znak ili EOF (kod pogreške) int scanf(const char *format, arg1, arg2, ...,arg n); vraća broj učitanih argumenata (0…n) ili EOF (kraj datoteke) Tipovi formatskih specifikacije za scanf: %d,%i,%o,%u,%x,%c,%s,%e,%f,%g,%p,%[…],%[^…]. Prefiksi: h(za short) l(long, double) L(long double), npr. %hd, %ld, %lf, %Lf int printf(const char *format, arg1, arg2, ...,arg n);

vraća broj ispisanih znakova

Tipovi formatskih specifikacije za printf: %d,%i,%o,%u,%x,%X,%c,%s,%e,%f,%g,%G,%e,%E,%p. int puts(const char *s); char *gets(char *string);

vraća EOF u slučaju pogreške vraća NULL ako kao prvi znak pročita kraj datoteke (CTRL+Z (windows) ili CTRL+D(unix)) ili ako je nastupila pogreška

FILE *fopen(const char *filename, const char *mode); mode: "w","a","r","w+","a+","r+" Napomena: U Windows op. sustavu za binarne datoteke treba na kraj dodati b vraća 0 ako je operacija uspjela ili EOF u slučaju pogreške vraća pročitani znak ili EOF (pogreška ili kraj datoteke) const char *format, arg1, arg2, ..., arg n); vraća broj učitanih argumenata ili EOF(pogreška ili kraj datoteke) char *fgets(char *s, int n, FILE *stream); vraća NULL u slučaju pogreške ili kraja datoteke int fputc(int c, FILE *stream); vraća ispisani znak ili EOF u slučaju pogreške int fprintf (FILE *stream, const char *format, arg1, arg2, ..., arg n); vraća broj ispisanih znakova ili EOF u slučaju pogreške int fputs(char *s, FILE *stream); vraća nenegativni broj ili EOF u slučaju pogreške size_t fread(void *ptr, size_t size, size_t n, FILE *stream); vraća broj učitanih objekata. (0..n) size_t fwrite(void *ptr, size_t size, size_t n, FILE *stream); vraća broj ispisanih objekata. U slučaju pogreške taj je broj < n. int fseek(FILE *stream, long offset, int whence); vraća 0 ako je pozicioniranje uspjelo ili broj različit od 0 u slučaju pogreške whence: SEEK_SET – pozicioniranje u odnosu na početak datoteke SEEK_CUR - pozicioniranje u odnosu na trenutnu poziciju u datoteci SEEK_END - pozicioniranje u odnosu na kraj datoteke int fclose(FILE *fp); int fgetc(FILE *stream); int fscanf (FILE *stream,

long ftell(FILE *stream);

vraća trenutnu poziciju u datoteci ili -1 u slučaju pogreške

ELEKTROTEHNIČKI FAKULTET UNIVERZITETA U BEOGRADU PROGRAMIRANJE 2 MATERIJAL ZA VEŽBE NA TABLI I PRIPREMU ISPITA verzija: 26.06.2008. Detaljna objašnjenja vezana za bilo koji deo gradiva vezanog za teoriju dostupna su u beleškama sa predavanja, kao i u knjizi koja opisuje jezik C dovoljno precizno: • Laslo Kraus – Programski jezik C. Više zadataka se može pronaći u odgovarajućoj zbirci prof. Krausa: • Laslo Kraus, Zbirka zadataka iz programskog jezika C. Sve ove knjige su dostupne u skriptarnici ili u biblioteci fakulteta. Zadaci koji su tipa ispitnih pitanja su priloženi u celosti, uključujući i obrazloženje rešenja tamo gde je to potrebno. U materijal je uključeno i nekoliko ispitnih zadataka, koji su rešeni u potpunosti i komentarisani do odgovarajućeg stepena opširnosti. Materijal će biti stalno dorađivan. Nove verzije će redovno biti dodavane na Internet stranicu predmeta: http://rti.etf.rs/ir1p2 Sugestije, primedbe i uočene greške poslati putem elektronske pošte na adresu [email protected].

Ove materijale treba koristiti isključivo kao podsetnik a nipošto kao jedini izvor znanja. Nemojte NIKAD učiti programski kod napamet. Svako ponavljanje bez razumevanja programskog koda je štetno i na ispitu će biti kažnjavano oduzimanjem poena.

Elektrotehnički fakultet Univerziteta u Beogradu

Predstavljanje realnih brojeva

SADRŽAJ

Standard za aritmetiku realnih brojeva Zaokruživanje Zadatak Z12 Zadatak IZ3 Zadatak IZ4 Zadatak IZ5 Zadatak IZ34A (integralni ispit, 09.10.1998. godine) Zadatak Z14

Programski jezik C

Zadatak C5 Zadatak C10 Operatori: prioritet i redosled primene Zadatak C15 Zadatak C20 Zadatak C25 Zadatak C30 Zadatak C35 Zadatak C40 Zadatak C45 Zadatak C47 Zadatak C48 Zadatak C50 Zadatak C55 Zadatak C57 Zadatak C60 Zadatak C65 Zadatak C70 Zadatak C75 Zadatak C80b Zadatak C90 Zadatak C95 Zadatak C100 Zadatak C104 Zadatak C110 Zadatak C115 Zadatak C120 Zadatak C122 Zadatak C125 Zadatak CI-2007-Jan-2 Zadatak CI-2006-Okt-1 Zadatak CI-2007-Okt-2 Zadatak CI-2006-Sep-2 Zadatak CI-2006-Jan-1 Zadatak CI-2006-Jan-2 Zadatak CI-2006-Sep-1 Zadatak C2008-A1 Zadatak C2008-S11 Zadatak C2008-S12 Zadatak C2008-S21 Zadatak C2008-S22

Materijal za vežbe na tabli i pripremu ispita

Programiranje 2

3

3 5 6 6 7 8 9 9

10

10 11 12 13 14 15 16 17 17 18 19 19 20 21 22 22 23 24 25 26 27 28 29 30 32 33 34 37 41 42 43 44 45 46 48 49 50 50 51 51 52

Strana 2/52

Elektrotehnički fakultet Univerziteta u Beogradu

Programiranje 2

PREDSTAVLJANJE REALNIH BROJEVA Standard za aritmetiku realnih brojeva – – –

Raznolikost predstavljanja realnih brojeva u računarima. Problem portabilnosti numeričkog softvera. Standard za binarnu aritmetiku realnih brojeva u pokretnom zarezu: ANSI/IEEE Std 754-1985. (IEEE – The Institute of Electrical and Electronics Engeneers ANSI – American National Standard Institute)

Standard specificira: 1. Osnovni i prošireni format realnih brojeva 2. Operacije +, –, *, /, ostatka (rem), kvadratnog korena( 3. 4. 5. 6.

) i komparaciju realnih brojeva

Konverzije: celi brojevi ⇔ realni brojevi Konverzije: realni brojevi ⇔ realni brojevi (različiti formati brojeva) Konverzije: osnovni format realnih brojeva ⇔ decimalni niz znakova Rukovanje kodovima grešaka

Osnovna osobina standarda: niz bitova, generisan kao rezultat aritmetičke operacije, može nositi dvojaku informaciju: 1. ispravno obavljena operacija ⇒ niz bitova je rezultat operacije 2. detektovana je neka greška ⇒ niz bitova je binarno kodirani signal greške (tzv. NaN = Not a Number) Postoje dva tipa NaN–a: 1. signalni NaN (signal NaN) – signalizira neispravnu operaciju kad god se pojavi u ulozi operanda 2. tihi (mirni) NaN (quiet NaN) – za skoro sve aritmetičke operacije ne signalizira neispravnost U skladu sa IEEE standardom, realni brojevi se u memorijske reči smeštaju na sledeći način: memorijska reč broj bita u datom polju smisao polja

s eeeeeee mmmmmmmmmmm 1 k p znak eksponent mantisa

Ukupan broj bita: n = 1 + k + p Vrednost tako predstavljenog realnog broja se računa na sledeći način:

R = (–1)s⋅2E⋅M gde vrednosti s, E i M imaju sledeća tumačenja: • s predstavlja znak datog broja (s=0 ⇒ broj je pozitivan; s=1 ⇒ broj je negativan) • E predstavlja celobrojni eksponent, čija se vrednost računa u kôdu sa viškom, kao E = e-v, gde je o e vrednost neoznačenog celog broja čija se vrednost dobija na osnovu bita eee...e, polja eksponent o v se naziva višak, a predstavlja celobrojnu vrednost koja se računa: v=2k-1-1, gde je k broj bita polja eksponent • M predstavlja vrednost mantise sa skrivenim bitom (videti objašnjenje u nastavku) Materijal za vežbe na tabli i pripremu ispita

Strana 3/52

Elektrotehnički fakultet Univerziteta u Beogradu

Programiranje 2

U zavisnosti vrednosti bita polja eksponent, postoje četiri različita načina tumačenja sadržaja memorijske reči u kojoj je smešten realan broj. 1. Kada biti polja eksponent nisu sve 0 ili sve 1 (e≠0000...0 i e≠1111...1), vrednost eksponenta E se nalazi u opsegu [Emin, Emax] i tada memorijska reč sadrži uobičajen realni broj sa normalizovanom mantisom. Vrednost mantise M se računa dodavanjem skrivenog bita vrednosti 1 i decimalnog zareza ispred niza bita polja mantisa: M=1.mmmmm...m. Skriveni bit se podrazumeva pa zbog toga nije potrebno posebno ga skladištiti u memorijskoj reči. 2. Kada su biti polja eksponent sve 0 (e=0000...0), ali biti polja mantisa nisu sve 0 (m≠0000...0), tada memorijska reč sadrži realan broj sa nenormalizovanom mantisom. Vrednost eksponenta E se u ovom specijalnom slučaju računa kao E=e-v+1. Vrednost mantise M se računa dodavanjem skrivenog bita vrednosti 0 i decimalnog zareza ispred niza bita polja mantisa: M=0.mmmm...m. Kao i u prethodnom slučaju, ovaj skriveni bit se podrazumeva. Smisao realnih brojeva sa nenormalizovanom mantisom je proširenje opsega za realne brojeve veoma male apsolutne vrednosti. 3. Kada su biti polja eksponent i mantisa sve 0 (e=0000...0, m=0000...0), tada memorijska reč sadrži realan broj čija je vrednost 0. Treba primetiti da zbog znaka (bit s), standard dozvoljava postojanje pozitivne i negativne nule (+0 i -0) ali između njih ne pravi razliku (tj. imaju istu vrednost). 4. Kada su biti polja eksponent sve 1 (e=1111...1), vrednost eksponenta E je veća od Emax i razlikuju se dva slučaja: a. vrednosti bita polja mantisa su sve 0 (m=0000...0) : realni broj ima vrednost ±∞ (u zavisnosti od vrednosti bita s) b. vrednosti bita polja mantisa nisu sve 0 (m≠0000...0) : radi se o NaN-u, koji označava matematički nedefinisanu vrednost Pozitivni realni brojevi, manji od najmanjeg realnog broja (minREALl) koji je moguće predstaviti sa zadatim brojem bita se zaokružuju na vrednost 0 (tzv. potkoračenje, eng. underflow), dok se pozitivni realni brojevi veći od najvećeg realnog broja (maxREAL) zaokružuju na vrednost +∞ (tzv. prekoračenje, eng. overflow). Slično važi za negativne realne brojeve. -∞ [ -maxREAL

]

0 |

-minREAL

[

] +∞

minREAL

maxREAL

Standardni formati realnih brojeva IEEE Standard uvodi 4 formata realnih brojeva (format se odnosi na broj bitova pojedinih polja): (1) jednostruki (2) jednostruki prošireni (3) dvostruki (4) dvostruki prošireni FORMAT jednostruki jednostruki prošireni dvostruki dvostruki prošireni

w 32 ≥ 43 64 ≥ 79

Materijal za vežbe na tabli i pripremu ispita

k 8 ≥ 11 11 ≥ 15

p 23 ≥ 32 52 ≥ 64

v +127 nedef. +1023 nedef.

Emax +127 ≥ +1023 +1023 ≥ +16383

Emin -126 ≤ -1022 -1022 ≤ -16382

Strana 4/52

Elektrotehnički fakultet Univerziteta u Beogradu

Programiranje 2

Jednostruki format 1 k=8 p = 23 s e e e e e e e e mmmmmmmmmmmmmmmmmmmmmmm 31 30 23 22 0 Opseg brojeva: 1. Najmanji nenormalizovani: e=0 ⇒ 2. Najveći nenormalizovani: e=0 ⇒ 3. Najmanji normalizovani: e=1 ⇒ 4. Najveći realan broj: e=254 ⇒

R R R R

= = = =

(–1)s⋅2-126⋅(0.00000…………1) (–1)s⋅2-126⋅(0.11111…………1) (–1)s⋅2-126⋅(1.00000…………0) (–1)s⋅2127⋅(1.11111…………1)

minREAL = (–1)s⋅2-126⋅2-23 = (–1)s⋅2-149 ≈ (–1)s⋅1.4⋅10-45 maxREAL = (–1)s 2127⋅(1.111...1) ≈ (–1)s⋅2128 = (–1)s⋅3.4⋅1038

Zaokruživanje Zbog ograničenog broja bita polja mantisa, najčešće nije moguće prezicno smeštanje realnog broja u memorijsku reč, već se pristupa zaokruživanju. Podrazumeva se da je posmatrani broj, pre smeštanja u memorijsku reč, beskonačno precizan, pa se zaokruživanje vrši sa ciljem da se uklopi u odredišni format. Skoro sve operacije nad realnim brojevima daju najpre “beskonačno precizan” rezultat koji se potom zaokružuje prema odredišnom formatu. 1. Osnovni način zaokruživanja: prema najbližoj vrednosti. 2. Ako su dve zaokružene vrednosti podjednako udaljene od “beskonačno precizne” vrednosti – bira se ona vrednost kod koje je bit najmanjeg značaja 0. 3. Ako je apsolutna vrednost “beskonačno precizne” vrednosti: |R| ≥ 2Emax⋅(2–2–p), vrši se zaokruživanje na (–1)s⋅(∞) p i Emax se određuju iz odredišnog formata. U praksi, ovo znači sledeće (desno od vertikalne crte se nalaze biti koji se odbacuju): 1 binarna x.xx | 0zzz...zzz predstava broja vrednost na koju x.xx se zaokružuje

2 x.xx | 1yyy...yyy (makar jedan y je 1) x.xx + 0.01

3 x.x0 | 100...00

4 x.x1 | 100...00

x.x0

x.x1 + 0.01

Ako se pažljivije pogleda, ovakav način zaokruživanja odgovara uobičajenom načinu zaokruživanja realnih brojeva. Gubitak tačnosti ima za posledicu da kod računarskog sabiranja realnih brojeva ne važi uvek zakon asocijativnosti. Primer1: decimalni računar sa 4 značajne cifre: R=123.4, r=0.049 ⇒ R+r=123.449 ≈ 123.4 Z1 = ((R+r)+r)+r = 123.4 Z2 = ((r+r)+r)+R = 123.4+0.147 = 123.547 ≈ 123.5 ≠ Z1 Opšte pravilo: početi sumiranje od brojeva manje apsolutne vrednosti.

Materijal za vežbe na tabli i pripremu ispita

Strana 5/52

Elektrotehnički fakultet Univerziteta u Beogradu

Programiranje 2

Zadatak Z12 Za smeštanje realnih brojeva koriste se 8 bitova od kojih 3 bita predstavljaju eksponent u kodu sa viškom. Mantisa se smešta sa skrivenim bitom. Za brojeve veće od nule odrediti najmanju i najveću vrednost koja se može predstaviti na gornji način.

Zadatak IZ3 Napomena: ovaj zadatak ilustruje razliku između savremenog IEEE standarda za reprezentaciju realnih brojeva i standarda VAX koji mu je prethodio. Format predstave realnih brojeva je seeeemmmmm – s je bit za znak broja, eeee su bitovi za eksponent broja (kôd sa viškom), a mmmmm su bitovi normalizovane mantise (sa skrivenim bitom). U računaru A – predstava realnih brojeva je sa viškom 8, a u računaru B – sa viškom 7. U računaru A – mantisa je 0.5≤MAB: "); scanf("%f,%f", &A,&B); printf("\n Unesi drugi skup Y[C,D] C>D: "); scanf("%f,%f", &C,&D); /* Logika zadatka je sljedeca: Prvo moramo pretpostaviti da je prvi broj intervala veci ili jednak drugom, A>=B i C>=D, jer ako ovo ne vrijedi interval nije matematicki ispravan (taj uslov nismo ispitivali da nebi komplikovali zadatak). Presjek skupa ce biti neki interval [MIN,MAX]. Uslov da zadatak ima RJEŠENJE je da je B < C kao i D < A sto je i logicno jer u suprotnom presjeka nema! Broj MIN je ustvari jedan od brojeva A ili C (manji broj od ova 2 broja), a MAX je manji broj od brojeva B i D. Ako je A=C ili B=D uzima se vrijednost pod ELSE ali tada nam je svakako svejedno koja ce se uzeti vrijednost kao MIN ili MAX jer je tada A=C odnosno B=D */ if (A>C) MAX=A; else MAX=C; if (B-10&&a-10&&b-10&&c"); scan("%d %d", &xa, &ya); printf("Upisite koordinate vrha trokuta B >"); scan("%d %d", &xb, &yb); printf("Upisite koordinate vrha trokuta C >"); scan("%d %d", &xc, &yc); xTezista = xa + xb + xc / 3; yTezista = ya + yb + yc / 3; printf("Teziste trokuta A(%d, %d), B(%d, %d), C(%d, %d) jest T(%d, %d)" , xa, ya, xb, yb, xc, yc, xTezista, yTezista); return 0 }

Zatim pomoću copy-paste upišite program u datoteku na svom računalu i pokušajte ga prevesti. Prvo ispravite isključivo sintaktičke pogreške. Nakon toga ispravite pogreške povezivanja. Kada se program uspješno prevede, testirajte program s različitim ulaznim podacima: - za vrhove trokuta s koordinatama (0, 0), (3, 3) i (6, 0) - za vrhove trokuta s koordinatama (0, 0), (1, 1) i (2, 0) - za vrhove trokuta s koordinatama (0, 0), (1.5, 1.5) i (3, 0) Ispravite sve logičke pogreške. Uputa: pri rješavanju ovog zadatka korisno je poslužiti se C prevodiocem, koji će otkriti one sintaktičke pogreške i pogreške povezivanja koje sami možda ne uspijete uočiti. 2. Napisati C program kojim će se s tipkovnice učitati pozitivni cijeli broj koji ima 5 znamenaka. Nije potrebno provjeravati je li korisnik upisao ispravan broj. Program treba na zaslon ispisati prvu i posljednju znamenku učitanog broja. 3. Napisati C program kojim će se s tipkovnice učitati pozitivni cijeli broj koji ima 5 znamenaka. Nije potrebno provjeravati je li korisnik upisao ispravan broj. Program treba na zaslon ispisati drugu i četvrtu znamenku.

1

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 02-UvodProgramiranje.pdf - do stranice: 47

Rješenja 1. Sintaktičke pogreške: − točka-zarez se ne smije stavljati iza pretprocesorskih naredbi − definicija varijabli xa, ya, xb, yb, itd. je neispravna. Varijable koje se odjednom definiraju međusobno se odjeljuju zarezom, a ne točkom-zarezom. Tip integer ne postoji. − varijable xTezista i yTezista nisu definirane − nedostaje točka-zarez iza naredbe return. Pogreške povezivanja: − u stdio.h ne postoji funkcija scan (postoji scanf) − program mora sadržavati funkciju main (a ne mein) Logičke pogreške: − koordinate težišta se neispravno izračunavaju. X koordinate triju vrhova treba prvo zbrojiti, a tek tada podijeliti s 3 (trenutno se na kvocijent z/3 dodaje vrijednost za x i y. Isto vrijedi i za y koordinate. − nakon dodavanja zagrada program će ispravno raditi za koordinate vrhova (0, 0), (3, 3) i (6, 0), ali ne i za koordinate (0, 0), (1, 1) i (2, 0). Razlog je u cjelobrojnom dijeljenju i tipu rezultata: 0+1+0 cjelobrojno podijeljeno s 3 daje 0, a ne 0.33333. Potrebno je promijeniti tip svih varijabli: koordinate vrhova trokuta i težišta trebaju biti realni, a ne cijeli brojevi. Program nakon ispravljanja svih pogrešaka: #include int main (void) { float xa, ya, xb, yb, xc, yc; float xTezista, yTezista; printf("Upisite koordinate vrha trokuta A >"); scanf("%f %f", &xa, &ya); printf("Upisite koordinate vrha trokuta B >"); scanf("%f %f", &xb, &yb); printf("Upisite koordinate vrha trokuta C >"); scanf("%f %f", &xc, &yc); xTezista = (xa + xb + xc) / 3; yTezista = (ya + yb + yc) / 3; printf("Teziste trokuta A(%f, %f), B(%f, %f), C(%f, %f) jest T(%f, %f)" , xa, ya, xb, yb, xc, yc, xTezista, yTezista); return 0; }

2

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 02-UvodProgramiranje.pdf - do stranice: 47

2. #include int main (void) { int broj; printf("Upisite 5-znamenkasti pozitivni cijeli broj: "); scanf("%d", &broj); printf("Prva znamenka: %d\n", broj/10000); printf("Posljednja znamenka: %d\n", broj%10); return 0; }

3. #include int main (void) { int broj; printf("Upisite 5-znamenkasti pozitivni cijeli broj: "); scanf("%d", &broj); printf("Druga znamenka: %d\n", broj%10000/1000); printf("Cetvrta znamenka: %d\n", broj%100/10); return 0; }

3

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 02-UvodProgramiranje.pdf - do stranice: 84

Napomene: - Savjetuje se navedene zadatke riješiti ubrzo nakon predavanja - Savjetuje se ne gledati rješenja prije nego se pokuša samostalno riješiti zadatke

3. vježbe uz predavanja 1. Pročitajte i zaključite što bi sljedeći program (kada bi bio ispravan) trebao raditi. Napišite tekst programskog zadatka koji bi odgovarao programu. Nacrtajte dijagram toka. Zatim pomoću copy-paste upišite program u datoteku na svom računalu i pokušajte ga prevesti. Ispravite sve vrste pogrešaka i testirajte s različitim ulaznim podacima. #definiraj PI 3,1415926 float main (void) { int a; float b; printf("Upisite duljinu velike poluosi a>"); scanf("%f", &a); printf("Upisite duljinu male poluosi b>"); scanf("%f", &b); if (a > 0) { if (b > 0) { povrsina = A x B x pi; printf("Povrsina elipse s poluosima a=%f i b=%f je %f", povrsina); scanf("%f", &b); } else { printf("Duljina male poluosi mora biti veca od nule.\nKraj. "); } } else { printf("Duljina velike poluosi mora biti veca od nule.\nKraj. "); } return O; }

2. Napisati C program za pretvaranje vrijednosti temperature izražene u različitim mjernim jedinicama. Na početku program treba ispisati poruku: Program za konverziju Fahrenheit - Celsius i obrnuto. Za F u C upisite 1, a za C u F bilo koji drugi cijeli broj >

Ako korisnik upiše broj 1, tada ispisati poruku Upisite temperaturu izrazenu u stupnjevima Fahrenheit >

te s tipkovnice učitati realni broj koji predstavlja temperaturu izraženu u stupnjevima Fahrenheit, izračunati ekvivalentnu temperaturu izraženu u stupnjevima Celsius te ispisati temperaturu izraženu u obje jedinice xxxx.xxx st. F = xxxx.xxx st. C

Ako korisnik upiše bilo koji drugi cijeli broj, tada ispisati poruku Upisite temperaturu izrazenu u stupnjevima Celsius >

te s tipkovnice učitati jedan realni broj koji predstavlja temperaturu izraženu u stupnjevima Celsius, izračunati ekvivalentnu temperaturu izraženu u stupnjevima Fahrenheit te ispisati temperaturu izraženu u obje jedinice xxxx.xxx st. C = xxxx.xxx st. F

1

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 02-UvodProgramiranje.pdf - do stranice: 84

3. Nacrtati dijagram toka i napisati C program za zbrajanje ili množenje realnih brojeva. Na početku program treba ispisati poruku Program za zbrajanje ili mnozenje. Upisite dva realna broja >

i učitati dva realna broja koji predstavljaju operande. Nakon toga program treba ispisati poruku Upisite vrstu operacije (1-zbrajanje, 2-mnozenje) >

i učitati cijeli broj kojim se određuje vrsta operacije. Ako je korisnik upisao broj 1, tada treba izračunati i ispisati zbroj operanada, a ako je upisao broj 2, tada izračunati i ispisati umnožak operanada. Ako za odabir vrste operacije korisnik nije upisan niti broj 1 niti broj 2, tada treba ispisati poruku Neispravan odabir operacije.

2

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 02-UvodProgramiranje.pdf - do stranice: 84

Rješenja 1. Programski zadatak: napisati program kojim će se učitati duljine velike i male poluosi elipse. Nakon učitavanja duljina obje osi, ispitati ispravnost unesenih podataka. Ako je velika poluos neispravno zadana, ispisati poruku "Duljina velike poluosi mora biti veca od nule.", u sljedećem retku na zaslonu ispisati "Kraj", te završiti s izvršavanjem programa. Ako je mala poluos neispravno zadana, ispisati poruku "Duljina male poluosi mora biti veca od nule.", u sljedećem retku na zaslonu ispisati "Kraj", te završiti s izvršavanjem programa. Ako su duljine obje osi ispravno zadane, izračunati površinu elipse i ispisati poruku " Povrsina elipse s poluosima a=x.xxxxxx i b=x.xxxxxx je x.xxxxxx. Dijagram toka: učitaj a, b Da a>0 Ne ispiši "neispravna velika os, kraj"

Da b>0 Ne ispiši "neispravna mala os, kraj"

izračunaj i ispiši površinu elipse

Program nakon ispravljanja svih pogrešaka: #include #define PI 3.1415926 int main (void) { float a; float b; float povrsina; printf("Upisite duljinu velike poluosi a> "); scanf("%f", &a); printf("Upisite duljinu male poluosi b> "); scanf("%f", &b); if (a > 0) { if (b > 0) { povrsina = a * b * PI; printf("Povrsina elipse s poluosima a=%f i b=%f je %f", a, b, povrsina); } else { printf("Duljina male poluosi mora biti veca od nule.\nKraj"); } } else { printf("Duljina velike poluosi mora biti veca od nule.\nKraj"); } return 0; }

3

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 02-UvodProgramiranje.pdf - do stranice: 84

2. #include int main (void) { float cels, fahr; int izbor; printf("Program za konverziju Fahrenheit - Celsius i obrnuto.\n"); printf("Za F u C upisite 1, a za C u F bilo koji drugi cijeli broj >"); scanf("%d", &izbor); if (izbor == 1) { printf("Upisite temperaturu izrazenu u stupnjevima Fahrenheit >"); scanf("%f", &fahr); cels = (fahr - 32.) * 5. / 9.; printf("%8.3f st. F = %8.3f st. C\n", fahr, cels); } else { printf("Upisite temperaturu izrazenu u stupnjevima Celsius >"); scanf("%f", &cels); fahr = cels * 9. / 5. + 32.; printf("%8.3f st. C = %8.3f st. F\n", cels, fahr); } return 0; }

3. U ovom rješenju je prikazan samo dio dijagrama toka: Ne op = 1 Da ispiši zbroj

Ne op = 2 Da ispiši umnožak

ispiši "neispravna operacija"

#include int main (void) { float x, y; int operacija; printf("Program za zbrajanje ili mnozenje.\nUpisite dva realna broja >"); scanf("%f %f", &x, &y); printf("Upisite vrstu operacije (1-zbrajanje, 2-mnozenje) >"); scanf("%d", &operacija); if (operacija == 1) printf("Zbroj je %f\n", x + y); else if (operacija == 2) printf("Umnozak je %f\n", x * y); else printf("Neispravan odabir operacije.\n"); return 0; }

4

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 30

Napomene: - Savjetuje se navedene zadatke riješiti ubrzo nakon predavanja - Savjetuje se ne gledati rješenja prije nego se pokuša samostalno riješiti zadatke

4. vježbe uz predavanja 1. Dekadski broj 29 prikažite u obliku binarnog broja 2. Binarni broj 10011011 prikažite u obliku dekadskog broja (za prikaz ovog binarnog broja nije korištena tehnika dvojnog komplementa) 3. Registar od 8 bitova koristi se za prikaz brojeva tehnikom dvojnog komplementa. Koja dekadska vrijednost je prikazana u registru, ako je sadržaj registra 00011011. 4. Registar od 8 bitova koristi se za prikaz brojeva tehnikom dvojnog komplementa. Koja dekadska vrijednost je prikazana u registru, ako je sadržaj registra 10011011. 5. Dekadski broj -14 prikazati kao binarni broj u registru od 5 bitova, korištenjem tehnike dvojnog komplementa 6. Dekadski broj -14 prikazati kao binarni broj u registru od 10 bitova, korištenjem tehnike dvojnog komplementa 7. Koji se najveći i najmanji broj (izraziti u dekadskom obliku) može pohraniti u registru od 12 bita a. ako se ne koristi tehnika dvojnog komplementa b. ako se koristi tehnika dvojnog komplementa 8. Koliko najmanje bitova treba imati registar ako je u njega potrebno pohraniti dekadski broj 38 a. ako se ne koristi tehnika dvojnog komplementa b. ako se koristi tehnika dvojnog komplementa 9. U binarnom brojevnom sustavu, uz primjenu tehnike dvojnog komplementa, koristeći registre veličine 5 bitova, obavite operacije: a. 410 + 710 b. 1210 - 510 c. 710 + 1110 d. 1210 - 1610 Rezultate provjerite pretvorbom dobivenih binarnih rezultata u dekadske brojeve 10. Dekadski broj 110 pretvoriti u oktalni broj: a. direktno (uzastopnim dijeljenjem s 8) b. indirektno, grupiranjem znamenaka binarnog broja 11. Dekadski broj 94 pretvoriti u heksadekadski broj: a. direktno (uzastopnim dijeljenjem sa 16) b. indirektno, grupiranjem znamenaka binarnog broja 12. Heksadekadske brojeve F2C i 4E napisati u obliku binarnih i oktalnih brojeva 13. Oktalni broj 76431 napisati u obliku heksadekadskog broja

1

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 30

14. Dekadski broj -9 pohraniti u registar od 5 bitova (tehnikom dvojnog komplementa). Rezultat prikazati kao a. binarni broj b. heksadekadski broj c. oktalni broj

2

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 30

Rješenja 1.

29 14 7 3 1

: : : : :

2 2 2 2 2

= 14 = 7 = 3 = 1 = 0

ostatak ostatak ostatak ostatak ostatak

1 0 1 1 1

Broj 29 je pozitivan, stoga nije potrebno izračunavati dvojni komplement. Rješenje:

11101

Provjera:

1⋅24 + 1⋅23 + 1⋅22 + 0⋅21 + 1⋅20 = 29

2. Prva znamenka jest jedinica, ali u zadatku piše da nije korištena tehnika dvojnog komplementa. To znači da se radi o pozitivnom broju: 1⋅27 + 0⋅26 + 0⋅25 + 1⋅24 + 1⋅23 + 0⋅22 + 1⋅21 + 1⋅20 = 155 3. U zadatku piše da se za prikaz broja koristi tehnika dvojnog komplementa, ali prvi bit u registru nije jedinica. To znači da je u registru prikazan pozitivan broj. Vrijednost određujemo na isti način kao da se tehnika dvojnog komplementa uopće ne koristi: 1⋅24 + 1⋅23 + 0⋅22 + 1⋅21 + 1⋅20 = 27 4. Za prikaz broja se koristi tehnika dvojnog komplementa, a prvi bit u registru jest jedinica. To znači da je u registru prikazan neki negativan broj x. Izračunavanjem dvojnog komplementa dobit će se broj koji je jednak po apsolutnoj vrijednosti, ali suprotnog predznaka (dakle, pozitivan broj): 10011011

-> x

01100100

-> jedinični komplement

+ 1 = 01100101

dodaje se jedan kako bi se dobio dvojni komplement -> -x

Dobiveni broj -x je pozitivan broj (prvi bit mu nije jedinica), stoga se lako može odrediti o kojem se dekadskom broju radi: 1⋅26 + 1⋅25 + 0⋅24 + 0⋅23 + 1⋅22 + 0⋅21 + 1⋅20 = 10110 Ako je -x=10110, onda je x=-10110. Konačno rješenje jest: u registru je pohranjen dekadski broj -101 5. Negativni cijeli brojevi prikazuju se tehnikom dvojnog komplementa. Nije moguće direktno odrediti binarni prikaz broja -14, stoga se prvo određuje binarni prikaz pozitivnog broja 14 (uzastopnim dijeljenjem s 2). Voditi računa o tome da registar ima 5 bitova! +1410 = 011102 3

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 30

Binarni broj iste apsolutne vrijednosti, ali suprotnog predznaka dobije se izračunavanjem dvojnog komplementa 01110 10001 +

1

= 10010

-> jedinični komplement dodaje se jedan kako bi se dobio dvojni komplement -> dvojni komplement

Konačno rješenje: -14 prikazan u tehnici dvojnog komplementa u registru od 5 bitova jest 10010 6. Slično kao u prethodnom zadatku. Treba voditi računa da se sada radi o 10-bitnom registru! +1410 = 00000011102 Negativna vrijednost dobije se izračunavanjem dvojnog komplementa 0000001110 1111110001 +

-> jedinični komplement dodaje se jedan kako bi se dobio dvojni komplement

1

= 1111110010

-> dvojni komplement

Konačno rješenje: -14 prikazan u tehnici dvojnog komplementa u registru od 10 bitova jest 1111110010 7. a) Ako se ne koristi tehnika dvojnog komplementa, raspon brojeva koji se može prikazati u registru od n bitova jest [0, 2n-1]. Najveći broj koji se može prikazati u 12-bitnom registru jest 212-1 = 4095. Najmanji broj koji se može prikazati jest 0. b) Ako se koristi tehnika dvojnog komplementa, raspon brojeva koji se može prikazati u registru od n bitova jest [-2n-1, 2n-1-1]. Najveći broj koji se može prikazati u 12-bitnom registru jest 211-1 = 2047. Najmanji broj koji se može prikazati jest -2048. 8. a) Ako se ne koristi tehnika dvojnog komplementa, raspon brojeva koji se može prikazati u registru od 5 bitova jest [0, 31], a u registru od 6 bitova [0, 63]. Potreban je registar od 6 bitova. b) Ako se koristi tehnika dvojnog komplementa, raspon brojeva koji se može prikazati u registru od 6 bitova jest [-32, 31], a u registru od 7 bitova [-64, 63]. Potreban je registar od 7 bitova.

9. a)

00100 + 00111 = 01011

-> 4 -> 7 -> 11

4

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 30

b)

01100 + 11011 = 00111

-> 12 -> -5 -> 7

c)

00111 + 01011 = 10010

-> 7 -> 11 -> -14

U ovom slučaju rezultat nije kakav bi se očekivao jer se u registru od 5 bitova, u tehnici dvojnog komplementa, broj 18 ne može prikazati. d)

01100 + 10000 = 11100

-> 12 -> -16 -> -4

10. a) 110 : 8 = 13 13 : 8 = 1 1 : 8 = 0

ostatak 6 ostatak 5 ostatak 1

Rješenje:

156

Provjera:

1⋅82 + 5⋅81 + 6⋅80

b) 11010 = 11011102 Binarne znamenke grupirati po tri. PAZITI da se grupiranje obavi "s desna na lijevo": 1 101 1102 = 1568 11. a)

94 : 16 = 5 5 : 16 = 0

ostatak 14 ostatak 5

Rješenje:

5E

Provjera:

5⋅161 + 14⋅160

b) 9410 = 10111102 Binarne znamenke grupirati po četiri. PAZITI da se grupiranje obavi "s desna na lijevo": 101 11102 = 5E16

5

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 30

12. Svaka heksadekadska znamenka pretvara se u četiri binarne: F 2 C16 = 1111 0010 11002 4 E16 = 0100 11102 Heksadekadski broj se lako pretvara u oktalni: heksadekadski broj treba napisati kao binarni broj, zatim binarne znamenke grupirati u grupe po tri. PAZITI da se grupiranje obavi "s desna na lijevo": F 2 C16 = 1111 0010 11002 = 111 100 101 1002 = 74548 4 E16 = 0100 11102 = 01 001 1102 = 1168 13. Oktalni broj se lako pretvara u heksadekadski: oktalni broj treba napisati kao binarni broj, zatim binarne znamenke grupirati u grupe po četiri. PAZITI da se grupiranje obavi "s desna na lijevo": 7 6 4 3 18 = 111 110 100 011 0012 = 111 1101 0001 10012 = 7D1916 14.

-910 = 101112 = 1716 = 278

6

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 67

Napomene: - Savjetuje se navedene zadatke riješiti ubrzo nakon predavanja - Savjetuje se ne gledati rješenja prije nego se pokuša samostalno riješiti zadatke

5. vježbe uz predavanja 1. Koje vrijednosti poprimaju varijable a, b nakon izvođenja sljedećeg programskog odsječka: char a; short int b; a = 120; b = 32000; a = a + 10; b = b + 1000;

Svoje rješenje provjerite tako da programski odsječak, dopunjen naredbom za ispis vrijednosti varijabli na zaslon i ostalim nužnim naredbama, izvedete na svom računalu. 2. Napisati sadržaj registra u kojem je, prema IEEE 754 standardu za prikaz brojeva u jednostrukoj preciznosti, pohranjen broj -17.7812510. Sadržaj registra napisati u oktalnom i heksadekadskom obliku. 3. U registru od 32 bita upisan je broj C2 B0 00 0016. Napisati koji je broj predstavljen u tom registru, ako registar služi za pohranu varijable tipa float. Rezultat napisati u dekadskom brojevnom sustavu. 4. U registru od 32 bita upisan je broj 43 00 20 0016. Napisati koji je broj predstavljen u tom registru, ako registar služi za pohranu varijable tipa float. Rezultat napisati u dekadskom brojevnom sustavu. 5. U registru od 32 bita upisan je broj 3 01 22 40 00 008. Napisati koji je broj predstavljen u tom registru, ako registar služi za pohranu varijable tipa float. Rezultat napisati u dekadskom brojevnom sustavu. 6. U registru od 32 bita upisan je broj 3 77 40 00 00 008. Napisati koji je broj predstavljen u tom registru, ako registar služi za pohranu varijable tipa float. 7. U registru od 32 bita upisan je broj 7F C0 00 0016. Napisati koji je broj predstavljen u tom registru, ako registar služi za pohranu varijable tipa float. 8. U registru od 32 bita upisan je broj 80 00 00 0016. Napisati koji je broj predstavljen u tom registru, ako registar služi za pohranu varijable tipa float. 9. U registru od 32 bita upisan je broj 00 68 00 0016. Napisati koji je broj predstavljen u tom registru, ako registar služi za pohranu varijable tipa float. 10. U registru od 32 bita upisan je broj 80 00 00 0116. Napisati koji je broj predstavljen u tom registru, ako registar služi za pohranu varijable: a. signed int i; b. unsigned int j; c. float x; Rezultate napisati u dekadskom brojevnom sustavu.

1

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 67

11. Napisati sadržaje registara u kojima je, prema IEEE 754 standardu za prikaz brojeva u jednostrukoj preciznosti, pohranjen sadržaj varijabli x i y nakon obavljanja sljedećih naredbi: float x, y; x = 0.f; y = -3.75f / x; Sadržaje registara napisati u heksadekadskom obliku. 12. Napisati sadržaj registra u kojem je, prema IEEE 754 standardu za prikaz brojeva u jednostrukoj preciznosti, pohranjen sadržaj varijable x nakon obavljanja sljedećih naredbi: float x; x = 0.f; x = x / x; Sadržaj registra napisati u heksadekadskom obliku. 13. Što će se ispisati uz pomoć sljedećih naredbi (napomena: u svim naredbama 0 predstavlja znamenku nula, a ne slovo O) char c; c = 'A' + '0'; printf("%d\n", c); printf("%c\n", c); printf("%c\n", 'D' - 'A' + '0'); printf("%d\n", 'D' - 'A' + '0'); printf("%d\n", '7' - '5'); printf("%d\n", '7' - 5); printf("%c\n", '7' - 5); printf("%d\n", '0' % 10);

2

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 67

Rješenja 1. Pri rješavanju ovakvih zadataka treba se sjetiti koji se najveći/najmanji brojevi mogu prikazati u varijablama određenih tipova podataka. 2. Prvi bit za predznak se postavlja na P=1. Time je pitanje predznaka riješeno (upamtiti: u IEEE 754 formatu se ne koristi ništa što podsjeća na tehniku dvojnog komplementa!) Sada treba odrediti karakteristiku i mantisu. Prvo pretvoriti broj u binarni oblik: 17.7812510 = 10001.110012 Normalizirati: 10001.110012 = 1.000111001 ⋅ 24 BE = 410 ⇒ K = 4 + 127 = 13110 = 100000112 M = 1.000111001 U 32-bitni registar prepisati P, K, te M (ali BEZ SKRIVENOG BITA!): 1 10000011 00011100100000000000000 Grupirati po tri znamenke s desna na lijevo 11 000 001 100 011 100 100 000 000 000 0002 = 301434400008 Grupirati po četiri znamenke s desna na lijevo 1100 0001 1000 1110 0100 0000 0000 00002 = C18E400016 3. Varijable tipa float pohranjuju se prema IEEE 754 formatu jednostruke preciznosti C2 B0 00 0016 = 1100 0010 1011 0000 0000 0000 0000 00002 Odrediti predznak: P = 1, stoga je broj negativan. Odrediti binarni eksponent: K = 100001012 = 13310 ⇒ BE = 133 - 127 = 6 Odrediti mantisu (vratiti joj skriveni bit!): "mantisa bez skrivenog bita" = .011000000000000000000002 M = 1.011000000000000000000002 Rezultat se dobije množenjem mantise s 2BE 1.011000000000000000000002 ⋅ 26 = 1011000.02 = 88.010 Ne zaboraviti negativni predznak (jer P=1) Konačni rezultat: -88.0 4.

128.12510

5.

-12.62510 3

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 67

6. K = 255, u mantisi su svi bitovi postavljeni na nulu. Radi se o prikazu beskonačnosti. Budući da je predznak P=1, konačno rješenje jest: -∞. 7. K = 255, a u mantisi postoji jedan ili više bitova koji su postavljeni na jedinicu. U registru je prikazana vrijednost NaN (Not a Number). 8. K = 0, a u mantisi su svi bitovi postavljeni na 0. Radi se o prikazu broja 0. Budući da je predznak P=1, konačno rješenje jest: -0.0. 9. K = 0, a u mantisi postoje bitovi koji su postavljeni na jedan. Radi se o prikazu denormaliziranog broja. 00 68 00 0016 = 0000 0000 0110 1000 0000 0000 0000 00002 BE = -126 (kod denormaliziranog broja ne koristi se formula BE = K - 127) M = 0.1101 (kod denormaliziranog skriveni bit nije 1, nego 0) BE

Rezultat se dobije množenjem mantise s 2

0.11012 ⋅ 2-126 = 0.812510 ⋅ 2-126 ≈ 9.55 ⋅ 10-39 10. 80 00 00 0116 = 1000 0000 0000 0000 0000 0000 0000 0001 a) Radi se 1000 0111 + = 0111

o prikazu broja u tehnici dvojnog komplementa. Broj je negativan: 0000 0000 0000 0000 0000 0000 0001 1111 1111 1111 1111 1111 1111 1110 1 1111 1111 1111 1111 1111 1111 11112 = 214748364710

U registru je prikazan broj -2147483647 b) Radi se o prikazu broja u kojem se ne koristi tehnika dvojnog komplementa. Broj je pozitivan, unatoč tome što je prvi bit jedinica: 1000 0000 0000 0000 0000 0000 0000 00012 = 214748364910 c) Radi se o prikazu broja u IEEE 754 formatu: ≈ -1.4 ⋅ 10-45 11. Rezultat operacije je -∞. Rješenje jest: FF 80 00 0016 12.

Rezultat operacije je NaN. Rješenje jest (jedno od mogućih, jer na predavanjima nismo specificirali koje vrijednosti trebaju imati bitovi u mantisi kad se prikazuje NaN): 7F C0 00 0016

13.

Ispravnost vlastitog rješenja provjeriti izvođenjem programskog odsječka na računalu. 4

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 102

Napomene: - Savjetuje se navedene zadatke riješiti ubrzo nakon predavanja - Savjetuje se ne gledati rješenja prije nego se pokuša samostalno riješiti zadatke

6. vježbe uz predavanja 1. Odredite najveću moguću relativnu i najveću moguću apsolutnu pogrešku koja se može očekivati pri pohrani broja 2⋅1022 u IEEE 754 formatu jednostruke preciznosti. 2. Gdje se (i zašto) u sljedećem odsječku programa nalaze sintaktičke pogreške: int thin, tall, short; float which, while, when, why, who; char single, double, triple; signed long a777, 7b, _19; 3. Pronađite koje su konstante ispravno, a koje neispravno napisane. Za ispravno napisane konstante odredite kojeg su tipa i koliko okteta zauzimaju u memoriji: 2 4u 7f 9.1 14.5U 0101u 12.1L 12.1e+22F 12.1e22 12.1Fe-22 12.1E11L 12.1E11u 0x22L 0xABC 0x2f 2F 0x2F.1F 021.1f 4. Napisati sadržaj registra u kojem je, prema IEEE 754 standardu za prikaz brojeva u dvostrukoj preciznosti pohranjen broj -0.2510. Sadržaj registra napisati u heksadekadskom obliku. 5. U registru od 64 bita upisan je broj C0 3D 80 00 00 00 00 0016. Napisati koji je broj predstavljen u tom registru, ako registar služi za pohranu varijable double x. Rezultat napisati u dekadskom brojevnom sustavu. 6. Napisati sadržaj registra u kojem je, prema IEEE 754 standardu za prikaz brojeva u dvostrukoj preciznosti pohranjen broj -∞. Sadržaj registra napisati u heksadekadskom obliku. 7. Napisati sadržaj registra u kojem je, prema IEEE 754 standardu za prikaz brojeva u dvostrukoj preciznosti pohranjena vrijednost NaN. Sadržaj registra napisati u heksadekadskom obliku. 8. Odredite najveću moguću relativnu i najveću moguću apsolutnu pogrešku koja se može očekivati pri pohrani broja 2⋅1022 u IEEE 754 formatu dvostruke preciznosti.

1

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 102

Rješenja 1. Najveća moguće relativna pogreška ovisi isključivo o broju bitova mantise m. Vodite računa o tome da parametar m uključuje i skriveni bit. Kod prikaza prema IEEE 754 standardu jednostruke preciznosti m = 24. Najveća moguća relativna pogreška iznosi 2-24 ≈ 6 ⋅ 10-8 Najveća moguća apsolutna pogreška ovisi o parametru m i konkretnom broju x koji se prikazuje: Najveća moguća apsolutna pogreška iznosi x ⋅ 2-24 ≈ 2 ⋅ 1022 ⋅ 6 ⋅ 10-8 = 1.2 ⋅ 1015

2. int thin, tall, short; float which, while, when, why, who; char single, double, triple; signed long a777, 7b, _19; U prvom retku se za ime varijable koristi ključna riječ short; U drugom retku se za ime varijable koristi ključna riječ while; U trećem retku se za ime varijable koristi ključna riječ double; U četvrtom retku ime varijable 7b započinje znamenkom (nije dopušteno) 3. 2 4u 7f 9.1 14.5U 0101u 12.1L 12.1e+22F 12.1e22 12.1Fe-22 12.1E11L 12.1E11u 0x22L 0xABC 0x2f 2F 0x2F.1F 021.1F

signed int - 4 okteta unsigned int - 4 okteta pogreška: nedostaje točka double - 8 okteta pogreška: ne postoji tip unsigned double unsigned int u oktalnom obliku - 4 okteta long double - 8 okteta float - 4 okteta double - 8 okteta pogreška: F na pogrešnom mjestu long double - 8 okteta pogreška: ne postoji tip unsigned double long int u heksadekadskom obliku - 4 okteta int u heksadekadskom obliku - 4 okteta int u heksadekadskom obliku - 4 okteta pogreška: nedostaje točka pogreška: ne može se realni broj zapisati u heksadekadskom obliku float, 0 na početku nema nikakvo značenje, ali nije pogreška - 4 okteta

4. BFD0000000000000 5. -29.5

2

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 102

6. FFF0000000000000 7. Prikazano je jedno od mogućih rješenja. Bitno je da su svi bitovi karakteristike postavljeni na 1, te da je barem jedan bit mantise postavljen na 1 FFF8000000000000 8. Najveća moguća relativna pogreška iznosi 2-53 ≈ 1.1 ⋅ 10-16 Najveća moguća apsolutna pogreška iznosi x ⋅ 2-53 ≈ 2.2 ⋅ 106

3

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 132

Napomene: - Savjetuje se navedene zadatke riješiti ubrzo nakon predavanja - Savjetuje se ne gledati rješenja prije nego se pokuša samostalno riješiti zadatke

7. vježbe uz predavanja 1. Napisati C program koji će s tipkovnice učitati cijeli broj x, a zatim na zaslon ispisati tekst Istina je ako je učitani broj u intervalu [1, 9] ili je u intervalu [80, 90]. 2. Napisati C program koji će s tipkovnice učitati cijeli broj m, a zatim na zaslon ispisati tekst Istina je ako je učitani broj neparan pozitivan broj. 3. Napisati C program koji će na zaslon ispisati tekst Istina je ako uvjet iz 1. zadatka nije zadovoljen (napisati jedno rješenje uz korištenje operatora negacije i jedno rješenje bez korištenja operatora negacije). 4. Napisati C program koji će na zaslon ispisati tekst Istina je ako uvjet iz 2. zadatka nije zadovoljen (napisati jedno rješenje uz korištenje operatora negacije i jedno rješenje bez korištenja operatora negacije). 5. Napisati C program koji će s tipkovnice učitati dva znaka u varijable c1 i c2 tipa char. Ako su oba učitana znaka velika slova abecede (A-Z) i pri tome su oba znaka samoglasnici, ispisati tekst Ucitani znakovi su "veliki" samoglasnici (primijetite da unutar teksta treba dva puta ispisati i dvostruke navodnike). 6. Ispisati tekst Barem jedan od znakova nije veliki samoglasnik ako uvjet iz 5. zadatka nije zadovoljen (napisati jedno rješenje uz korištenja operatora negacije i jedno rješenje bez korištenja operatora negacije). 7. Napisati C program koji će s tipkovnice učitati znak. Ako je učitani znak malo slovo abecede ili znamenka, ispisati tekst Istina je. 8. Napisati C program koji će s tipkovnice učitati dva znaka. Ako oba učitana znaka predstavljaju heksadekadske znamenke, ispisati poruku Upisan je ispravan dvoznamenkasti heksadekadski broj. 9. Što je rezultat evaluacije svakog od sljedećih izraza (treba odrediti tip podatka i vrijednost): 12 / 2*3 15 / 2*3 15. / 2*3 15.f / 2*3 15.f / 2*3. 15 / 2*3. 12 / (2*3) 2 * 2+3 2 * 5%2 2 * (5%2) (float)15/2/3

1

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 03-TipoviPodataka.pdf - do stranice: 132

(float)(15/2/3) (float)(15/2)/3 (float)((15/2)/3) 3.5f * (double)4 + 3 * 5/(double)2

10. Što će biti sadržaj svake od definiranih varijabli nakon obavljanja sljedećeg programskog odsječka (za varijable tipa char treba navesti njihovu numeričku vrijednost): char c1, c2; float f1, f2; double f3, f4; c1 = 132.f - (double)2; f1 = -2147483648.0; f2 = -2147483645.0; f3 = -2147483645.0; f4 = -2147483645.0f; c2 = 126;

11. Što će se ispisati programskim odsječkom: int i; i = !0 = 1 && x = 80 && x 0 && m % 2 != 0) { printf("Istina je"); } return 0; }

3.

if (!(x >= 1 && x = 80 && x 9) && (x < 80 || x > 90)) {

4.

if (!(m > 0 && m % 2 != 0)) {

ili if (m = 'a' && c = '0' && c = 'a' && c1 = 'A' && c1 = '0' && c1 = 'a' && c2 = 'A' && c2 = '0' && c2 18, int 15 / 2*3 -> 21, int 15. / 2*3 -> 22.5, double 15.f / 2*3 -> 22.5, float 15.f / 2*3. -> 22.5, double 15 / 2*3. -> 21.0, double 12 / (2*3) -> 2, int 2 * 2+3 -> 7, int 2 * 5%2 -> 0, int 2 * (5%2) -> 2, int (float)15/2/3 -> 2.5, float (float)(15/2/3) -> 2.0, float (float)(15/2)/3 -> 2.333333, float (float)((15/2)/3) -> 2.0, float 3.5f * (double)4 + 3 * 5/(double)2 -> 14.0 + 7.5 -> 21.5, double

10.

c1 c2 f1 f2 f3 f4

= = = = = =

-126 126 -2147483648.000000 -2147483648.000000 -2147483645.000000 -2147483648.000000

11. Svoje rješenje provjerite obavljanjem navedenih naredbi u vlastitom C programu. 5

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 04-OstaliOperatori.pdf - do stranice: 37

Napomene: - Savjetuje se navedene zadatke riješiti ubrzo nakon predavanja - Savjetuje se ne gledati rješenja prije nego se pokuša samostalno riješiti zadatke

8. vježbe uz predavanja 1. Što će se ispisati sljedećim odsječkom programa: char c, c1; c = 'a' / 2*1.1; printf("%d\n", c); c1 = 1 + c++; printf("%d %d\n", c, c1); c1 = ++c + 12; printf("%d %c %d %c\n", c, c, c1, c1); 2. Što će se ispisati sljedećim odsječkom programa: int i1 = 5, i2, j1 = 5, j2, k1, k2; i2 = ++i1 + 3; printf("%d %d\n", i1, i2); j2 = j1++ + 3; printf("%d %d\n", j1, j2); k1 = i2++ * --j2; printf("%d %d %d\n", i2, j2, k1); i2++; ++j2; k2 = ++i2 * j2++; printf("%d %d %d\n", i2, j2, k2); 3. Što će se ispisati sljedećim programskim odsječkom: int i = 23, j = 13, k = 11, m; printf("%d\n", i || j && k); printf("%d\n", i | j & k); m = i == j && k; printf("%d %d\n", m, -!m < 0); m = i ^ (j=13); printf("%d %d\n", m, j); j = 7; m = 7; m = i & ~(j==7); printf("%d\n", m); m = ~(~k | k); printf("%d\n", m); 4. Što će se ispisati sljedećim programskim odsječkom: int i = 6; printf("%d\n", i 1) { fakt *= n; --n; } printf("%d\n", fakt); } return 0; }

7

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 05-KontrolneNaredbe.pdf - do stranice: 33

8.

#include int main (void) { int n, ostatak; scanf("%d", &n); if (n == 0) { printf("0\n"); } else { while (n > 0) { ostatak = n % 16; if (ostatak < 10) printf("%d", ostatak); else printf("%c", 'A' + ostatak - 10); n = n / 16; } } return 0; }

9.

#include int main (void) { int n, ostatak; scanf("%d", &n); if (n == 0) printf("0\n"); else while (n > 0) { ostatak = n % 8; printf("%d", ostatak); n = n / 8; } return 0; }

10.

#include int main (void) { unsigned nt broj; int kolikoPosmaknutiDesno, pomocna; scanf("%u", &broj); printf("Upisali ste broj %d\n", broj); kolikoPosmaknutiDesno = 31; while (kolikoPosmaknutiDesno >= 0) { pomocna = broj >> kolikoPosmaknutiDesno; /* sada se u varijabli pomocna, na poziciji nultog bita (najmanje znacajnog bita) nalazi znamenka koja se u varijabli broj nalazi na poziciji kolikoPosmaknutiDesno. Vrijednost tog bita, 0 ili 1, moze se dobiti tako da se obavi operacija: pomocna & 000000000000000000000000000000012 */ printf("%d", pomocna & 1); /* u sljedecem koraku posmaknuti za 30 mjesta, u sljedecem za 29 mjesta itd. */ --kolikoPosmaknutiDesno; } printf("\n"); return 0; }

8

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 05-KontrolneNaredbe.pdf - do stranice: 33

Rješenje 11. zadatka #include int main (void) { int i; float suma, brojnik; i = 1; suma = 0.f; do { brojnik = i % 2 ? 1.f : -1.f; suma += brojnik / i; ++i; } while (i 16) { printf("Upisali ste neispravan broj\n"); } else { for (i = 0; i < n; ++i) { scanf("%d", &znamenka); dekadski = dekadski*2 + znamenka; } printf("%d\n", dekadski); } return 0; }

Petlja s poznatim brojem ponavljanja je najpogodnija za ovaj slučaj jer je u trenutku kad petlja započinje poznato koliko puta se tijelo te petlje treba obaviti (uočite, to može biti i "nula puta", npr. ako u ovom slučaju korisnik za vrijednost varijable n upiše nulu).

4

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 05-KontrolneNaredbe.pdf - do stranice: 61

Rješenje 4. zadatka #include int main (void) { int n, znamenka, dekadski = 0; scanf("%d", &n); if (n < 0 || n > 16) { printf("Upisali ste neispravan broj\n"); } else { if (n > 0) { do { scanf("%d", &znamenka); dekadski = dekadski*2 + znamenka; --n; } while (n > 0); } printf("%d\n", dekadski); } return 0; } Petlja s ispitivanjem uvjeta na kraju nije pogodna za rješavanje ovog zadatka, jer je moguće da tijelo petlje neće biti potrebno obaviti niti jednom (onda kada se za vrijednost varijable n učita 0). Zato je if naredbom potrebno provjeriti treba li uopće započeti s obavljanjem petlje.

Rješenje 5. zadatka #include int main(void) { unsigned int a; int i; printf("Upisite nenegativni cijeli broj a: "); scanf ("%u", &a); /* za unsigned se kod citanja koristi %u umjesto %d */ for (i = 10; i >= 0; --i) { printf("%d", a >> 3*i & 0x7); } printf ("\n"); return 0;

}

5

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 05-KontrolneNaredbe.pdf - do stranice: 61

Rješenje 6. zadatka #include int main(void) { unsigned int a; int i, broj; printf("Upisite nenegativni cijeli broj a: "); scanf ("%u", &a); for (i = 7; i >= 0; --i) { broj = a >> 4*i & 0xF; if (broj 1) { f = f1 + f0; f0 = f1; f1 = f; } printf ("%d\n", f); } } int main(void) { fibonacci(30); return 0; }

Uočiti: zadatak se ne može riješiti pomoću polja, kao prethodni zadatak, jer se ne zna unaprijed koliko bi polje u funkciji trebalo biti veliko. Argument n se ne može pri definiciji polja koristiti kao dimenzija polja jer dimenzija polja pri definiciji mora biti cjelobrojni konstantni izraz. Dakle, nije dopušteno sljedeće: void fibonacci (int n) { int i, fbroj[n];

6

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 25

Rješenje 11. zadatka #include int kolikoInt(void) { return sizeof(int); } int main(void) { int brojBajtovaZaInt; brojBajtovaZaInt = kolikoInt(); printf("Ovaj prevodilac za tip int koristi bajtova: %d\n", brojBajtovaZaInt); return 0; }

7

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 35

Napomene: - Savjetuje se navedene zadatke riješiti ubrzo nakon predavanja - Savjetuje se ne gledati rješenja prije nego se pokuša samostalno riješiti zadatke

16. vježbe uz predavanja U svim zadacima u kojima se traži definiranje funkcije, treba napisati odgovarajući glavni program (tj. funkciju main) u kojem ćete po potrebi definirati stvarne argumente, s tipkovnice učitati njihove vrijednosti, pozvati funkciju i ispisati rezultat. 1. Napisati funkciju koja za zadani cijeli broj n vraća n2, ali tako da rezultat vraća preko adrese koju je dobila kao argument. Funkcija ne smije promijeniti stvarni argument n definiran u pozivajućem programu. Kojeg je tipa funkcija? 2. Napisati funkciju koja sadržaj neke cjelobrojne varijable n iz pozivajućeg programa mijenja u 2 n . Dakle, funkcija treba promijeniti vrijednost neke cjelobrojne varijable koja je definirana u pozivajućem programu. Kojeg je tipa funkcija? 3. Napišite funkciju tipa double koja za zadanu vrijednost tipa double vraća zadanu vrijednost (tipa double) uvećanu za 10.0. Hoćete li dobiti ispravan rezultat ako funkciju pozovete sa stvarnim argumentom tipa int? 4. Napišite funkciju koja zadanoj varijabli tipa double vrijednost uvećava za 10.0. Dakle, funkcija treba promijeniti vrijednost neke realne (double) varijable koja je definirana u pozivajućem programu. Hoćete li dobiti ispravan rezultat ako funkciju pozovete sa stvarnim argumentom koji je pokazivač na varijablu tipa int? 5. Napišite funkciju koja za dvije zadane vrijednosti tipa int u pozivajući program vraća dvije vrijednosti: prva vraćena vrijednost je veća među zadanim vrijednostima, a druga vraćena vrijednost je manja među zadanim vrijednostima. Npr. ako se funkciji zadaju vrijednosti 2 i 3*2, funkcija u pozivajući program mora vratiti vrijednosti 6 i 2. 6. Napišite funkciju koja vrijednosti u zadanim varijablama x, y i z (tipa double) poredava po veličini, od najveće prema najmanjoj. Drugim riječima, očekuje se da će funkcija zamijeniti vrijednosti u varijablama x, y i z tako da vrijednosti budu poredane od najveće prema najmanjoj. Npr. ako se funkcija pozove za varijable x=2.0, y=4.0, z=3.0, nakon izvršavanja funkcije u varijablama x, y, z se moraju nalaziti vrijednosti x=4.0, y=3.0, z=2.0. 7. Napišite funkciju koja prima pokazivače na dvije varijable tipa int, te vraća pokazivač na onu od njih koja ima veću vrijednost. Ako varijable imaju istu vrijednost, funkcija vraća pokazivač na prvu varijablu. 8. Napišite funkciju koja prima pokazivače na dvije varijable tipa int, te vraća vrijednost varijable koja ima veću vrijednost.

1

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 35

Rješenja

Rješenje 1. zadatka #include void kvad2(int n, int *rez) { *rez = n*n; } int main (void) { int n, n2; printf ("Upisite n: "); scanf("%d", &n); kvad2(n, &n2); printf("n na kvadrat (preko adrese) je: %d\n", n2); printf("Vrijednost varijable n se nije promijenila: %d\n", n); return 0; }

Funkcija kvad2 kao drugi argument dobija adresu varijable u koju će zapisati rezultat. Jedina naredba u toj funkciji upravo to i radi: na adresu kamo pokazuje pokazivač rez, zapisuje n2. Primijetite da pozivajući program za drugi stvarni argument predaje adresu varijable n2. Tip funkcije je void, jer funkcija pomoću naredbe return ne treba vratiti niti jednu vrijednost. Ipak, uočite da će uvjet iz programa (da funkcija ne smije promijeniti stvarni argument) biti narušen ukoliko se funkcija pozove na sljedeći način: kvad2(n, &n);

Rješenje 2. zadatka #include void kvad3(int *n) { *n = *n * *n; } int main (void) { int n; printf ("Upisite n: "); scanf("%d", &n); kvad3(&n); printf("n na kvadrat (promjena originalne varijable preko adrese) je: %d\n", n); return 0; }

Funkcija kvad3 dobija adresu varijable u kojoj se nalazi cijeli broj čiji kvadrat treba izračunati, ali se na tu istu adresu također zapisuje i rezultat. Varijabla n iz pozivajućeg programa će biti promijenjena!

2

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 35

Rješenje 3. zadatka #include double uvecajZa10(double x) { return x + 10.; } int main (void) { double arg, rez; printf("Upisite realni broj: "); scanf("%lf", &arg); rez = uvecajZa10(arg); printf("%f uvecan za 10.0 jest %f\n", arg, rez); return 0; }

Sada treba testirati što će se dogoditi ako se funkcija pozove s cjelobrojnim argumentom? int main (void) { int arg, rez; printf("Upisite cijeli broj: "); scanf("%d", &arg); rez = uvecajZa10(arg); printf("%d uvecan za 10.0 jest %d\n", arg, rez); return 0; }

Ako se funkcija pozove s cjelobrojnim stvarnim argumentom, dobit će se ispravan rezultat jer se pri prijenosu stvarnog u formalni argument obavlja implicitna konverzija (int→ double), a pri pridruživanju rezultata funkcije varijabli rez obavlja se implicitna konverzija (double→ int).

3

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 35

Rješenje 4. zadatka #include void uvecajZa10(double *x) { *x = *x + 10.; } int main (void) { double arg; printf("Upisite realni broj: "); scanf("%lf", &arg); uvecajZa10(&arg); printf("Uvecana varijabla jest %f\n", arg); return 0; }

Sada treba testirati što će se dogoditi ako se funkciji umjesto pokazivača na varijablu tipa double preda pokazivač na varijablu tipa int? int main (void) { int arg; printf("Upisite cijeli broj: "); scanf("%d", &arg); uvecajZa10(&arg); printf("Uvecana varijabla jest %d\n", arg); return 0; }

Prevodilac će dojaviti upozorenje, ali će program ipak uspjeti prevesti (ako se radi s gcc prevodiocem, da bi prevođenje uspjelo u ovom primjeru, treba ispustiti opciju -pedantic-errors) . Rezultat izvršavanja programa neće biti ispravan. To se moglo očekivati: funkcija je dobila adresu int varijable (pokazuje na neko područje u memoriji od 4 bajta), a "misli" da je dobila adresu double varijable (adresu koja pokazuje na područje memorije veličine 8 bajta). Prekršili smo pravilo koje smo definirali na predavanjima: pokazivač na objekte tipa x smije se koristiti isključivo za pohranu adresa objekata tipa x. Za vježbu, provjerite kakve ćete rezultate dobiti ako u funkciji, umjesto tipa double koristite tip float.

4

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 35

Rješenje 5. zadatka #include void poredaj(int a, int b, int *veci, int *manji) { if (a > b) { *veci = a; *manji = b; } else { *veci = b; *manji = a; } } int main (void) { int veci, manji; poredaj(2, 3*2, &veci, &manji); printf("veci i manji su: %d %d\n", veci, manji); return 0; }

Rješenje 6. zadatka #include void poredaj(double *x, double *y, double *z) { double pom; if (*x < *y) { pom = *x; *x = *y; *y = pom; } if (*x < *z) { pom = *x; *x = *z; *z = pom; } if (*y < *z) { pom = *y; *y = *z; *z = pom; } } int main (void) { double a = 1.0, b = 2.0, c = 3.0; poredaj(&a, &b, &c); printf("poredani su: %f %f %f\n", a, b, c); return 0; }

5

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 35

Rješenje 7. zadatka #include int *vratiAdresuVeceg(int *x, int *y) { if (*x >= *y) return x; else return y; } int main (void) { int a = 5, b = 2; int *veci; veci = vratiAdresuVeceg(&a, &b); printf("veci od zadana dva broja je: %d\n", *veci); return 0; }

Rješenje 8. zadatka #include int vratiVrijednostVeceg(int *x, int *y) { if (*x > *y) return *x; else return *y; } int main (void) { int a = 5, b = 2; int veci; veci = vratiVrijednostVeceg(&a, &b); printf("veci od zadana dva broja je: %d\n", veci); return 0; }

6

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

Napomene: - Savjetuje se navedene zadatke riješiti ubrzo nakon predavanja - Savjetuje se ne gledati rješenja prije nego se pokuša samostalno riješiti zadatke

17. vježbe uz predavanja 1. Napisati prototip (deklaraciju) za svaku funkciju koja se nalazi u rješenjima prethodnih vježbi. 2. U modulu mat2.c napisati funkcije čiji su prototipovi navedeni u nastavku int binCoeff(int m, int n); int factorial(int n); int iabsolute(int n); float fabsolute(float x);

/* /* /* /*

izracunava izracunava izracunava izracunava

"m povrh n" */ n! */ apsolutnu vrijednost */ apsolutnu vrijednost */

Prototipove navedenih funkcija smjestiti u datoteku s prototipovima mat2.h. Funkciju main ("glavni program") smjestiti u modul glavni.c U glavnom programu treba izračunati i na zaslon ispisati rezultate za: factorial(0) factorial(25) factorial(26) binCoeff(13, 3) binCoeff(4, 4) iabsolute(-5) iabsolute(0) iabsolute(-5.7f) fabsolute(-5) fabsolute(-5.7f) Testirati prevođenje na dva načina: • • •

tako da se oba modula prevedu i povežu samo jednim pozivom prevodioca tako da se zasebno prevede svaki modul, a zatim se dobiveni objektni kôd poveže u izvršni kôd koje datoteke su stvorene za vrijeme prevođenja na prvi, odnosno drugi način?

3. Što će se ispisati tijekom izvođenja sljedećeg programa: #include void fun(void) { int x = 5; printf("%d\n", x); x++; } int main (void) { fun(); fun(); return 0; }

1

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

4. Što će se ispisati tijekom izvođenja sljedećeg programa: #include void fun(void) { static int x = 5; printf("%d\n", x); x++; } int main (void) { fun(); fun(); return 0; }

5. Što će se ispisati tijekom izvođenja sljedećeg programa: #include void fun(void) { static int x = 5; int y = 5; printf("%d %d\n", ++x, --y); } int main (void) { fun(); fun(); fun(); return 0; }

6. Što će se ispisati tijekom izvođenja sljedećeg programa: #include int main (void) { static int i = 5; int prviPut = 1; labela: { static int i = 10; int j = 15; printf("%d %d\n", i, j); i++; j++; } i++; printf("%d\n", i); if (prviPut) { prviPut = 0; goto labela; } return 0; }

2

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

7. Što će se ispisati tijekom izvođenja sljedećeg programa: #include static int x = 25; void fun1(void) { static int x = 5; printf("%d\n", ++x); } void fun2(void) { int x = 10; printf("%d\n", ++x); } void fun3(void) { printf("%d\n", ++x); } int main (void) { x++; { static int x = 15; { int x = 20; printf("%d\n", x++); } printf("%d\n", x++); } printf("%d\n", x++); fun1(); fun2(); fun3(); fun1(); fun2(); fun3(); return 0; }

U zadacima u kojima se traži definiranje funkcije, treba napisati prototipove funkcija, te odgovarajući glavni program (tj. funkciju main) u kojem ćete po potrebi definirati stvarne argumente, pozvati funkciju i ispisati rezultat.

3

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

8. Napisati funkciju zbroji tipa int koja vraća zbroj dvaju zadanih cijelih brojeva i funkciju mnozi tipa int koja vraća umnožak dvaju zadanih cijelih brojeva. Svaka od funkcija, osim što izračunava rezultat i vraća ga u pozivajući program, na zaslon ispisuju koliko je puta bila pozvana. Npr. ako se u glavnom programu obave naredbe: printf("2*2=%d\n", printf("2+3=%d\n", printf("4+2=%d\n", printf("2*5=%d\n", printf("2*3=%d\n",

mnozi(2,2)); zbroji(2,3)); zbroji(4,2)); mnozi(2,5)); mnozi(2,3));

na zaslonu se treba ispisati: Funkcija 2*2=4 Funkcija 2+3=5 Funkcija 4+2=6 Funkcija 2*5=10 Funkcija 2*3=6

mnozi do sada je pozvana 1 puta zbroji do sada je pozvana 1 puta zbroji do sada je pozvana 2 puta mnozi do sada je pozvana 2 puta mnozi do sada je pozvana 3 puta

← ispisano u funkciji mnozi ← ispisano u glavnom programu ← ispisano u funkciji zbroji ← ispisano u glavnom programu ← ispisano u funkciji zbroji ← ispisano u glavnom programu ← ispisano u funkciji mnozi ← ispisano u glavnom programu ← ispisano u funkciji mnozi ← ispisano u glavnom programu

4

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

9. Slično kao prethodni zadatak, uz dodatak: svaka od funkcija mora ispisati ne samo koliko je puta bila pozvana ona sama, nego i koliko puta je bila pozvana bilo koja od funkcija zbroji i mnozi. Npr. ako se u glavnom programu obave naredbe: printf("2*2=%d\n", printf("2+3=%d\n", printf("4+2=%d\n", printf("2*5=%d\n", printf("2*3=%d\n",

mnozi(2,2)); zbroji(2,3)); zbroji(4,2)); mnozi(2,5)); mnozi(2,3));

na zaslonu se treba ispisati: Funkcija Funkcije 2*2=4 Funkcija Funkcije 2+3=5 Funkcija Funkcije 4+2=6 Funkcija Funkcije 2*5=10 Funkcija Funkcije 2*3=6

mnozi do sada je pozvana 1 puta zbroji i mnozi do sada su pozvane 1 puta zbroji do sada je pozvana 1 puta zbroji i mnozi do sada su pozvane 2 puta zbroji do sada je pozvana 2 puta zbroji i mnozi do sada su pozvane 3 puta mnozi do sada je pozvana 2 puta zbroji i mnozi do sada su pozvane 4 puta mnozi do sada je pozvana 3 puta zbroji i mnozi do sada su pozvane 5 puta

← ispisano u funkciji mnozi ← ispisano u funkciji mnozi ← ispisano u glavnom programu ← ispisano u funkciji zbroji ← ispisano u funkciji zbroji ← ispisano u glavnom programu ← ispisano u funkciji zbroji ← ispisano u funkciji zbroji ← ispisano u glavnom programu ← ispisano u funkciji mnozi ← ispisano u funkciji mnozi ← ispisano u glavnom programu ← ispisano u funkciji mnozi ← ispisano u funkciji mnozi ← ispisano u glavnom programu

5

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

10. Što će se ispisati tijekom izvođenja sljedećeg programa:

datoteka s prototipovima proto.h

modul modulA.c

void void void void

#include #include "proto.h" extern int x = 20;

fun1(void); fun2(void); fun3(void); fun4(void);

modul glavni.c #include #include "proto.h" extern int x; int main(void) { int x = 30; x += 2; printf("%d\n", x); fun1(); fun2(); fun3(); fun4(); fun3(); return 0; } void fun1(void) { x += 3; printf("%d\n", x); }

void fun2(void) { x += 4; printf("%d\n", x); }

modul modulB.c #include #include "proto.h" void fun3(void) { static int x = 5; x += 5; printf("%d\n", x); } void fun4(void) { extern int x; x += 6; printf("%d\n", x); }

Provjeriti rješenje izvođenjem programa na vlastitom računalu i pri tome testirati prevođenje na dva načina: • •

tako da se svi moduli prevedu i povežu samo jednim pozivom prevodioca tako da se zasebno prevede svaki modul, a zatim se dobiveni objektni kôd poveže u izvršni kôd

6

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

Rješenja

Rješenje 2. zadatka modul glavni.c #include #include "mat2.h" int main(void) { printf("%d\n", factorial(0)); printf("%d\n", factorial(25)); printf("%d\n", factorial(26)); /* zasto ovdje rezultat nece biti dobar?*/ printf("%d\n", binCoeff(13, 3)); printf("%d\n", binCoeff(4, 4)); printf("%d\n", iabsolute(-5)); printf("%d\n", iabsolute(0)); printf("%d\n", iabsolute(-5.7f)); printf("%3.1f\n", fabsolute(-5)); printf("%3.1f\n", fabsolute(-5.7f)); return 0; }

modul mat2.c #include "mat2.h" int binCoeff(int m, int n) { return factorial(m) / ( factorial(n) * factorial(m - n) ); } int factorial(int n) { int i, f = 1; for (i = 2; i = 0 ? n : -n; } float fabsolute(float x) { return x >= 0.0f ? x : -x; }

datoteka s prototipovima funkcija mat2.h int binCoeff(int m, int n); int factorial(int n); int iabsolute(int n); float fabsolute(float x);

7

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

Rješenje 3. zadatka Pri svakom pozivu funkcije varijabla x se ponovno inicijalizira. Ispisat će se: 5 5

Rješenje 4. zadatka Varijabla x se inicijalizira samo jednom, na početku izvođenja programa, a njezina vrijednost ostaje sačuvana do kraja izvođenja programa (ne gubi se završetkom funkcije). Ispisat će se: 5 6

Rješenje 5. zadatka Potrebno je uočiti koje su varijable definirane u programu, te na temelju smještajnog razreda kojem pripadaju odrediti njihovo područje važenja i trajnost. Varijabla x je statička vrijabla. To znači da se njezina trajnost proteže od početka do završetka programa. Varijabla se inicijalizira samo jednom, na početku izvođenja programa (čak i prije nego se prvi puta pozove funkcija), te njena vrijednost ostaje sačuvana do završetka programa. Varijabla x je definirana unutar funkcije, stoga se njezino područje važenja (tj. "područje programa u kojem je vidljiva") proteže od mjesta u funkciji na kojem je definirana do kraja funkcije. Varijabla y je automatska varijabla. Varijabla se inicijalizira svaki puta kad se pozove funkcija, a njezina vrijednost se gubi u trenutku završetka funkcije. x-trajnost x-važenje

y-trajnost y-važenje

void fun(void) { static int x = 5; int y = 5; printf("%d %d\n", ++x, --y); } int main (void) { fun(); fun(); fun(); return 0; }

8

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

Rješenje 6. zadatka Ovdje je važno uočiti da postoje dvije varijable naziva i. Na slici koja prikazuje područja važenja i trajnosti, prva varijabla i (definirana na početku glavnog programa) označena je oznakom i1, a druga oznakom i2. i1-trajnost

i2-trajnost

i1-važenje #include int main (void) { static int i = 5; int prviPut = 1; labela: { static int i = 10; int j = 15; printf("%d %d\n", i, j); i++; j++; } i++; printf("%d\n", i); if (prviPut) { prviPut = 0; goto labela; } return 0;

i2-važenje

}

Rješenje 7. zadatka Ovdje je važno uočiti da postoji 5 različitih varijabli naziva x. Kad se odredi trajnost i područje važenja svake od tih varijabli, zadatak je lako riješiti.

9

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

Rješenje 8. zadatka #include int zbroji(int x, int y); int mnozi(int x, int y); int main (void) { printf("2*2=%d\n", printf("2+3=%d\n", printf("4+2=%d\n", printf("2*5=%d\n", printf("2*3=%d\n", return 0; }

mnozi(2,2)); zbroji(2,3)); zbroji(4,2)); mnozi(2,5)); mnozi(2,3));

int zbroji(int x, int y) { static int brojPoziva = 0; brojPoziva++; printf("Funkcija zbroji do sada je pozvana %d puta\n", brojPoziva); return x+y; } int mnozi(int x, int y) { static int brojPoziva = 0; brojPoziva++; printf("Funkcija mnozi do sada je pozvana %d puta\n", brojPoziva); return x*y; }

10

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 64

Rješenje 9. zadatka #include int zbroji(int x, int y); int mnozi(int x, int y); int ukupniBrojPoziva = 0; int main (void) { printf("2*2=%d\n", printf("2+3=%d\n", printf("4+2=%d\n", printf("2*5=%d\n", printf("2*3=%d\n",

mnozi(2,2)); zbroji(2,3)); zbroji(4,2)); mnozi(2,5)); mnozi(2,3));

return 0; } int zbroji(int x, int y) { static int brojPoziva = 0; brojPoziva++; ukupniBrojPoziva++; printf("Funkcija zbroji do sada je pozvana %d puta\n", brojPoziva); printf("Funkcije zbroji i mnozi do sada su pozvane %d puta\n", ukupniBrojPoziva); return x+y; } int mnozi(int x, int y) { static int brojPoziva = 0; brojPoziva++; ukupniBrojPoziva++; printf("Funkcija mnozi do sada je pozvana %d puta\n", brojPoziva); printf("Funkcije zbroji i mnozi do sada su pozvane %d puta\n", ukupniBrojPoziva); return x*y; }

Rješenje 10. zadatka

Ovdje je važno uočiti postojanje definicija triju različitih varijabli x. • • •

na početku modula modulA.c definirana je eksterna varijabla x na početku funkcije main definirana je automatska varijabla x na početku funkcije fun3 definirana je statička varijabla x

Nakon što se odredi trajnost i područje važenja svake pojedine varijable, zadatak je lako riješiti.

11

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 78

Napomene: - Savjetuje se navedene zadatke riješiti ubrzo nakon predavanja - Savjetuje se ne gledati rješenja prije nego se pokuša samostalno riješiti zadatke

18. vježbe uz predavanja U svim zadacima u kojima se traži definiranje funkcije, treba napisati odgovarajući glavni program (tj. funkciju main) u kojem ćete po potrebi definirati stvarne argumente, pozvati funkciju i ispisati rezultat. 1. Napišite funkciju toApsDim koja vrijednosti elemenata cjelobrojnog jednodimenzijskog polja mijenja u njihove apsolutne vrijednosti. Funkciju, glavni program i prototipove smjestite u tri zasebne datoteke. Testirati prevođenje na dva načina: • •

tako da se oba modula prevedu i povežu samo jednim pozivom prevodioca tako da se zasebno prevede svaki modul, a zatim se dobiveni objektni kôd poveže u izvršni kôd

2. Napišite funkciju koja u zadanom jednodimenzijskom realnom polju prebroji koliko članova je veće od 0.0, koliko članova je manje od 0.0 i koliko članova je jednako 0.0. Dobivene vrijednosti funkcija mora vratiti u pozivajući program. Funkciju, glavni program i prototipove smjestite u tri zasebne datoteke. Testirati prevođenje na dva načina: • •

tako da se oba modula prevedu i povežu samo jednim pozivom prevodioca tako da se zasebno prevede svaki modul, a zatim se dobiveni objektni kôd poveže u izvršni kôd

3. Što će se ispisati sljedećim programom: #include int main (void) { int a = 2, x = 10; int *p = &a; x += *p * 3; printf ("%d %d\n", *p, x); return 0; }

4. Što će se ispisati sljedećim programom: #include void f (int *p) { printf ("%d %d\n", *p, *p+1); } int main (void) { int polje[6] = {1, 2, 3, 4, 5, 6}; int *pp; pp = &polje[0]; f(pp++); f(pp); f(++pp); return 0; }

1

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 78

5. Što će se ispisati sljedećim programom: #include void f (int *p) { static int i = 2; printf ("%d\n", *(p + ++i)); } int main (void) { int polje[8] = {1, 2, 3, 4, 5, 6, 7, 8}; f(&polje[0]); f(&polje[0]); f(&polje[0]); f(&polje[1]); return 0; }

6. Što će se ispisati sljedećim programom: #include void f (int *p) { int i = 3; printf ("%d\n", *(p + --i)); } int main (void) { int polje[6] = {1, 2, 3, 4, 5, 6}; f(&polje[0]); f(&polje[1]); f(&polje[2]); return 0; }

2

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 78

Rješenja Rješenje 1. zadatka glavni.c #include #include "toapsdim.h" #define MAXDIM 100 int main (void) { int m; int polje[MAXDIM]; int i; printf ("Upisite m manji ili jednak %d: ", MAXDIM); scanf("%d", &m); printf ("Upisite elemente polja:\n"); for (i = 0; i < m; ++i) scanf("%d", &polje[i]); printf("\nSlijedi ispis ucitanog niza\n\n"); for (i = 0; i < m; ++i) printf("%d ", polje[i]); toApsDim(&polje[0], m); printf("\nSlijedi ispis izmijenjenog polja\n\n"); for (i = 0; i < m; ++i) printf("%d ", polje[i]); return 0; }

toapsdim.c #include "toapsdim.h" void toApsDim(int *polje, int n) { /* ili (int polje[], int n) */ int i; for (i = 0; i < n; ++i) if (*(polje + i) < 0) *(polje + i) = - *(polje + i); }

toapsdim.h void toApsDim(int *polje, int n);

3

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 08-Funkcije.pdf - do stranice: 78

Rješenje 2. zadatka

funkcije.h void prebroji(float *polje, int n, int *vecihOdNula, int *manjihOdNula, int *jednakihNula);

funkcije.c #include "funkcije.h" void prebroji(float *polje, int n, int *vecihOdNula, int *manjihOdNula, int *jednakihNula) { int i; *vecihOdNula = *manjihOdNula = *jednakihNula = 0; for (i = 0; i < n; ++i) if (*(polje + i) < 0.0) (*manjihOdNula)++; else if (*(polje + i) == 0.0) (*jednakihNula)++; else (*vecihOdNula)++; return; }

test.c #include #include "funkcije.h" #define MAXDIM 100 int main (void) { int m; float polje[MAXDIM]; int i; int vecih, manjih, jednakih; printf ("Upisite m manji ili jednak %d: ", MAXDIM); scanf("%d", &m); printf ("Upisite elemente polja:\n"); for (i = 0; i < m; ++i) scanf("%f", &polje[i]); printf("\n\nSlijedi ispis ucitanog niza\n"); for (i = 0; i < m; ++i) printf("%f\n", polje[i]); prebroji(&polje[0], m, &vecih, &manjih, &jednakih); printf("\nn>0 ima: %d n "); scanf("%d %d %d", &m, &n, &brojHitaca); gadjaj(&polje[0][0], m, n, MAXS, brojHitaca); ispisiPolje(&polje[0][0], m, n, MAXS); printf("\n"); gadjaj(&polje[0][0], m, n, MAXS, brojHitaca); ispisiPolje(&polje[0][0], m, n, MAXS); return 0; }

4

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 09-UgradjeneFunkcije.pdf - do stranice: 38

Funkcija će pri svakom pozivu ponovo inicijalizirati generator ako se blok naredbi if (! generatorInicijaliziran) { srand ((unsigned) time(NULL)); generatorInicijaliziran = 1; }

zamijeni sa srand ((unsigned) time(NULL));

Druga inicijalizacija generatora će se tada dogoditi vrlo vjerojatno s istim početnim uvjetima-seed (jer će se oba poziva funkcije vrlo vjerojatno obaviti unutar iste sekunde), stoga će i nizovi generiranih pseudoslučajnih brojeva biti jednaki.

Rješenje 5. zadatka #include #define MAX 20 void obrniNiz(char *niz); int main (void) { char niz[MAX+1]; printf ("Upisite niz znakova (ne dulji od %d znakova):", MAX); gets(niz); printf("%s\n", niz); obrniNiz(niz); printf("%s\n", niz); /* VAZNO PITANJE: */ /* zasto funkciju nije moguce pozvati ovako: obrniNiz("Prosinac"); */ return 0; } void obrniNiz(char *niz) { int i; char pom; int duljina = 0; while (*(niz + duljina)) duljina++; for (i = 0; i < duljina/2; ++i) { pom = niz[i]; niz[i] = niz[duljina-1-i]; niz[duljina-1-i] = pom; } }

5

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 09-UgradjeneFunkcije.pdf - do stranice: 38

Rješenje 6. zadatka #include #include void umetniZnak(char *niz, char c); int main (void) { char niz[7+1+1]; strcpy(niz, "Studeni"); umetniZnak(niz, 'A'); printf("%s\n", niz); /* VAZNO PITANJE: */ /* zasto funkciju nije moguce pozvati ovako: umetniZnak("Studeni", 'A'); */ return 0; } void umetniZnak(char *niz, char c) { int i; int duljina = 0; while (*(niz + duljina)) ++duljina; niz[duljina+1] = '\0'; for (i = duljina; i > 0; --i) niz[i] = niz[i-1]; niz[0] = c; }

Rješenje 7. zadatka #include #include void umetniZnakove(char *niz, char c); int main (void) { char niz[7+1+7]; strcpy(niz, "Studeni"); umetniZnakove(niz, 'X'); printf("%s\n", niz); /* VAZNO PITANJE: */ /* zasto funkciju nije moguce pozvati ovako: umetniZnakove("Studeni", 'A'); */ return 0; } void umetniZnakove(char *niz, char c) { int i; int duljina = 0; while (*(niz + duljina)) ++duljina; *(niz + 2*duljina) = '\0'; for (i = duljina - 1; i >= 0; --i) { *(niz + 2*i + 1) = *(niz + i); *(niz + 2*i) = c; } }

6

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 09-UgradjeneFunkcije.pdf - do stranice: 38

Rješenje 8. zadatka #include char *myStrcpy(char *cilj, char *izvor); int main (void) { char ciljniNiz[20+1]; char *izvorniNiz = "Niz znakova"; myStrcpy(ciljniNiz, izvorniNiz); printf("%s\n", ciljniNiz); myStrcpy(ciljniNiz, "Ana"); printf("%s\n", ciljniNiz); /* moze i ovako: objasnite zasto! */ printf("%s\n", myStrcpy(ciljniNiz, "Iva")); /* VAZNO PITANJE: */ /* zasto funkciju nije moguce pozvati ovako: myStrcpy(" return 0; }

", "Ana"); */

char *myStrcpy(char *cilj, char *izvor) { int i = 0; while (*(izvor+i)) { *(cilj+i) = *(izvor+i); ++i; } *(cilj+i) = '\0'; return cilj; }

Rješenje 9. zadatka #include int myStrlen(char *niz); int main (void) { char niz[20+1] = "Ovo je niz"; char *konst = "Ovo je konstantni niz"; printf("%d\n", myStrlen(niz)); printf("%d\n", myStrlen(konst)); printf("%d\n", myStrlen("Ovo je jos jedan konstatni niz")); return 0; } int myStrlen(char niz[]) { int i = 0; while (niz[i]) ++i; return i; }

7

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 09-UgradjeneFunkcije.pdf - do stranice: 38

Rješenje 10. zadatka #include char *myStrcat(char *cilj, char *izvor); int main (void) { char ciljniNiz[30+1] = "Prvi"; char ciljniNiz2[30+1] = "Pocetak"; char *izvorniNiz = "Drugi"; myStrcat(ciljniNiz, izvorniNiz); printf("%s\n", ciljniNiz); myStrcat(ciljniNiz, "Treci"); printf("%s\n", ciljniNiz); /* moze i ovako: objasnite zasto! */ printf("%s\n", myStrcat(ciljniNiz, "Cetvrti")); /* VAZNO PITANJE: */ /* zasto funkciju nije moguce pozvati ovako: myStrcat("ABC", "EFG") */ /* proucite sljedecu naredbu */ myStrcat(myStrcat(ciljniNiz2, "Sredina"), "Kraj"); printf("%s\n", ciljniNiz2); return 0; } char *myStrcat(char cilj[], char *izvor) { /* takodjer moze char *cilj */ int i = 0, j = 0; while (cilj[i]) ++i; while (izvor[j]) cilj[i++] = izvor[j++]; cilj[i] = '\0'; return cilj; }

Rješenje 11. zadatka #include #include int brojPodnizova(char *s1, char *s2) { char *pocetakPotrage = s1; int brojac = 0; while ((pocetakPotrage = strstr(pocetakPotrage, s2)) != NULL) { ++brojac; ++pocetakPotrage; } return brojac; } int main (void) { char s1[40+1]; char s2[40+1]; printf("Upisite nizove s1 i s2\n"); gets(s1); gets(s2); printf("Niz %s se unutar niza %s pojavljuje %d puta\n", s2, s1, brojPodnizova(s1, s2)); return 0; }

8

Programiranje i programsko inženjerstvo, ZPR-FER-UNIZG Vježbe uz predavanja 09-UgradjeneFunkcije.pdf - do stranice: 38

Rješenje 13. zadatka #include #define MYTOUPPER(c) ((c) >= 'a' && (c) 15 ); printf("Tablica mnozenja %dx%d\n", n, n); printf(" "); for (i = 1; i New Item. Odaberite C++ File (.cpp). Novoj datoteci dodijelite neko ime (npr. mojPrviProgram). Kliknite Add. Otvoriti će se upravo kreirana tekstualna datoteka.

12

7. U datoteku upišite slijedeći programski kod: #include void main() { printf("Hello World!\n"); getchar(); }

/* program čeka unos znaka sa tipkovnice */

Na kraju programa nalazi se poziv funkcije getchar(). Ta funkcija čeka na unos znaka sa tipkovnice. Da se u programu ne nalazi ova linija koda, prozor sa tekstom Hello World! bi se zatvorio čim bi se program izvršio (gotovo trenutačno), te ne biste uspjeli vidjeti rezultate programa. 8. Kliknite Debug --> Build Solution, pa zatim Debug --> Start Debugging. Alternativno možete pritisnuti F7 (Build Solution) i F5 (Start Debugging). 9. Vaš program će se izvršiti. Otvoriti će se slijedeći prozor:

10. Pritisnite Enter da bi zatvorili prozor.

13

VJEŽBA 2 Komentari u C-u #include void main() { printf("Hello World!\n"); }

/* ispisuje "Hello World!" na ekran */

Prilikom prevođenja programa, prevoditelj zanemaruje sve što se nalazi unutar znakova '/*' i '*/'. Oni označavaju početak i kraj komentara. /* ovo je komentar */ /* i ovo je komentar */ Ovakva vrsta komentara se danas sve rijeđe koristi. Popularnost im je pala nakon što su u programiranje uvedeni jednoredni komentari "uvezeni" iz programskog jezika C++. // komentar Gornji komentar je jednoredi komentar. Prednost ovakvih komentara je što programer ne mora paziti na završetak komentara, a nedostatak je taj što su ograničeni na jedan redak. Komentari su posebno važni kod velikih programa (sa više tisuća linija koda), jer programski kod postaje pregledniji i razumljiviji. Escape nizovi Kombinacije znakova koje započinju sa znakom '\' (engl. backslash), te iza kojih slijedi neki drugi znak, slovo, ili niz brojeva (ako se koristi oktalni ili heksadecimalni zapis), nazivaju se escape nizovi. Njihova zadaća jest da promijene značenje određenih znakova ili simbola (Tablica 2.1). Na primjer, znak 'n' se tretira na jedan način kada predstavlja ime varijable, a na drugi način kada se ispred njega nalazi znak '\'. Escape nizovi koji se najčešće koriste u C-u: Tablica 2.1. Escape nizovi Znak

Escape niz

ASCII

zvono (upozorenje)

\a

007 14

(engl. bell code, audible alert) backspace

\b

008

vodoravni tabulator

\t

009

okomiti tabulator

\v

011

novi red ili LF (engl. Line Feed)

\n

010

nova stranica

\f

012

CR (engl. Carriage Return)

\r

013

navodnik

\"

034

jednostruki navodnik (apostrof)

\'

039

upitnik

\?

063

backslash

\\

092

null znak

\0

000

Znakovi pretvorbe #include int main() { int a; a = 10; printf("%d\n", a); return 0; }

// deklariramo cjelobrojnu varijablu a // varijabli a pridružujemo vrijednost 10 // na ekran ispisujemo vrijednost varijable a // funkcija vraća nulu

U gornjem se primjeru koristi znak %d. Kada se ispred nekih određenih slova (Tablica 2.2) nalazi znak '%', onda slovo predstavlja znak pretvorbe (engl. conversion character). On označava tip ili format znaka koji se ispisuje na ekran ili učitava sa tipkovnice (npr. int, float...). Tablica 2.2. Znakovi pretvorbe Znak pretvorbe

Značenje

c

podatak je znak

d

podatak je decimalna cjelobrojna vrijednost s predznakom

e

podatak je realna vrijednost u eksponencijalnoj formi

f

podatak je realna vrijednost bez eksponencijalne forme

g

podatak je realna vrijednost podatak se zapisuje u najkraćoj mogućoj formi (ili f ili e)

i

podatak je decimalna cjelobrojna vrijednost s predznakom

15

podatak može biti zapisan u dekadskom, oktalnom ili heksadecimalnom brojevnom sustavu o

podatak je oktalna vrijednost, bez vodeće nule

s

podatak je niz znakova (engl. string) null znak ('\0') se automatski dodaje na kraj stringa

u

podatak je decimalna vrijednost bez predznaka

x

podatak je heksadecimalna vrijednost, bez vodećeg 0x

[. . .]

podatak je niz znakova koji može sadržavati razmake

Funkcija printf() Funkcija printf() se koristi za ispis znakova na ekran računala. Primjer: #include void main() { int a = 5; float b = 5.1; // na ekran ispisujemo vrijednost cjelobrojne varijable a printf("Vrijednost varijable a = %d\n", a); // na ekran ispisujemo vrijednost realne varijable b printf("Vrijednost varijable b = %f\n", b); // program čeka na unos znaka sa tipkovnice getchar(); } Funkcija printf() dozvoljava korištenje ograničenja na minimalnu duljinu niza koji se upisuje (npr. niz brojeva). Ako je niz veći od minimalne duljine, onda ograničenje nema nikakvog utjecaja na ispis niza. U programerskim krugovima ta je situacija poznata pod imenom "override". Funkcija printf() također dozvoljava specifikaciju točnosti nekog broja (npr. zaokruživanje broja na određeni broj decimala iza decimalne točke). Primjer: #include void main() { 16

int a = 12345; float b = 345.678; printf("%3d\n", a); printf("%10d\n\n", a); printf("%6g\n", b); printf("%13g\n\n", b); printf("%13e\n", b); printf("%16e\n\n", b); printf("%.4f\n", b); printf("%.30e\n", b); }

getchar();

ISPIS PROGRAMA:

Funkcija scanf() Funkcija scanf() se koristi za učitavanje znakova sa tipkovnice. Primjer: #include void main() { int a; float b; // sa tipkovnice se učitava cijeli broj i sprema se u varijablu a scanf("%d", &a); // sa tipkovnice se učitava realni broj i sprema se u varijablu b 17

scanf("%f", &b); }

getchar();

Kada se ispred imena varijable nalazi znak &, onda &ime_varijable predstavlja adresu te varijable. Da bi funkcija scanf() mogla nekoj varijabli promijeniti vrijednost, najprije mora znati na kojoj se memorijskoj adresi ta varijabla nalazi. Kao i funkcija printf(), funkcija scanf() omogućava postavljanje ograničenja na duljinu niza koji se učitava sa tipkovnice. Primjer: #include void main() { int a, b, c; scanf("%3d %3d %3d", &a, &b, &c); printf("a = %d b = %d c = %d\n", a, b, c); } Unos: 1 2 3 Ispis: a = 1 b = 2 c = 3 Unos: 123 456 789 Ispis: a = 123 b = 456 c = 789 Unos: 123456789 Ispis: a = 123 b = 456 c = 789 Unos: 1234 5678 9 Ispis: a = 123 b = 4 c = 567 Napomena: preostala dva znaka će biti ignorirana, osim ako ih ne pokupi neka druga scanf() funkcija.

18

ZADACI Zadatak 2.1. Napišite program koji ispisuje zauzeće memorije u bajtovima (engl. byte) za sve standardne tipove programskog jezika C (char, int, short, long, float, double, unsigned short, unsigned int, unsigned long). Koristite funkciju sizeof(). Rješenje: #include void main () { printf("Sizeof(char) = %d", sizeof(char)); printf("\nSizeof(int) = %d", sizeof(int)); printf("\nSizeof(short) = %d", sizeof(short)); printf("\nSizeof(long) = %d", sizeof(long)); printf("\nSizeof(float) = %d", sizeof(float)); printf("\nSizeof(double) = %d", sizeof(double)); printf("\nSizeof(unsigned short) = %d", sizeof(unsigned short)); printf("\nSizeof(unsigned int) = %d", sizeof(unsigned int)); printf("\nSizeof(unsigned long) = %d\n", sizeof(unsigned long)); getchar(); } ISPIS PROGRAMA:

Zadatak 2.2. Napišite program u kojem deklarirate integer i char varijablu. Objema pridijelite vrijednost znakovne konstante (primjerice 'c'). Ispišite vrijednosti varijabli i veličinu memorije koju zauzimaju. Kako komentirate rezultate? Rješenje: #include void main() 19

{

int i = 'c'; char n = 'c' ; printf("sizeof(int) = %d\n", sizeof(int)); printf("sizeof(char) = %d\n", sizeof(char)); printf("vrijednost (i) = %d\n", i); printf("vrijednost (n) = %d\n", n); printf("vrijednost (i) = %c\n", i); printf("vrijednost (n) = %c\n", n);

}

getchar();

ISPIS PROGRAMA:

Zadatak 2.3. Napišite program koji ispisuje znakove 'z' i 'Z', s tim da su za prvi znak rezervirana 3 mjesta za ispis na ekran, a za drugi 5 mjesta. Rješenje: #include void main() { printf("Slova:\n%3c\n%5c\n", 'z', 'Z'); getchar(); } Zadatak 2.4. Primjer cjelobrojnog i realnog dijeljenja. Kako komentirate rezultate programa? #include void main() { int a = 5; int b = 2; 20

int d = 5/2; float c = a/b;

}

// cjelobrojno dijeljenje – rezultat je 2 // Iako je c float, vrši se cjelobrojno dijeljenje jer su i a i b cijeli brojevi

printf("c = %f\n",c); printf("Uzrok problema : 5/2 = %f\n", 5/2); printf("Popravljeno : 5.0/2.0 = %f\n", 5.0/2.0); printf("Moze i : 5/2.0 = %f i 5.0/2 = %f \n", 5/2.0, 5.0/2); printf("Za varijable moramo uvesti cast-ing : %f\n", (float)a/(float)b); getchar();

ISPIS PROGRAMA:

Zadatak 2.5. Napišite program za unos cijelog broja sa tipkovnice. Rješenje: #include void main() { int x; printf("Unesite cijeli broj: "); scanf("%d", &x); printf("Unijeli ste broj: %d\n", x);

}

getchar(); getchar();

Zadatak 2.6. Napišite program za unos cijelog broja sa tipkovnice. Neka program ispiše kvadrat tog broja. Rješenje: #include 21

void main() { int a, b; printf("Unesite varijablu a.\n"); scanf("%d", &a);

// sa tipkovnice se učitava varijabla a

b = a * a;

// u varijablu b ide vrijednost kvadrata broja a

printf("Kvadrat broja a je %d.\n", b);

}

getchar(); getchar();

22

DOMAĆI RAD Zadatak 2.7. Napišite program za unos dva realna broja. Neka program ispiše rezultat njihovog zbrajanja, oduzimanja, množenja i dijeljenja. Zadatak 2.8. Napišite program za unos realnog broja. Neka program ispiše kvadrat i korijen tog broja. Napomena: funkcija za računanje korijena je sqrt() (engl. square root) i definirana je u biblioteci . Zadatak 2.9. Napišite program koji za proizvoljni radijus računa opseg kruga.

23

DODATAK ASCII tablica Tablica 2.3. ASCII tablica

24

VJEŽBA 3 Naredbe kontrole toka Naredbe kontrole toka svoje djelovanje zasnivaju na logičkim izrazima. Najčešće se koriste u svrhu grananja programa, tj. odabira jedne od nekoliko mogućih radnji. Jedna od najčešće korištenih naredbi za kontrolu toka je if-else konstrukcija. If-else konstrukcija općenito ima slijedeću sintaksu: if (logički izraz) naredba1 if (logički izraz) naredba 1 else naredba2

Slika 3.1. Grafički prikaz grananja Primjer (if-else konstrukcija): Programsko rješenje za grananje prikazano na slici 3.1: #include void main() { int a = 7; int b = 3; if (a > b) printf("Vrijednost varijable a = %d.\n", a); else printf("Vrijednost varijable b = %d.\n", b); getchar(); 25

} ISPIS PROGRAMA: Vrijednost varijable a = 7. Umjesto if-else konstrukcije ponekad se može koristiti uvjetni operator. Uvjetni operator je ternarni operator, jer ima tri elementa. Opći oblik: logički_izraz ? izraz_1 : izraz_2; Program bi u ovom slučaju ispitao vrijednost logičkog izraza, te ako je ta vrijednost jednaka jedinici izvršio bi se izraz 1, a ako je jednaka nuli izvršio bi se izraz 2. Primjer (uvjetni operator): Program provjerava da li je varijabla a veća od nule. Ako jest, postavlja varijablu b na jedinicu, a ako nije, postavlja varijablu b na nulu. Zatim ispisuje vrijednost varijable b. #include void main() { int a = 7; int b; (a > 0) ? b = 1 : b = 0; printf("b = %d \n", b); getchar(); } ISPIS PROGRAMA: b=1 Programske petlje Programske petlje se koriste kada se više puta želi ponoviti ista naredba ili blok naredbi. U programskom jeziku C postoje tri petlje: for, while i do-while. Opći oblik programske petlje for: for (inicijalizacija; uvjet; promjena vrijednosti) { blok naredbi } 26

Primjer (for petlja): Program ispisuje brojeve od 0 do 9. #include void main() { int a; for (a = 0; a < 10; a++) printf("%d\n", a); }

getchar();

Opći oblik programske petlje while: while (uvjet) { blok naredbi } Primjer (while petlja): Program ispisuje brojeve od 9 do 0. #include void main() { int a = 9; while (a > -1) { printf("%d\n", a); a--; } }

getchar();

Opći oblik programske petlje do-while: do { blok naredbi } while (uvjet);

27

Primjer (do-while petlja): Program ispisuje brojeve od 0-9. #include void main() { int a = 0; do { printf("%d\n", a); a++; } while (a < 10); getchar(); } Petlje for i while se ne moraju uopće izvršiti (ako uvjet nije ispunjen), ali petlja do-while je specifična po tome što se ona uvijek izvrši barem jedan put, pa tek nakon prvog izvršavanja provjerava uvjet.

28

ZADACI Zadatak 3.1. Napišite program koji prihvaća i uspoređuje bilo koja dva cijela broja unesena sa tipkovnice. Neka se na ekran ispiše rezultat usporedbe tih brojeva. Rješenje: #include void main() { int a; int b; printf("Unesite prvi broj: "); scanf("%d", &a); printf("Unesite drugi broj: "); scanf("%d", &b); if (a > b) printf("%d > %d \n", a, b); else if (a < b) printf("%d > %d \n", b, a); else printf("%d = %d \n", a, b); getchar(); getchar(); } Zadatak 3.2. Napišite program koji ispisuje sumu svih brojeva od 1 do 10. Rješenje: #include void main() { int i; int suma = 0; for (i = 1; i b, b postaje a if (a > b) { temp = a; a = b; b = temp; } // ako je a > c, c postaje a if (a > c) { temp = a; a = c; c = temp; } // ako je b > c, c postaje b if (b > c) { temp = b; b = c; c = temp; } printf("a = %d, b = %d, c = %d\n", a, b, c); getchar(); getchar(); } Zadatak 3.5. Napišite program u kojem korisnik unosi ocjene ispita, sve dok se ne unese 0. Ocjene izvan dopuštenog raspona ocjena od 1 do 5 se zanemaruju. Program treba ispisati koliko je na 31

ispitu bilo pozitivnih (prolaznih), a koliko negativnih (nedovoljnih) ocjena, te kolika je bila prosječna ocjena ispita. Rješenje: #include void main () { int ocjena, brPoz = 0, brNeg = 0, suma = 0; float prosjek; do { printf("Unesite ocjenu: \n"); scanf("%d", &ocjena); if ((ocjena < 0) || (ocjena > 5)) { printf("NEPOSTOJECA OCJENA!\n"); } else { suma += ocjena; if (ocjena == 1) brNeg++; else if (ocjena > 1) brPoz++;

} } while (ocjena != 0);

prosjek = (float)suma/(brPoz + brNeg);

}

printf("\nBroj pozitivnih ocjena: %d\n", brPoz); printf("Broj negativnih ocjena: %d\n", brNeg); printf("--------------------------\n"); printf("Ukupno ocjena: \t\t%d\n", brPoz + brNeg); printf("\nProsjecna ocjena iznosi %g.\n", prosjek); getchar(); getchar();

32

Zadatak 3.6. Što je rezultat izvođenja slijedećih programa? Ispišite sve međurezultate do cilja! a)

b)

c)

#include

#include

#include

void main() { int i; for (i = 0; i < 5; i++) printf("%d\n",i); }

void main() { int i = 5; while (i) printf("%d\n", --i); }

void main() { int i = 5; while (i) printf("%d\n", i--); }

d)

e)

f)

#include

#include

#include

void main() { int i = 5; i = i + 2;

void main() { int i = 5;

void main() { int i = 5; int uvjet = 1; int suma = 0;

if (--i > 4) i = i + 3;

if (i < 5) printf ("Izlaz A\n");

}

else printf("Izlaz B\n");

}

while (uvjet) { if (i-- > 2) uvjet = 0; suma = suma + i; }

printf("%d\n", i);

}

printf("%d\n", suma);

Rješenja: a)

b)

c)

0 1 2 3 4

4 3 2 1 0

5 4 3 2 1

d)

e)

f)

Izlaz B

4

4 33

DODATNI ZADACI Zadatak 3.7. Napišite program koji pomoću for petlje ispisuje kvadrate, kubove i korijene prvih 20 prirodnih brojeva. Napomena: funkcija za računanje korijena je sqrt (engl. square root) i nalazi se u standardnoj biblioteci . Zadatak 3.8. Napišite program koji generira i ispisuje deset slučajnih brojeva. Napomena: koristite standardne biblioteke i , te funkcije srand() i rand(). Zadatak 3.9. Napišite program za pogađanje broja generiranog od strane računala. Broji se broj pokušaja, a pri svakom pokušaju računalo daje informaciju da li je broj veći, manji ili pogođen. Ako je broj pogođen, izlazi se iz programa, uz ispis pogođenog broja te ukupnog broja pokušaja. Napomena vezana uz zadatke 3.8 i 3.9: U programiranju ne postoje stvarno slučajni brojevi, već samo pseudoslučajni. Generator pseudoslučajnih brojeva će za istu ulaznu vrijednost uvijek generirati iste pseudoslučajne brojeve. Funkcija srand() postavlja inicijalnu vrijednost za generator slučajnih brojeva koji je implementiran u funkciji rand(). Ako se ne koristi srand(), rand() će izgenerirati uvijek isti niz pseudoslučajnih brojeva [2]. DOMAĆI RAD Zadatak 3.10. Napišite program u kojem korisnik unosi četiri cjelobrojne varijable, a program ih sortira po veličini (od najveće prema najmanjoj) isključivo korištenjem if naredbi (biti će vam potrebno šest if naredbi). Ne koristite petlje! Zadatak 3.11. a) Napišite program koji ispisuje cijelu ASCII tablicu. b) Napišite program u kojem korisnik unosi neki znak (slovo ili broj) a program onda ispisuje sva ostala slova ili brojeve koji se u ASCII tablici nalaze iza unesenog znaka. Na primjer, ako korisnik upiše broj '7', program treba ispisati '8 9'. Ako korisnik unese 'b', program treba ispisati ostatak abecede. Razlikujte mala i velika slova! Ignorirajte sve unesene znakove koji ne predstavljaju slovo ili broj! Napomena: možete koristiti funkcije isdigit(), isupper() i islower() za provjeru unesenih znakova, ali onda morate uključite biblioteku . Zadatak 3.12. Napišite program koji ispisuje rješenja kvadratne jednadžbe. Neka korisnik unese koeficijente kvadratne jednadžbe. Podsjetnik: 2

ax bxc=0

x=

−b± b2 −4ac 2a

34

VJEŽBA 4 Naredba switch-case Ako u izvornom kodu postoji posebno dugačak lanac if-else naredbi, dobra je praksa takve naredbe zamijeniti sa naredbom switch-case. Naredba switch-case je preglednije zapisana if-else naredba. Najčešće se koristi za stvaranje izbornika (engl. menu) [3]. Na primjer, ako korisnik unese broj '1', izvrši se jedna naredba, a ako unese '2', izvrši se neka druga naredba. Opći oblik naredbe switch-case: switch (izraz) { case 'vrijednost izraza 1': ... break; case 'vrijednost izraza 2': ... break; ... default: ... }

// izvršava se ako nijedan drugi uvjet nije zadovoljen

Primjer (switch-case naredba): Program dozvoljava unos jednoznamenkastog cijelog broja sa tipkovnice, te obaviještava korisnika o tome je li uneseni broj paran, neparan ili jednak nuli. #include void main() { int a; printf("Unesite cijeli broj: "); scanf("%d", &a); switch (a) { case 0: printf("Uneseni broj je jednak nuli.\n"); break; case 1: case 3: case 5: case 7: case 9: 35

printf("Uneseni broj je neparan.\n"); break; case 2: case 4: case 6: case 8:

}

}

printf("Uneseni broj je paran.\n"); break; default: printf("Dozvoljen je unos samo jednoznamenkastih brojeva.\n"); break;

getchar(); getchar();

Primijetite da je nakon više case naredbi izostavljena naredba break. To znači da će se za sve te case naredbe izvršiti isti blok programskog koda (prvi na koji program naiđe tijekom izvođenja). Ovakva je situacija poznata pod imenom "propadanje" kroz switch-case naredbu (engl. fallthrough). Naredbe break i continue Naredba break se koristi za prekid izvršavanja neke petlje. Slično, naredba continue se koristi za prekid jedne iteracije neke petlje, te skok na iduću iteraciju. Primjer (naredba break): #include void main() { int i; for (i = 0; i < 3; i++) { printf("Unutar petlje...\n"); break; }

// izlazak iz petlje

printf("Izasli smo iz for petlje!\n"); }

getchar();

36

ISPIS PROGRAMA: Unutar petlje... Izasli smo iz petlje! Primjer (naredba continue): #include void main() { int i; for (i = 0; i < 3; i++) { printf("Prvi tekst...\n"); continue; printf("Drugi tekst...\n"); }

// skače na provjeru uvjeta // ovaj se dio koda nikad ne izvrši

getchar(); } ISPIS PROGRAMA: Prvi tekst... Prvi tekst... Prvi tekst... Naredba goto Naredba goto je naredba bezuvjetnog skoka. Izvršavanjem naredbe goto program se grana na zadanu programsku liniju [3]. Opći oblik: #include void main() { ime_labele: ...programski kod... }

goto ime_labele;

37

Primjer (goto naredba): #include void main() { int i = 0; start: printf("Beam me up, Scotty!\n"); if (i == 0) { i = 1; goto start; }

// start je ime labele

// program skače na labelu start

getchar(); } ISPIS PROGRAMA: Beam me up, Scotty! Beam me up, Scotty! Većina programera izbjegava korištenje goto naredbe, budući da ona najčešće smanjuje preglednost i razumljivost programskog koda. U programskim jezicima više razine ova se naredba gotovo nikad ne koristi, a neki je jezici čak i zabranjuju. Sve što se može napraviti sa naredbom goto, može se napraviti i sa standardnim petljama u C-u. End-of-File (EOF) End-of-File (EOF) označava stanje u kojem se nađe operacijski sustav kada na standardnom ulazu ponestane znakova za čitanje. EOF je implementiran kao cjelobrojna konstanta koja ovisi o operacijskom sustavu, i na Windows-ima je definirana kao -1. Ako koristite neki drugi operacijski sustav, na slijedeći način možete provjeriti vrijednost konstante EOF: #include void main() { printf("%d\n", EOF); getchar(); }

38

Funkcija getchar() Funkcija getchar() se koristi za čitanje samo jednog znaka sa standardnog ulaza. Prototip funkcije: int getchar(void); Primijetite da funkcija vraća cijeli broj, a ne znak! Primjer: #include void main() { int i; int brojac = 0; while ((i = getchar()) != EOF) brojac++; printf("Broj upisanih znakova: %d.\n", brojac); getchar(); } Da bi zaustavili unos znakova u gornjem programu, tj. da bi signalizirali operacijskom sustavu da želite prestati sa unosom znakova, pozicionirajte se u novi redak i pritisnite Ctrl-Z (Windows) ili CtrlD (Linux). Funkcija getchar() će onda znati da je došlo do EOF, i vratiti će -1. Umjesto da pritišću Ctrl-Z ili Ctrl-D kada žele prekiniti unos znakova, programeri često koriste makro naredbe. Primjer: #include #define EOF '\n'

// makro naredba koja EOF u programskom kodu zamjenjuje sa \n

void main() { int i; int brojac = 0;

39

while ((i = getchar()) != EOF) brojac++; printf("Upisali ste %d znakova.\n", brojac); getchar(); } U ovome programu više nije potrebno pritisnuti Ctrl-Z ili Ctrl-D za prekid unosa znakova, već je potrebno samo prijeći u novu liniju, tj. stisnuti enter. Napomena: jedna od čestih grešaka u programiranju jest dodjela vrijednosti koju vraća funkcija getchar() nekoj znakovnoj varijabli, te onda uspoređivanje te varijable sa EOF. Primjer: #include void main() { char i; int brojac = 0;

// primijetite da je ovdje sada char, a ne int --> ovo se ne preporučuje

while ((i = getchar()) != EOF) brojac++; printf("Broj upisanih znakova: %d.\n", brojac); getchar(); } Sada pretpostavimo da char varijabla može sadržavati jednu od 128 znakovnih konstanti (u ASCII tablici je 128 znakova). Funkcija getchar() može vratiti cjelobrojnu vrijednost bilo koje od tih 128 znakovnih konstanti, ali može vratiti i -1 kada naiđe na EOF. Dakle, funkcija getchar() može vratiti ukupno 129 mogućih cjelobrojnih vrijednosti. U gornjem primjeru, kada se cjelobrojna vrijednost koju vraća funkcija getchar() (ukupno 129 mogućih vrijednosti) pokuša mapirati u char varijablu (ukupno 128 mogućih vrijednosti), mora doći do kolizije. Ako se slučajno -1 konvertira u 65 (znak 'A' u ASCII tablici), program može prestati sa čitanjem znakova iz datoteke ili sa standarnog ulaza kada naiđe na znak 'A', jer će pomisliti da je riječ o oznaci za EOF. Da je pak varijabla 'i' bila deklarirana kao unsigned char, EOF se nikada ne bi konvertirao u negativnu vrijednost, pa bi program zaglavio u beskonačnoj petlji. Međutim, ovakve vas stvari trebaju zabrinjavati samo kada niste sigurni koliko memorije zauzimaju 40

int i char varijable. U današnje su vrijeme uglavnom i jedna i druga dovoljno velike da, bez kolizije sa nekom drugom vrijednošću, prihvate vrijednost koju vraća funkcija getchar(). Ipak, kada se radi o uspoređivanju te vrijednosti sa EOF, uvijek se preporučuje koristiti int varijablu umjesto char varijable. Ako se pak ipak odlučite za char, morate pripaziti da uvijek bude deklarirana kao char ili signed char, a nikada unsigned char!

41

ZADACI Zadatak 4.1. Napišite program koji oponaša rad jednostavnog kalkulatora koji podržava operacije zbrajanja, oduzimanja, množenja i dijeljenja. Korisnik programu zadaje izraz, primjerice 9 * 5, a program ispisuje rezultat tog izraza. Koristite naredbu switch-case. Rješenje: #include void main() {

int broj1 = 0, broj2 = 0; float rezultat; char operator1; printf("Unesite izraz: \n"); scanf("%d %c %d", &broj1, &operator1, &broj2); switch (operator1) { case '+': rezultat = broj1 + broj2; printf("%d %c %d = %.4f\n", broj1, operator1, broj2, rezultat); break; case '-': rezultat = broj1 - broj2; printf("%d %c %d = %.4f\n", broj1, operator1, broj2, rezultat); break; case '*': rezultat = broj1 * broj2; printf("%d %c %d = %.4f\n", broj1, operator1, broj2, rezultat); break; case '/': if (broj2 == 0) { printf("Greska: dijeljenje sa nulom!\n"); break; } else { rezultat = (float)broj1 / broj2; 42

printf("%d %c %d = %.4f\n", broj1, operator1, broj2, rezultat); break; } default: printf("Nepoznata operacija!\n"); break; } getchar(); getchar();

}

Zadatak 4.2. Napišite program koji od korisnika traži da unese dva cijela broja, te zatim ispisuje tri broja koja se nalaze između njih. Ako je udaljenost brojeva manja od tri, program treba ispisati odgovarajuću poruku. Rješenje: #include void main () { int x, y, i; printf("Unesite dva cijela broja: "); scanf("%d %d", &x, &y); oznaka: if (y >= x) { if ((y - x) < 4) { printf("Brojevi su nedovoljno udaljeni!\n"); } else {

}

printf("Brojevi unutar zadanih granica: "); for (i = x + 1; i < x + 4; i++) printf("%d ", i);

}

else { 43

// ako je y > x zamijenimo im vrijednosti int t = x; x = y; y = t; goto oznaka; } getchar(); getchar(); } Zadatak 4.3. Napišite program u kojem korisnik unosi deset cijelih brojeva (koristite for petlju), a program vraća njihovu aritmetičku sredinu. Rješenje: #include void main() { int broj = 0; int brojac = 10; int suma = 0; int i; float prosjek = 0; for (i = 0; i < brojac; i++) { printf("Unesite broj: "); scanf ("%d", &broj); suma += broj; } prosjek = (float)suma/brojac; printf("Prosjecna vrijednost ovih deset unesenih brojeva iznosi: %f\n", prosjek);

}

getchar(); getchar();

44

Zadatak 4.4. Napišite program koji, uz pomoć petlji, na ekran ispisuje: a)

b)

* ** *** ****

ABBB AABB AAAB AAAA

Rješenje (a): #include void main() { char znak = '*'; int i, j; for (i = 4; i > 0; i--) { for (j = i-1; j < 4; j++) { printf("%c", znak); } printf("\n"); } }

getchar();

Rješenje (b): #include void main() { char a = 'A'; char b = 'B'; int i, j, k; for (i = 0; i < 4; i++) { for (j = i; j >= 0; j--) printf("%c", a); for (k = 3-i; k > 0; k--) 45

printf("%c", b); }

printf("\n");

getchar(); } Zadatak 4.5. Napišite program koji, uz pomoć naredbe getchar(), broji ukupan broj riječi koje unese korisnik. Prekid unosa znakova neka bude pritisak na tipku enter. Napomena: da bi izbjegli višestruke praznine, za definiciju riječi uzmite znak ili niz znakova kojima prethodi praznina. Rješenje: #include #define EOF '\n' void main() { int znak, prethodniZnak = ' '; int brojac = 0, i = 0; while ((znak = getchar()) != EOF) { if ((znak != ' ') && (prethodniZnak == ' ')) brojac++; }

prethodniZnak = znak;

printf("Broj upisanih rijeci: %d.\n", brojac); getchar(); }

46

DODATNI ZADACI Zadatak 4.6. Napišite program koji računa n-tu faktorijelu. Napomena: n se unosi pomoću scanf() funkcije, i ne može biti veći od 12. Zadatak 4.7. Napišite program koji ispisuje sve vrijednosti funkcije y = 15 * x + 76, za sve cijele parne brojeve x iz intervala [-7, 7]. Zadatak 4.8. Napišite program koji, pomoću petlji, generira i ispisuje: 1 232 34543 4567654 567898765 67890109876 7890123210987 890123454321098 90123456765432109 DOMAĆI RAD Zadatak 4.9. Napišite program koji zadanu vrijednost duljine u centimetrima pretvara u inche. (x(in) = x(cm) / 2,54). Zadatak 4.10. Napišite program koji za učitanu ocjenu ispisuje njezin opis. Na primjer, ako korisnik unese 5, program ispisuje "Izvrstan". Ako korisnik unese broj izvan intervala [1,5], program treba ispisati odgovarajuću poruku. Koristite switch-case naredbu. Zadatak 4.11. Napišite program koji ispisuje sve parne brojeve iz intervala od 0 do 100.

47

VJEŽBA 5 Nizovi Kolekcija varijabli istog tipa i zajedničkog imena zove se polje ili niz (engl. array). Niz u memoriji zauzima kontinuirani niz memorijskih lokacija [2], a deklaracijom niza rezervira se memorija potrebna za njegove članove. Nizovi mogu biti višedimenzionalni, a ime bilo kojeg niza predstavlja adresu prvog elementa njegovog elementa. Opći oblik glasi: tip ime_niza[izraz], gdje je izraz pozitivni cijeli broj. Primjer deklaracije niza: int x[100]; char text[80]; float n[12];

// niz od 100 cijelih brojeva // niz od 80 znakova // niz od 12 realnih brojeva

Primjer inicijalizacije niza: int znamenke[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; float x[6] = {0, 0.25, 0, -0.50, 0, 0}; char boja[5] = {'P', 'L', 'A', 'V', 'A'}; Polja ili nizovi u programiranju su slični vektorima i matricama u matematici. Ako se želi pristupiti članu nekog niza, to se radi pomoću indeksiranja. Primjer: #include void main() { int a[] = {1, 4, 7, 8, 17}; printf("%d ", a[0]); printf("%d", a[4]); }

// ispisujemo vrijednost prvog člana niza // ispisujemo vrijednost posljednjeg člana niza

getchar();

ISPIS PROGRAMA: 1 17

48

Niz znakova Niz znakova (engl. string) je polje koje se sastoji od niza znakova i koje završava oznakom kraja niza '\0' (engl. string terminator). Primjer (niz znakova): Program pohranjuje znakovni niz "Dalmacija" u jednodimenzionalni znakovni niz X. Budući da riječ "Dalmacija" ima devet slova, znakovni niz mora imati mjesta za deset znakova. Oznaka kraja niza se računa kao jedan znak. Ovako to izgleda u memoriji: D

a

l

m

a

c

i

j

a

\0

X[0] = 'D' X[1] = 'a' X[2] = 'l' X[3] = 'm' X[4] = 'a' X[5] = 'c' X[6] = 'i' X[7] = 'j' X[8] = 'a' X[9] = '\0' Programski kod: #include void main() { char X[10] = "Dalmacija"; printf("Znakovni niz glasi: %s.\n", X); getchar(); } ISPIS PROGRAMA: Znakovni niz glasi: Dalmacija.

49

Višedimenzionalni nizovi Višedimenzionalni nizovi su skupovi višedimenzionalnog niza bila bi matrica.

jednodimenzionalnih

nizova.

Primjer

nekog

Grafički prikaz dvodimenzionalnog niza: Tablica 5.1. Grafički prikaz dvodimenzionalnog niza X

stupac 0

stupac 1

stupac 2

stupac 3

stupac 4

stupac 5

stupac 6

redak 0

X[0][0]

X[0][1]

X[0][2]

X[0][3]

X[0][4]

X[0][5]

X[0][6]

redak 1

X[1][0]

X[1][1]

X[1][2]

X[1][3]

X[1][4]

X[1][5]

X[1][6]

redak 2

X[2][0]

X[2][1]

X[2][2]

X[2][3]

X[2][4]

X[2][5]

X[2][6]

redak 3

X[3][0]

X[3][1]

X[3][2]

X[3][3]

X[3][4]

X[3][5]

X[3][6]

Opći oblik: tip_niza ime_niza [dimenzija_1] [dimenzija_2] ... [dimenzija_n] tip_niza ime_ niza [broj_redaka] [broj_stupaca] ... [dodatne_dimenzije] Primjer deklaracije niza: float tablica[50][50]; char stranica[24][80]; static double zapisi [x][y][z]; Primjer inicijalizacije niza: int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; Primjer (dvodimenzionalni niz): Program ispisuje dvodimenzionalni niz od tri retka i četiri stupca. #include void main() { int niz[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; int i, j; for (i = 0; i < 3; i++) for (j = 0; j < 4; j++) printf("Niz [%d][%d] = %d\n", i, j, niz[i][j]); getchar(); 50

} ISPIS PROGRAMA:

51

ZADACI Zadatak 5.1. Napišite program koji jednodimenzionalni niz od sedam članova inicijalizira na nulu. Rješenje: #include void main() { int i; int niz[7]; for (i = 0; i < 7; i++) { niz[i] = 0; printf("niz[%d] = %d\n", i, niz[i]); } }

getchar();

Zadatak 5.2. Napišite program koji učitava niz od deset cijelih brojeva, te ih ispisuje na ekran korištenjem for petlje. Rješenje: #include void main() { int i = 0; int a; int niz[10]; for (i = 0; i < 10; i++) { printf("Unesite %d. broj: ", i+1); scanf("%d", &a); niz[i] = a; } printf("\n"); //ispisujemo originalni niz 52

for (i = 0; i < 10; i++) printf("%d. broj: %d\n", i+1, niz[i]);

}

getchar(); getchar();

Zadatak 5.3. Napišite program koji učitava znakovni niz sa tipkovnice i zatim ga ispisuje na ekran. Rješenje: #include void main() { char niz[80]; printf("Unesite znakovni niz: "); scanf("%s", niz);

// alternativno: gets(niz);

printf("Upisali ste niz: %s.\n", niz);

}

getchar(); getchar();

Zadatak 5.4. Napišite program u kojemu korisnik unosi niz od 10 cijelih brojeva. Program od tog niza napravi dva druga niza: jedan koji sadrži samo parne brojeve iz originalnog niza, te drugi koji sadrži samo neparne brojeve iz originalnog niza. Rješenje: #include void main() { int i = 0; int a; int niz[10]; int parni[10]; int neparni[10]; int brojac_parni = 0; int brojac_neparni = 0; for (i = 0; i < 10; i++) 53

{

printf("Unesite %d. broj: ", i+1); scanf("%d", &a); niz[i] = a;

} printf("\n"); // ispisujemo originalni niz for (i = 0; i < 10; i++) printf("%d. broj: %d\n", i+1, niz[i]); // parne i neparne brojeve dijelimo u dva niza for (i = 0; i < 10; i++) { if ((niz[i] % 2) == 0) // parni { parni[brojac_parni] = niz[i]; brojac_parni++; } else { }

// neparni neparni[brojac_neparni] = niz[i]; brojac_neparni++;

} printf("\nNiz koji sadrzi parne brojeve: "); // ispisujemo originalni niz for (i = 0; i < brojac_parni; i++) printf("%d ", parni[i]); printf("\nNiz koji sadrzi neparne brojeve: "); // ispisujemo originalni niz for (i = 0; i < brojac_neparni; i++) printf("%d ", neparni[i]); getchar(); getchar(); } Zadatak 5.5. Napišite program za zbrajanje matrica dimenzija 3x3. Matrice inicijalizirajte na proizvoljne vrijednosti unutar programskog koda. 54

Primjer zbrajanja matrica [4]:

  

 

1 3 2 0 0 5 10 30 25 1 3 7 1 0 0  7 5 0 = 17 05 00 = 8 5 0 1 2 2 2 1 1 12 21 21 3 3 3

Rješenje: #include void main() { int A[3][3] = {{1, 3, 2}, {1, 0, 0}, {1, 2, 2}}; int B[3][3] = {{0, 0, 5}, {7, 5, 0}, {2, 1, 1}}; int C[3][3]; int i, j; // zbrajanje matrica for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) C[i][j] = A[i][j] + B[i][j]; printf("Rezultat zbrajanja matrica:\n"); for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { printf("[%d][%d] = %d", i, j, C[i][j]); printf("\n"); } getchar(); getchar(); } ISPIS PROGRAMA:

55

Zadatak 5.6. Napišite program za množenje dviju matrica. Neka prva matrica ima dimenzije 2x3, a druga 3x2. Matrice inicijalizirajte na proizvoljne vrijednosti unutar programskog koda. Primjer množenja matrica [4]:







3 1 1 0 2⋅ 1⋅30⋅22⋅1 1⋅10⋅12⋅0 = 5 1 2 1 = −1 3 1 −1⋅33⋅21⋅1 −1⋅13⋅11⋅0 4 2 1 0

 

Rješenje: #include void main() { int A[2][3] = {{1, 0, 2}, {-1, 3, 1}}; int B[3][2] = {{3, 1}, {2, 1}, {1, 0}}; int C[2][2] = {{0, 0}, {0, 0}}; int i, j, k; // mnozenje matrica for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) { for (k = 0; k < 3; k++) C[i][j] = C[i][j] + A[i][k] * B[k][j]; } printf("Rezultat mnozenja matrica:\n"); for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) { printf("[%d][%d] = %d", i, j, C[i][j]); printf("\n"); } }

getchar();

ISPIS PROGRAMA:

56

Zadatak 5.7. Napišite program u kojem korisnik unosi elemente kvadratne matrice dimenzija 5x5. Program treba ispisati sve elemente matrice koji se nalaze ispod glavne dijagonale, te zatim te elemente prebaciti u jednodimenzionalni niz i onda ispisati i taj niz. Rješenje: #include void main() { int i, j, k = 0; int const n = 5; int niz[n][n]; int novi_niz[10]; // korisnik unosi elemente matrice for (i = 0; i < n; i++) { printf("Unesite %d elemenata %d. retka: ", n, i+1); for (j = 0; j < n; j++) scanf("%d", &niz[i][j]); } printf("\n"); // ispisujemo unesenu matricu for (i = 0; i < n; i++) { for (j = 0; j < n; j++) printf("%d ", niz[i][j]); }

printf("\n");

printf("\n"); // ispisujemo elemente ispod glavne dijagonale matrice for (i = 0; i < n; i++) { for (j = 0; j < i; j++) { printf("%d ", niz[i][j]); novi_niz[k] = niz[i][j]; k++; }

57

}

printf("\n");

printf("\n"); // ispisujemo novi niz for (i = 0; i < k; i++) printf("%d ", novi_niz[i]); printf("\n");

}

getchar(); getchar();

58

DODATNI ZADACI Zadatak 5.8. Napišite program koji učitava dva niza znakova i ispituje da li su ti nizovi jednaki. Kraj učitavanja niza određen je oznakom za prijelaz u novi redak ('\n'). Zadatak 5.9. Napišite program koji učitava redak teksta i pretvara mala slova u velika. Redak mora biti ograničen na 80 znakova. Napomena: za pretvaranje malih slova u velika koristite funkciju toupper() iz biblioteke , a za unošenje retka funkciju gets(). DOMAĆI RAD Zadatak 5.10. Napišite program koji učitava ime i prezime korisnika, dob i godinu rođenja, te ispisuje te informacije na ekran. Zadatak 5.11. Napišite program u kojem korisnik unosi niz od deset cijelih brojeva, a program ispisuje njihovu aritmetičku sredinu. Zadatak 5.12. Napišite program za zbrajanje matrica. Elemente matrice unosi korisnik, a svaka matrica može imati najviše 20 redaka i stupaca. Napomena: zbrajati se mogu samo matrice istih dimenzija.

59

VJEŽBA 6 Bubble sort algoritam Bubble sort algoritam je algoritam za sortiranje članova nekog niza. Ovo je jedan od najjednostavnijih, ali ujedno i najsporijih, algoritama za sortiranje. U slijedećem primjeru prikazan je način rada bubble sort algoritma. Algoritam u svakom koraku uspoređuje dva susjedna broja, i u slučaju da je prvi broj veći od drugog, zamjenjuje im mjesta. Napomena: podebljani brojevi su oni koji se trenutno uspoređuju. Ulazni niz brojeva: (0 8 1 3 2). 1. prolaz algoritma: • • • •

(0 8 1 3 2) --> (0 8 1 3 2) (0 8 1 3 2) --> (0 1 8 3 2) (0 1 8 3 2) --> (0 1 3 8 2) (0 1 3 8 2) --> (0 1 3 2 8)

// 0 < 8 --> nema zamjene // 8 > 1 --> zamjena // 8 > 3 --> zamjena // 8 > 2 --> zamjena

2. prolaz algoritma: • • • •

(0 1 3 2 8) --> (0 1 3 2 8) (0 1 3 2 8) --> (0 1 3 2 8) (0 1 3 2 8) --> (0 1 2 3 8) (0 1 2 3 8) --> (0 1 2 3 8)

// 0 < 1 --> nema zamjene // 1 < 3 --> nema zamjene // 3 > 2 --> zamjena // 3 < 8 --> nema zamjene

3. prolaz algoritma: • • • •

(0 1 2 3 8) --> (0 1 2 3 8) (0 1 2 3 8) --> (0 1 2 3 8) (0 1 2 3 8) --> (0 1 2 3 8) (0 1 2 3 8) --> (0 1 2 3 8)

// 0 < 1 --> nema zamjene // 1 < 2 --> nema zamjene // 2 < 3 --> nema zamjene // 3 < 8 --> nema zamjene

Iako je niz bio sortiran na kraju drugog prolaza algoritma, algoritam se je morao još jednom izvršiti da bi "znao" da je niz sortiran. Drugim riječima, algoritmu je potreban jedan prolaz bez ijedne zamjene da bi bio siguran da je algoritam sortiran. Analiza bubble sort algoritma Pod analizom algoritma podrazumijeva se procjena vremena izvršavanja tog algoritma. Vrijeme se poistovjećuje s brojem operacija koje odgovarajući program treba obaviti [5], i izražava se kao funkcija oblika O(n) (engl. Order(n)). Na primjer, za ulazni niz brojeva (0 8 1 3 2), n bi bio jednak pet. Da bi bubble sort algoritam uspio sortirati taj niz, u najboljem slučaju bi mu bilo potrebno O(n) operacija (u ovom slučaju 5), a u najgorem slučaju O(n2) operacija (u ovom slučaju 25). Kako se n povećava, najgore vrijeme izvršavanja algoritma eksponencijalno raste. Upravo zbog ovog 60

problema, bubble sort algoritam nije dobar za sortiranje nizova sa velikim brojem podataka. Drugi algoritmi kao što su heap sort i mergesort imaju puno bolje vrijeme izvršavanja – u najboljem slučaju O(n), u najgorem slučaju O(nlogn) – ali su ipak nešto kompliciraniji.

61

ZADACI Zadatak 6.1. Napišite program koji u niz sprema maksimalno 20 cijelih brojeva koje unosi korisnik. Program sortira brojeve po veličini i ispisuje sortirani niz. Koristite bubble sort algoritam. Rješenje: #include void main () { int niz[20]; int n; int temp; int i, j; printf("Koliko brojeva zelite unijeti? "); scanf("%d", &n); if ((n 20)) printf("Morate unijeti broj u rasponu od 1 do 20.\n"); else if (n == 1) printf("Nije potrebno sortirati niz od samo jednog clana.\n"); else { printf("\nUpisite clanove niza:\t "); for (i = 0; i < n; i++) scanf("%d", &niz[i]); for (i = 0; i < n-1; i++) for (j = i+1; j < n; j++) { if (niz[i] > niz[j]) { temp = niz[i]; niz[i] = niz[j]; niz[j] = temp; } } printf("\nSortirani niz:\t "); for (i = 0; i < n; i++) printf("%d ", niz[i]); } 62

printf("\n"); getchar(); getchar(); } ISPIS PROGRAMA:

Zadatak 6.2. Pomoću for petlje unesite prosječne mjesečne temperature za jednu godinu. Ispišite maksimalnu, minimalnu i prosječnu godišnju temperaturu. Rješenje: #include void main() { float godina[12]; int i; float prosjecnaTemperatura = 0; float max, min; for (i = 0; i < 12; i++) { printf("Temperatura za mjesec broj %d: \t", i+1); scanf("%f", &godina[i]); } printf("\n"); for (i = 0; i < 12; i++) { printf("Temperatura za mjesec broj %d: \t %.2f\n", i, godina[i]); } for (i = 0; i < 12; i++) prosjecnaTemperatura = prosjecnaTemperatura + godina[i];

63

prosjecnaTemperatura = prosjecnaTemperatura / 12; printf("Prosjecna temperatura: \t %.2f\n", prosjecnaTemperatura); max = godina[0]; for (i = 1; i < 12; i++) { if (max < godina[i]) max = godina[i]; } min = godina[0]; for (i = 1; i < 12; i++) { if (min > godina[i]) min = godina[i]; } printf("Maksimalna temperatura: \t %.2f\n", max); printf("Minimalna temperatura: \t %.2f\n", min);

}

getchar(); getchar();

Zadatak 6.3. Napišite program koji učitava proizvoljan broj znakova, te ispisuje koliko ima slova, brojeva i ostalih znakova. Kraj učitavanja niza određen je oznakom za prijelaz u novi redak ('\n'). Za unos znakova koristite funkciju getchar(). Rješenje: #include void main() { int slova = 0; int brojevi = 0; int ostaliZnakovi = 0; int znak; puts("Unesite neki niz znakova:"); while ((znak = getchar()) != '\n') { 64

if ((znak >= 'a' && znak = 'A' && znak = '0' && znak = 'a') && (maloSlovo 2 = 0001 a >> 3 = 0000 a >> 4 = 0000

0000 0000 0000 1000 0100

(dekadski brojevni sustav – 28087) (dekadski brojevni sustav – 438)

(dekadski brojevni sustav – 64) (dekadski brojevni sustav – 32) (dekadski brojevni sustav – 16) (dekadski brojevni sustav – 8) (dekadski brojevni sustav – 4)

Maskiranje bitova Bitznačajni operator I (AND) se najčešće koristi za maskiranje bitova. Maskiranje bitova je proces važan za računalnu sigurnost, grafiku i kriptografiju. Primjer: a = 0110 1101 1011 0111 maska = 0000 0000 0000 1111 rezultat = 0000 0000 0000 0111

(dekadski brojevni sustav – 28087) (dekadski brojevni sustav – 15) (dekadski brojevni sustav – 7)

82

#include void main() { unsigned n = 0x6db7; unsigned maska = 0xF;

// dekadski brojevni sustav - 28087 // dekadski brojevni sustav – 15

// korištenje znaka '#' uzrokuje pojavu znakova '0x' ispred keksadecimalnog broja printf("Vrijednost od n (n = %#x), maskirana sa %#x, je: %#x\n", n, maska, n & maska); }

getchar();

83

ZADACI Zadatak 9.1. Napišite program u kojem korisnik unosi neki cijeli broj, a program ispisuje njegov binarni ekvivalent. Rješenje: #include void main() { int x, i, n, b; printf ("Unesite jedan cijeli broj: "); scanf ("%d", &x); // sizeof(int) vraća broj bajtova koje zauzima int // 8 * sizeof(int) vraća broj bitova koje zauzima int n = 8 * sizeof(x); printf ("Binarni kod je: "); for (i = n-1; i >= 0; i--) { b = (x & (01 > (3^2+4*5)/ (9/2-8^3) izračunava vrijednost izraza 324⋅5 9 3 . −8 2 Pretraživanje i preuređivanje prethodno izvršenih izraza radimo sa 'tipkama-strelicama': gore, dolje, lijevo i desno. Na primjer, prethodni izraz pozivamo tipkom gore. 90

Slika 10.1. Osnovni prozor MATLAB-a Primijetili ste da se rezultat ispisuje na pod nazivom ans (engl. answer). Ukoliko želimo vrijednosti dodijeliti ime (ime konstante), ispred izraza napišemo ime i stavljamo znak jednakosti. Primjer: pozovimo prethodni izraz i dodijelimo ga konstanti k: >> k = (3^2+4*5)/ (9/2-8^3) >> k = -0.0571 Ukoliko ne želimo konstantu ispisati na ekran, na kraju izraza upisujemo točka-zarez ;. >> k = (3^2+4*5)/ (9/2-8^3); >> Na taj način konstanta k se ne ispisuje na ekranu, ali ostaje definirana u programu dok joj se ne dodijeli nova vrijednost, odnosno izraz.

91

Matrice i operacije sa matricama Rješavanje nekih problema (pogotovo onih vezanih za matrice i općenito za matematiku), puno je jednostavnije i brže u Matlabu nego u nekim drugom programskim jezicima (npr. C-u). Matrica je dvodimenzionalno polje brojeva, i usput osnovni podatak za rad u MATLAB-u. Matrica dimenzija [1,1] naziva se skalar, a matrica koja sadrži samo jedan stupac ili samo jedan redak naziva se vektor. Matrica se definira navođenjem njezinih redaka. Elementi redaka se razdvajaju zarezom ili praznim mjestom. Dva retka se razdvajaju simbolom ; na kraju retka. Primjer definiranja matrice u MATLAB-u: A = [16 3 2 13; 5 10 11 8; 9 6 7 12; 4 15 14 1]; ili A = [16, 3, 2, 13; 5, 10, 11, 8; 9, 6, 7, 12; 4, 15, 14, 1]; Ovime smo definirali matricu:



16 3 2 13 5 10 11 8 A= 9 6 7 12 4 15 14 1



Napomena: matrice je uobičajeno označavati velikim slovima. Matrične operacije i funkcije Nad matricama se primjenjuju matematičke operacije kao što su zbrajanje (oznaka je +), oduzimanje (-), množenje (*) i dijeljenje (množenje jedne matrice sa inverznom drugom matricom) (/). Napomena: prilikom množenja ili dijeljenja matrica treba voditi računa o uzajamnoj usklađenosti veličine matrica. MATLAB poznaje standardne matrične operacije i funkcije kao što su transponiranje, potenciranje, nalaženje inverzne matrice i određivanje determinante. Na primjer, slijedeće naredbe >> B' >> B^2 >> inv(B) >> det(B) određuju odgovarajuću transponiranu matricu B, kvadrat matrice B, inverznu matricu B (inverzna matrica se može odrediti i sa B^(-1)) i determinantu matrice B, respektivno.

92

Posebne operacije sa matricama Ako su matrice A i B istih dimenzija, i ukoliko je ⊕ binarna operacija, tada je . ⊕ posebna operacija koja se može primijeniti na matrice. Karakteristične posebne operacije (element sa elementom) su: množenje redaka (.*), dijeljenje redaka (./) i potenciranje redaka (.^). Primjer: >> A = [2 4 6; 1 2 3]; >> B = [1 1 1; 2 2 2]; >> A.*B

% A * B bi generiralo grešku

U gornjem smo primjeru definirali matrice A i B, te izračunali njihov umnožak. Izdvajanje stupaca (redaka) matrice Indeksiranje matrica, odnosno izdvajanje vektora redka ili vektora stupca ili pojedinih elemenata vrši se korištenjem simbola dvotočke ':'. Prvi indeks se odnosi na redak, a drugi indeks na stupac. Primjer: >> D = [9 8 7; 6 5 4; 3 2 1]; >> e = D(:, 3) >> f = D(1, :) >> g = D(1:2, 2) >> h = D(3, 2:3)

% definiramo matricu D % izdvajamo treći stupac matrice D % izdvajamo prvi redak matrice D % izdvajamo prva dva elementa drugog stupca % izdvajamo drugi i treći element trećeg retka

Napomene MATLAB razlikuje velika i mala slova. Bilo koja linija napisana iza znaka '%' predstavlja komentar. Informacije o pojedinim naredbama se mogu dobiti primjenom naredbe help. Na primjer: >> help zeros prikazuje opis naredbe zeros. Varijable se iz MATLAB-ove memorije brišu naredbama clear ime_varijable ili clear all. Naredbom clc se isprazni komandni ekran.

93

Vektori Matrica sa jednim retkom ili stupcem predstavlja vektor. Na primjer: >> v1 = [1 2 3 4]; >> v2 = [1; 2; 3; 4];

% matrica sa jednim retkom % matrica sa jednim stupcem

MATLAB omogućava definiranje vektora čiji elementi imaju konstantan međusoban razmak. Na primjer, definirajmo vektor na intervalu [0, 100] sa korakom 10. >> x = 0 : 10 : 100; Dakle, rezultat je [0 10 20 30 40 50 60 70 80 90 100] i u konkretnom primjeru, 0 je početni element vektora, 10 je srednji element koji predstavlja korak između dva elementa vektora, dok je 100 zadnji element. Pa možemo zaključiti da je sintaksa ovakvog definiranja vektora: ime_vektora = prvi_element : korak : zadnji_element; Ako se korak eksplicitno ne navede, po defaultu iznosi jedan. Polinomi Polinomi se definiraju preko vektora koeficijenata polinoma, od koeficijenta ispred najvišeg stupnja polinoma do slobodnog elementa. Na primjer, polinom 2x3 − x 23 se u MATLAB-u definira na slijedeći način: >> a = [2 -1 0 3] Napomena: primijetite da u zadanom polinomu koeficijent uz x ima iznos 0. Korijeni polinoma se mogu odrediti primjenom naredbe roots. Na primjer: >> roots([1 5 6]) pronalazi korijene polinoma x 2 5x6. Inverzna naredba roots naredbe je poly. Naime, ukoliko znamo korijene polinoma, primjenom naredbe poly, dobivamo koeficijente polinoma. Na primjer: >> poly([-2 -3]) čime iz zadanih korijena x1 = -2 i x2 = -3 dobivamo polinom x 2 5x6.

94

Rješavanje sustava linearnih jednadžbi Rješavanje sustava jednadžbi obavlja se preko operacija sa matricama. Pretpostavimo da tražimo rješenje Ax=B sustava.

Ax=B se može zapisati i na slijedeći način:

Rješenje sustava Ax=B bilo bi jednako x =A−1 B . Na primjer, rješenje sustava jednadžbi x + 3y = 7 i 2x – 5y = 8 bilo bi: >> A = [1 3; 2 -5] >> B = [7; -8] >> X = inv(A)*B Crtanje funkcija MATLAB posjeduje različite funkcije za grafički prikaz dobivenih rezultata. Najčešće korištena naredba za grafički prikaz funkcija je plot, sa sintaksom: plot(x, y, 'opcije') Na primjer: >> x = -1 : 0.01 : 1; >> plot(x, exp(x)+1) crta funkciju e x 1 za −1 ≤ x ≤ 1 u posebnom prozoru. Korak u definiranju vektora elemenata je u ovom konkretnom slučaju 0.01, i s ovim određujemo finoću i preciznost prikaza funkcije. Alternative MATLAB-u Besplatne alternative MATLAB-u su Octave (http://sourceforge.net/projects/octave/files/) i Scilab (http://www.scilab.org/products/scilab/download).

95

ZADACI Zadatak 10.1. Definirajte matrice:



 

−2 3 1 M= 1 5 6 7 4 1 Izdvojite:

a) b) c) d) e)

  

2 4 8 N = −1 2 9 2 3 2

1 1 1 O= 2 2 2 3 3 3

posljednji stupac matrice M, prva dva elementa drugog reda matrice N drugi i treći element prvog stupca matrice O drugi redak matrice M prva dva elementa trećeg stupca zbroja matrica M + N

Rješenje: >> M = [-2 3 1; 1 5 6; 7 4 1]; >> N = [2 4 8; -1 2 9; 2 3 2]; >> O = [1 1 1; 2 2 2; 3 3 3]; >> a = M(:, 3); >> b = N(2, 1:2); >> c = O(2:3, 1); >> d = M(2, :); >> E = M + N; >> e = E(1:2, 3); Zadatak 10.2. Riješite sustav jednadžbi: 1. x 2y=3 2. −x y4z=7 3. 2x3y−z=5 Rješenje: A = [1 2 0; -1 1 4; 2 3 -1]; B = [3; 7; 5]; x = inv(A) * B ISPIS PROGRAMA: 15 -6 7

96

Zadatak 10.3. Riješite sustav jednadžbi: 4. 5. 6. 7.

−x 1x 3=1 x 1− x 2=−1 x 2 x 3=1 x 1 x 4=3

Rješenje: A = [-1 0 1 0; 1 -1 0 0; 0 1 1 0; 1 0 0 1]; B = [1; -1; 1; 3]; x = inv(A) * B ISPIS PROGRAMA: -0.50000 0.50000 0.50000 3.50000 Zadatak 10.4. Pronađite korijene polinoma y=−3x 44x3 −x3. Rješenje: y = [-3 4 0 -1 3]; roots(y) ISPIS PROGRAMA (Octave): 1.48680 + 0.00000i 0.34243 + 0.82767i 0.34243 – 0.82767i -0.83832 + 0.00000i Zadatak 10.5. Na istoj slici iscrtati grafove funkcija y 1=x 32x 2−4 i y 2=−5x 23, na intervalu (5,5), sa korakom 0.01. Neka graf funkcije y 1 bude iscrtan žutom bojom, a graf funkcije y 2 zelenom bojom. Rješenje: x = -5 : 0.01 : 5; y1 = x.^3 + 2.*x – 4; y2 = -5.*x.^2 + 3; plot(x, y1, 'y', x, y2, 'g')

97

Zadatak 10.6. Nacrtajte graf funkcije y=

ex 1e

1 1 x

, na intervalu (-5,5), sa korakom 0.02.

Rješenje: x = -5 : 0.02 : 5; y = exp(x)./(1 + exp(1./(1 + x))); plot(y)

Zadatak 10.7. Nacrtajte graf funkcije y=

ex

1 , na intervalu (-5,5), sa korakom 0.5. Neka graf 1e 1 x bude zelene boje, i neka sve točke na grafu budu označeve kružićima. Napomena: dodatne opcije za crtanje grafa funkcije objašnjene su u dodatku.

Rješenje: x = -5 : 0.5 : 5; y = exp(x)./(1 + exp(1./(1 + x))); plot(y, '-og')

98

DOMAĆI RAD Zadatak 10.8. Pronađite korijene polinoma y=7x 4 4x 2−6x3. sinx cosx i y 2= , na intervalu (0,10), sa x x korakom 0.01. Neka graf funkcije y 1 bude iscrtan žutom bojom, a graf funkcije y 2 zelenom bojom. Zadatak 10.9. Na istoj slici iscrtati grafove funkcija y 1=

99

DODATAK VJEŽBI 10 Grafovi U MATLAB-u se može specificirati tip linije kojom se iscrtava graf, te se mogu prikazati oznake za željene točke na grafu. Tablica 10.1. Tipovi linija u MATLAB-u [7] Oznaka u MATLAB-u

Tip linije

'-'

solid line (default)

'--'

dashed line

':'

dotted line

'-.'

dash-dot line

'none'

no line

Tablica 10.2. Oznake točaka na grafovima u MATLAB-u [7] Oznaka u MATLAB-u

Tip točke

'+'

plus sign

'o'

circle

'*'

asterisk

'.'

point

'x'

cross

'square' ili 's'

square

'diamond' ili 'd'

diamond

'^'

upward-pointing triangle

'v'

downward-pointing triangle

'>'

right-pointing triangle

'y*z je logicka %d.\n", x>y*z);

Испис на екрану

2.34.

Који је резултат извршавања следећег програмског кода:

#include main() { int a = 53, c = 3==5, d = 3!=5; printf("53 - %d\n3==5 - %d\n3!=5 - %d\n", a, b, c, d); printf("Konjunkcija : 3>5 && 5>3 - %d\n", a && b); printf("Disjunkcija : 3>5 || 5>3 - %d\n", a || b); printf("Negacija : !(3>5) - %d\n", !a); getche(); return 0; }

25

Збирка решених задатака из Програмског језика С – I део

Испис на екрану

2.35. Саставити програм који тестира операторе над битовима за два унета броја у хексадецималном облику и броја помераја. #include main() { int x, y, n; printf("Unesite dva heksadecimalna broja: "); scanf("%i %i", &x, &y); printf("Unesite broj pomeraja: "); scanf("%d", &n); printf("%#x & %#x = %#x\n", x, y, x&y); printf("%#x | %#x = %#x\n", x, y, x|y); printf("%#x ^ %#x = %#x\n", x, y, x^y); printf("%#x > %d = %#x\n", y, n, y>>n); getche(); return 0; }

Испис на екрану

26

Збирка решених задатака из Програмског језика С – I део

3

ГРАЊАЊЕ У ПРОГРАМУ

3.1.

Саставити програм који исписује обавештење да ли је унети цео број паран или непаран.

Први начин: #include main() { int broj; printf("Unesite broj: "); scanf("%d", &broj); if (broj%2 == 0) printf("\nUneti broj je paran.\n"); else printf("\nUneti broj je neparan.\n"); getche(); return 0; }

Испис на екрану

Други начин (условни оператор): #include main() { int broj; printf("Unesite broj: "); scanf("%d", &broj); (broj%2 == 0) ? printf("\nUneti broj je paran.\n") : printf("\nUneti broj je neparan.\n"); getche(); return 0; }

27

Збирка решених задатака из Програмског језика С – I део

3.2. Саставити програм који исписује обавештење да ли је унети број позитиван, негативан или је једнак нули. Први начин: #include main() { int a; printf("Unesite ceo scanf("%d", &a); if(a < 0) printf("\nBroj je else if(a > 0) printf("\nBroj je else printf("\nBroj je getche(); return 0; }

broj: "); Испис на екрану

negativan.\n"); pozitivan.\n"); nula.\n");

Други начин (условни оператор): #include main() { int a; printf("Unesite ceo broj: "); scanf("%d", &a); if(a == 0) printf("\nBroj je nula.\n"); else (a > 0) ? printf("\nBroj je pozitivan.\n") : printf("\nBroj je negativan.\n"); getche(); return 0; }

3.3. Саставити програм који за два унета цела броја исписује какав постоји релациони однос између њих (једнаки су, први већи од другог или први је мањи од другог). Први начин: #include main() { int a, b; printf("Unesite broj a= "); scanf("%d", &a); printf("Unesite broj b= "); scanf("%d", &b); if(a == b) printf("Brojevi su jednaki.\n");

28

Испис на екрану

Збирка решених задатака из Програмског језика С – I део

else if (a > b) printf("Broj a je veci od b.\n"); else printf("Broj a je manji od b.\n"); getche(); return 0; }

Други начин (условни оператор): #include main() { int a, b; printf("Unesite broj a= "); scanf("%d", &a); printf("Unesite broj b= "); scanf("%d", &b); if(a == b) printf("Brojevi su jednaki.\n"); else (a > b) ? printf("Broj a je veci od b.\n") : printf("Broj a je manji od b.\n"); getche(); return 0; }

3.4.

Саставити програм који за три унета цела броја исписује највећи.

#include main() { int a, b, c, max; printf("Unesite tri cela broja: "); scanf("%d%d%d", &a, &b, &c); max=a; if(b>max) max=b; if(c>max) max=c; printf("Najveci je %d\n", max); getche(); return 0; }

29

Испис на екрану

Збирка решених задатака из Програмског језика С – I део

3.5.

Саставити програм који три унета реална броја уређује у неопадајућем редоследу.

#include main() { double x, y, z, p; printf("Unesite tri realna broja: "); scanf("%lf%lf%lf", &x, &y, &z); if(x>y) { p=x; x=y; y=p; } if(x>z) { p=x; x=z; z=p; } if(y>z) { p=y; y=z; z=p; } printf("Uredjeni brojevi: %.2f %.2f getche(); return 0; }

Испис на екрану

%.2f\n", x, y, z);

3.6. Саставити програм који проверава и исписује да ли се на k-том месту унетог броја n налази бит који има вредност 1 или 0. #include main() { int n,k; printf(" Unesite broj: "); scanf("%d",&n); printf(" Unesite poziciju tog broja koju zelite da proverite: "); scanf("%d",&n); if((n & (1 90) printf("Ocena je 10\n"); else if (a>80) printf("Ocena je 9\n"); else if (a>70) printf("Ocena je 8\n"); else if (a>60) printf ("Ocena je 7\n"); else if (a>50) printf("Ocena je 6\n"); else printf("Ocena je 5\n"); getche(); return 0; }

Испис на екрану

3.8. Саставити програм који ће за унети опсег позитивних целих бројева од а до b исписати да ли се у задатом опсегу налази квадрат броја х (број х се уноси са тастатуре). Први начин: #include main() { int a, b, x; printf("Unesite donju i gornju granicu opsega: "); scanf("%d %d", &a, &b); printf("Unesite ceo broj x= "); scanf("%d", &x); if((a , и а≠0. Коефицијенти а и b и знак се уносе са тастатуре. #include main() { char z; double a, b, x; printf("Unesite znak []: "); scanf("%c", &z); printf("Unesite koeficijente:\na= "); scanf("%lf", &a); printf("b= "); scanf("%lf", &b); printf("\nNejednacina ima oblik: %.2fx+%.2f%c0\n", a, b, z); x=-b/a; if(a>0) printf("\nResenje x%c%.2f", z, x); else printf("\nResenje .2f%c%x", z, x); getche(); return 0; }

Саставити програм за решавање система од две линеарне једначине: a1 x + b1 y = c1 и

3.17.

a2 x + b2 y = c2 . Коефицијенти a1, a2, b1, b2, c1 и c2 се уносе са тастатуре. За решавање система користити методу детерминанти: D = a1

a2

Dy =

a1 a2

b1 b2

= a1b2 − a2b1 , Dx =

c1

b1

c2

b2

= c1b2 − c2b1 и

c1 = a1c2 − a2 c1 . c2

Систем има три решења: Dy

1) D ≠ 0 :

x=

2) D = Dx = Dy = 0 : 3) остали случајеви:

неодређено (бесконачно решења)

Dx , D

y=

D

нема решења.

#include main() { double a1, b1, c1, a2, b2, c2, D, Dx, Dy, x, y; printf ("Unesite koeficijente prve jednacine:\na1= scanf("%lf", &a1); printf("b1= "); scanf("%lf", &b1); printf("c1= "); scanf("%lf", &c1); printf("Unesite koeficijente druge jednacine:\na2= scanf("%lf", &a2); printf("b2= "); scanf("%lf", &b2); printf("c2= "); scanf("%lf", &c2); D = a1 * b2 - a2 * b1; Dx = c1 * b2 - c2 * b1; Dy = a1 * c2 - a2 * c1; if(D != 0) { x=Dx/D; y=Dy/D; printf ("\nResenje sistema:\nx= %.2f\n", x);

37

");

");

Збирка решених задатака из Програмског језика С – I део

printf ("y= %.2f\n", y); } else if(Dx==0 && Dy==0) printf ("Sistem ima beskonacno resenja.\n"); else printf ("Sistem nema resenja.\n"); getche(); return(0); }

Испис на екрану

3.18. Саставити програм за решавање квадратне једначине ax 2 + bx + c = 0 . У зависности од коефицијента а и дискриминанте D = b 2 − 4ac имамо следећа решења: 1) а≠0 и D>0: два различита и реална ( x1,2 = −b ± d ), 2a −b 2) а≠0 и D=0: два једнака реална ( x1,2 = ), 2a 3) а≠0 и D0) { x1=(-b+sqrt(D))/(2*a); x2=(-b-sqrt(D))/(2*a);

38

scanf("%lf",&a);

Збирка решених задатака из Програмског језика С – I део

printf("n\Resenja:\nx1=%.2f, x2=%.2f",x1,x2); } else if(D==0) { x1=(-b/(2*a)); printf("\nResenje:\nx1=x2=%.2f",x1); } else { x1=-b/(2*a); x2=sqrt(-D)/(2*a); printf("\nKompleksna resenja:\n"); printf("x1=%.2f+i%.2f, x2=%.2f-i%.2fi",x1,x2,x1,x2); } } else { if(b!=0) { x1=-c/b; printf("\nResenje:\nx=%.2f",x1); } else printf("Sistem nema resenja."); } getche(); return 0; }

Испис на екрану

39

Збирка решених задатака из Програмског језика С – I део

4

FOR ПЕТЉА

4.1. Саставити програм који ће пет пута исписати реченицу Pozdrav svima! употребом FOR петље. #include main() { int i; for(i=1; i ... > 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 celobrojnog niza. Jednostavnim modi kacijama svakim od ovih algoritama niz se moze sortirati i u opadajucem, rastucem ili neopadajucem poretku.

5.1

Vremenska sloˇ zenost

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

Milena Vujoˇsevi´c–Janiˇci´c

5.2

5.2 Selection sort

Selection sort

Primer 5.1 (Selection sort) U prvom prolazu se razmenjuju vrednosti a[0] sa onim ˇclanovima ostatka niza koji su ve´ci od njega. Na taj naˇcin ´ce se posle prvog prolaza kroz niz a[0] postaviti na najve´ci element niza. U svakom slede´cem prolazu, postavlja se na i-to mesto najveci element niza a[i], a[i+1] ... a[n-1]. Primer sortiranja: n = 6 ------------i = 0 j = 1 => i = 0 j = 2 => i = 0 j = 3 => i = 0 j = 4 => i = 0 j = 5 => ------------i = 1 j = 2 => i = 1 j = 3 => i = 1 j = 4 => i = 1 j = 5 => ...

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

3 5 5 6 8

1 1 1 1 1

5 3 3 3 3

2 2 2 2 2

6 6 6 5 5

8 8 8 8 6

a: a: a: a:

8 8 8 8

3 3 5 6

1 1 1 1

2 2 2 2

5 5 3 3

6 6 6 5

#include #include #define MAXDUZ 100 void selection(int a[], int n) { /* pomocna i brojacke promenljive */ int pom,i,j; /*Sortiranje*/ for(i=0; i i = 5 j = 4 => -------------

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

3 3 3 3 3

1 5 5 5 5

5 1 2 2 2

2 2 1 6 6

43

6 6 6 1 8

8 8 8 8 1

Milena Vujoˇsevi´c–Janiˇci´c

5.3 Bubble sort

i = 4 j = 0 => a: 5 3 2 6 8 1 ... void bubble(int a[], int n) { int i, j, pom; for(i=n-1; i>0; i--) for(j=0; j ------------i = 3 j = 3 => ------------i = 4 j = 4 => ...

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

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

Milena Vujoˇsevi´c–Janiˇci´c

5.5 Razni zadaci

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

5.5

Razni zadaci

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

46

Milena Vujoˇsevi´c–Janiˇci´c

5.5 Razni zadaci

main() { int int int int int int int i;

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

for (i = 0; i < nc; i++) printf("%d ", c[i]); printf("\n"); } Primer 5.8 Program uˇcitava niz studenata i sortira ih po njihovim ocenama. #include #include #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 && iprezime); fscanf(in, "%s", s->ime); fscanf(in, "%s", s->smer);

50

Milena Vujoˇsevi´c–Janiˇci´c

5.5 Razni zadaci

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

51

Milena Vujoˇsevi´c–Janiˇci´c

5.5 Razni zadaci

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

Milena Vujoˇsevi´c–Janiˇci´c

5.5 Razni zadaci

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

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

53

Milena Vujoˇsevi´c–Janiˇci´c

5.5 Razni zadaci

return 0; } Primer 5.11 Interpolaciona pretraga se moˇze porediti sa pretragom reˇcnika: ako neko traˇzi reˇc na slovo B, sigurno ne´ce da otvori reˇcnik na polovini, ve´c verovatno negde bliˇze poˇcetku. /* Funkcija trazi u SORTIRANOM nizu a[] duzine n broj x. Vraca indeks pozicije nadjenog elementa ili -1, ako element nije pronadjen */ int interpolaciona_pretraga (int a[], int n, int x) { int l = 0; int d = n - 1; int s; /* Dokle god je indeks l levo od indeksa d... while (l a[d]) return -1; /* U suprotnom, x je izmedju a[l] i a[d], pa ako su a[l] i a[d] jednaki, tada je jasno da je x jednako ovim vrednostima, pa vracamo indeks l (ili indeks d, sve jedno je).Ova provera je neophodna, zato sto bismo inace prilikom izracunavanja s imali deljenje nulom. */ else if(a[l] == a[d]) return l; /* Indeks s je uvek izmedju l i d, ali ce verovatno biti blize trazenoj vrednosti nego da smo prosto uvek uzimali srednji element.*/ /* Racunamo sredisnji indeks */ s = l + ((double)(x - a[l]) / (a[d] - a[l])) * (d - l); /* Ako je sredisnji element veci od x, tada se x mora nalaziti u levoj polovini niza */ if (x < a[s]) d = s - 1; 54

Milena Vujoˇsevi´c–Janiˇci´c

5.5 Razni zadaci

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

Milena Vujoˇsevi´c–Janiˇci´c

5.5 Razni zadaci

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

56

Milena Vujoˇsevi´c–Janiˇci´c

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; y=*ip;

*ip = 0;

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

/* ip cuva adresu promenljive x, tj ip pokazuje na x */ /* y dobija vrednost onoga sto se nalazi na adresi koju cuva promenljiva ip, tj posto ip cuva adresu promenljive x, a x ima vrednost 1 to je i y sada 1 */ /* preko pokazivaca njema se sadrzaj na adresi koju cuva ip, prema tome, x je sada 0 */ /* x je /* x je /* x je zagrada

sada 10*/ sada 11*/ sada 12, neophodna zbog prioriteta operatora*/

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 tip ali je neophodno eksplicitno naglasiti na koji tip ukazuje pokazivac svaki put kada zelimo da koristimo sadrzaj adrese koju pokazivac cuva. Primer 6.1 Upotreba pokazivaˇca na prazan tip. #include main() { void *pp; /* Ovaj pokazivac moze da pokazuje na adrese na kojima se nalaze razliciti tipovi podataka. */ int x=2; 58

Milena Vujoˇsevi´c–Janiˇci´c

6.1 Pokazivaˇcka aritmetika — primeri

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

6.1

Pokazivaˇ cka aritmetika — primeri

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

59

Milena Vujoˇsevi´c–Janiˇci´c

6.1 Pokazivaˇcka aritmetika — primeri

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

Milena Vujoˇsevi´c–Janiˇci´c

6.1 Pokazivaˇcka aritmetika — primeri

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

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

Milena Vujoˇsevi´c–Janiˇci´c

6.1 Pokazivaˇcka aritmetika — primeri

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

Milena Vujoˇsevi´c–Janiˇci´c

6.1 Pokazivaˇcka aritmetika — primeri

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

63

Milena Vujoˇsevi´c–Janiˇci´c 6.2 Niska karaktera i pokazivaˇc na konstantnu nisku

printf("%s",linija); }

6.2

Niska karaktera i pokazivaˇ c na konstantnu nisku

Prilikom deklaracije treba praviti razliku izmed¯u niza znakova i pokazivaca na konstantnu 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 znake moguce je promeniti, na primer, nakon naredbe poruka[0] = ’D’; poruka ce sadrzati tekst Danas je lep dan!. Niska poruka se uvek odnosi na novo 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 je preusmeriti ovaj pokazivac sledecom naredbom pporuka = poruka; i u ovom slucaju ce pokazivac pporuka pokazivati na mesto u memoriji na kojem se nalazi poruka. Pokusaj modi kacije elementa niske karaktera na koju ukazuje pporuka nije de nisan (jer je to konstantna niska). Na primer, rezultat naredbe pporuka[0]=’D’ je nede nisan. 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, a pporuka3 na drugo mesto u memoriji. Ako uporedimo (pporuka1==pporuka3) uporedice se vrednosti pokazivaca. Ako uporedimo (pporuka1 < pporuka2) uporedice se vrednosti pokazivaca. Ako dodelimo pporuka1=pporuka3 tada ce pporuka1 dobiti vrednost pokazivaca pporuka3 i pokazivace na isto mesto u memoriji. Nece se izvrsiti kopiranje sadrzaja memorije. Primer 6.10 Nakon deklaracije char a[] = "informatika"; char *p = "informatika"; 64

Milena Vujoˇsevi´c–Janiˇci´c

6.3 Pokazivaˇci na pokazivaˇce

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

6.3

Pokazivaˇ ci na pokazivaˇ ce

Pokazivacke promenljive su podaci kao i svi drugi, pa samim tim imaju svoju adresu u memoriji. Zbog toga je moguce govoriti o pokazivacima na pokazivace. Na primer, ako je de nisan pokazivac na int: int *p; tada mozemo de nisati 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 pokazivaca na pokazivac pp, zato sto je *pp ekvivalentno sa p. Takod¯e, celom broju na koji pokazuje p moze se pristupiti i sa **pp zato sto je **pp ekvivalentno sa *p.

6.4

Nizovi pokazivaˇ ca

Elementi nizova mogu biti bilo kog tipa, pa i pokazivaci. Na primer, niz pokazivaca na karaktere se moze de nisati na sledeci nacin: char * pokazivaci[100]; Ovim se rezervise prostor za niz od 100 pokazivaca na karaktere. Njima se pristupa putem indeksa, kako je i uobicajeno. S obzirom da se ime niza uvek ponasa kao adresa prvog objekta u nizu, sledi da je izraz pokazivaci tipa ”pokazivac na pokazivac na char”. Otuda se uobicajena pokazivacka aritmetika moze primenjivati i na nizove pokazivaca.

65

Milena Vujoˇsevi´c–Janiˇci´c

6.4 Nizovi pokazivaˇca

Primer 6.11 Program ucitava linije iz fajla ulaz.txt i zatim ih ispisuje na standardnom izlazu sortirane po duˇzini, poˇcev od najkra´ce linije. #include #include #include #include



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

66

Milena Vujoˇsevi´c–Janiˇci´c

6.4 Nizovi pokazivaˇca

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

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

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

67

Milena Vujoˇsevi´c–Janiˇci´c

6.4 Nizovi pokazivaˇca

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

68

Glava 7

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

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

69

Milena Vujoˇsevi´c–Janiˇci´c

7.1 Osnovni primeri

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

7.1

Osnovni primeri

Primer 7.1 Raˇcunanje faktorijela prirodnog broja. #include unsigned long faktorijel_iterativno(int n) { long f = 1; int i; for (i = 1; i 2=1+1 | / \ 1 Fib(1) Fib(0) | | 1 1

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

Milena Vujoˇsevi´c–Janiˇci´c

7.1 Osnovni primeri

/* Funkcija izracunava n-ti fibonacijev broj */ int fibonacci(int n) { /* Ako je podproblem vec resen, uzimamo gotovo resenje! */ if(f[n] != 0) return f[n]; /* Izlaz iz rekurzije */ if(n < 2) return f[n] = 1; else /* Rekurzivni pozivi */ return f[n] = fibonacci(n - 1) + fibonacci(n - 2); } /* Test program */ int main() { int n, i; /*Inicijalizuje se niz*/ for(i=0; i ... | | | | | ----------Y ... ===>

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

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

| === ===== ======= ========= ----------Y

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

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

78

Milena Vujoˇsevi´c–Janiˇci´c

7.1 Osnovni primeri

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

79

Milena Vujoˇsevi´c–Janiˇci´c

7.1 Osnovni primeri

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

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

80

Milena Vujoˇsevi´c–Janiˇci´c

7.2 Binarna pretraga

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

7.2

Binarna pretraga

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

Milena Vujoˇsevi´c–Janiˇci´c

7.3 MergeSort algoritam

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

7.3

MergeSort algoritam

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

Milena Vujoˇsevi´c–Janiˇci´c

7.3 MergeSort algoritam

prolazi kroz desnu polovinu niza. Indeks k prolazi kroz pomocni niz b[] */ i = l; j = s + 1; k = 0; /* "ucesljavanje" koriscenjem pomocnog niza b[] */ while(i > > dobijamo tako sto zamenimo mesto pivotu i poslednjem elementu manjem od njega */ swap(a, l, last); /* Pivot se sada nalazi na poziciji last */ return last; } /* Funkcija sortira deo niza brojeva a izmedju pozicija l i r*/ void quick_sort(int a[], int l, int r) { int i, last; /* Ukoliko je interval [l, r] prazan nema nista da se radi */ if (l >= r) return; /* Particionisemo interval [l, r] */ int pivot = partition(a, l, r); /* Rekurzivno quick_sort(a, /* Rekurzivno quick_sort(a,

sortiramo elemente manje od pivota */ l, pivot-1); sortiramo elemente vece pivota */ pivot+1, r);

}

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

85

Glava 8

Pokazivaci na funkcije Binarne instrukcije koje cine funkciju su takod¯e negde u memoriji, i imaju svoju adresu. Zato je moguce je de nisati pokazivac na funkciju. Na primer, deklaracija: int (*fp)(int); de nise promenljivu f, koja je tipa ”pokazivac na funkciju koja prihvata argument tipa int, i vraca vrednost tipa int”. Ova promenljiva sadrzi adresu neke funkcije tog tipa. Na primer, ako je deklarisana funkcija: int f(int a); Tada je moguce dodeliti: fp = &f; /* Operator adrese nije neophodan. */ Promenljiva fp sadrzi adresu funkcije f. Funkcija f se sada moze pozivati dereferenciranjem pokazivaca fp: (*fp)(3); Zagrade oko izraza *fp su neophodne zbog prioriteta operatora. Dereferenciranjem pokazivaca na funkciju dobija se funkcija, koja se onda poziva na uobicajen nacin. Primer 8.1 Program demonstrira upotrebu pokazivaˇca na funkcije. #include int kvadrat(int n) { return n*n; } int kub(int n) { return n*n*n; } 86

Milena Vujoˇsevi´c–Janiˇci´c

Pokazivaˇci na funkcije

int parni_broj(int n) { return 2*n; } /* Funkcija izracunava sumu brojeva f(i) za i iz intervala [1, n]. int (*f) (int) u argumentu funkcije sumiraj je pokazivac na funkciju sa imenom f, koja kao argument prima promenljivu tipa int i vraca kao rezultat vrednost tipa int */ int sumiraj(int (*f) (int), int n) { int i, suma=0; for (i=1; ivrednost == broj) { pomocni = tekuci->sledeci; tekuci->sledeci = tekuci->sledeci->sledeci; free(pomocni); } else tekuci = tekuci->sledeci; /* vracamo novu glavu */ return glava; } /* void obrisi_element(Cvor **glava, int broj) { Cvor *tekuci; Cvor *pomocni; /* Brisemo sa pocetka liste sve eventualne cvorove koji su jednaki datom broju, i azuriramo pokazivac na glavu */ while(*glava != NULL && (*glava)->vrednost == broj) { pomocni = (*glava)->sledeci; free(*glava); *glava = pomocni; } /* Ako je nakon toga lista ostala prazna prekidamo funkciju */ if(*glava == NULL) return; /* Od ovog trenutka se u svakom koraku nalazimo

125

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

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

126

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

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

127

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

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

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

130

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

131

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

132

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

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

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

133

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

134

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

136

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

return napravi_cvor(rec); /* Ukoliko lista nije prazna poredimo rec sa elementom u glavi */ cmp=strcmp(pocetak->ime,rec); /* Ukoliko je rec pronadjena samo uvecavamo njen broj pojavljivanja */ if (cmp==0) { pocetak->br_pojavljivanja++; return pocetak; } /* Ukoliko je rec koju ubacujemo veca od tekuce reci, ubacujemo je rekurzivno u rep */ else if (cmp>0) { pocetak->sledeci=ubaci_sortirano(pocetak->sledeci,rec); return pocetak; } /* Ukoliko je rec koju ubacujemo manja od tekuce reci, gradimo novi cvor i ubacujemo ga ispred pocetka */ else { cvor* novi=napravi_cvor(rec); novi->sledeci=pocetak; return novi; } } /* Pomocna funkcija koja cita rec sa standardnog ulaza i vraca njenu duzinu, odnosno -1 ukoliko se naidje na EOF */ int getword(char word[], int lim) { int c, i=0; while (!isalpha(c=getchar()) && c!=EOF) ; if (c==EOF) return -1; do { word[i++]=c; }while (iime,rec)==0) return lista; /* Da bi pretrazivanje u sortiranoj listi bilo efikasnije: if (strcmp(lista->ime,rec)sledeci,rec); } main() { cvor* lista=NULL; char procitana_rec[80]; cvor* pronadjen; while(getword(procitana_rec,80)!=-1) lista=ubaci_sortirano(lista,procitana_rec); pronadjen=nadji_rec(lista,"programiranje"); if (pronadjen!=NULL) printf("Rec programiranje se javlja u listi!\n"); else printf("Rec programiranje se ne javlja u listi!\n"); ispisi_listu(lista); obrisi_listu(lista); } Primer 10.4 Primer ilustruje sortiranje liste. Lista moze biti sortirana na dva naˇcina. Prvi naˇcin je da se premeˇstaju ˇcvorovi u listi (ˇsto zahteva prevezivanje pokazivaˇca), a drugi je da se premeˇstaju vrednosti koje ˇcvorovi sadrˇze. Prvi naˇcin je komplikovaniji, ali je ˇcesto efikasniji, naroˇcito ako su objekti koji se ˇcuvaju kao podaci u ˇcvorovima veliki. U oba sluˇcaja se moˇze koristiti rekurzija, a moˇze se realizovati i iterativno. U ovom primeru ilustrujemo oba naˇcina sortiranja lista (prvi rekurzivno, a drugi iterativno). #include #include 138

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

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

139

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

140

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

141

Milena Vujoˇsevi´c–Janiˇci´c

10.1 Implementacija

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

142

Milena Vujoˇsevi´c–Janiˇci´c

10.2 Kruˇzna (cikliˇcna) lista

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

10.2

Kruˇ zna (cikliˇ cna) lista

pocetak ciklicne liste

A

B

C

...

Z

Slika 10.1: Kruzna lista

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

Milena Vujoˇsevi´c–Janiˇci´c

10.3 Red

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

10.3

Red

pocetak reda (brisanje — get)

A

kraj reda (dodavanje — add)

B

...

X

NULL

novi element Y pocetak reda

A

novi kraj reda

B

...

X

pocetak reda nakon brisanja

B

Y

NULL

kraj reda

...

X

Y

NULL

Slika 10.2: Red

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

Milena Vujoˇsevi´c–Janiˇci´c

10.3 Red

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

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

/* Funkcija dodaje na kraj reda novi fajl */ 145

Milena Vujoˇsevi´c–Janiˇci´c

10.3 Red

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

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

146

Milena Vujoˇsevi´c–Janiˇci´c

10.3 Red

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

147

Milena Vujoˇsevi´c–Janiˇci´c

10.3 Red

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

148

Milena Vujoˇsevi´c–Janiˇci´c

10.3 Red

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

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

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

Milena Vujoˇsevi´c–Janiˇci´c

10.3 Red

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

pomocni = *pocetak; *pocetak = (*pocetak)->sledeci; free(pomocni); if(*pocetak == NULL) *kraj = NULL; return 1; } main() { Cvor * pocetak = NULL, * kraj = NULL; int i, broj; /*Formiranje jednostavnog reda*/ for(i=0; i0 && odgovarajuce(otv_zagrade[br_otv-1], c)) { br_otv--; } else { printf("Visak zatvorenih zagrada: %c u liniji %d\n", c, br_linija); exit(1); } } } if (br_otv == 0) printf("Zagrade su u redu\n"); else printf("Visak otvorenih zagrada\n"); } Primer 10.9 Program proverava da li su etikete u datom HTML fajlu dobro uparene. #include #include #include #include



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

Milena Vujoˇsevi´c–Janiˇci´c

10.4 Stek

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

X

Y

...

A

NULL

X

V

...

A

NULL

...

A

NULL

novi element V

novi vrh steka

Y

vrh steka nakon brisanja

X

V

Slika 10.3: Stek

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

Milena Vujoˇsevi´c–Janiˇci´c

10.4 Stek

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

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

154

Milena Vujoˇsevi´c–Janiˇci´c

10.4 Stek

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

Milena Vujoˇsevi´c–Janiˇci´c

10.4 Stek

/* Funkcija dodaje broj na stek*/ void potisni_na_stek(Cvor **vrh, int broj) { Cvor * novi = napravi_cvor(broj); novi->sledeci = *vrh; *vrh = novi; } /* Funkcija skida broj sa steka */ int skini_sa_steka(Cvor **vrh, int *broj) { Cvor * pomocni; if(*vrh == NULL) return 0; if(broj != NULL) *broj = (*vrh)->broj); pomocni = *vrh; *vrh = (*vrh)->sledeci; free(pomocni); return 1; } main() { Cvor * vrh = NULL; int i, broj; /*Formiranje steka*/ for(i=0; ivrednost = broj; novi->prethodni = NULL; novi->sledeci = NULL; return novi; } /* Funkcija dodaje novi cvor na pocetak liste. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na novu glavu liste */ Cvor* dodaj_na_pocetak_liste(Cvor *glava, int broj) { Cvor *novi = napravi_cvor(broj); novi->sledeci = glava; if(glava != NULL) glava->prethodni = novi; return novi; } /* Funkcija dodaje novi cvor na kraj liste. Funkcija kreira novi cvor koriscenjem funkcije napravi_cvor(). Funkcija vraca pokazivac na glavu liste (koji moze biti promenjen u slucaju da je lista inicijalno bila prazna). */ Cvor* dodaj_na_kraj_liste(Cvor *glava, int broj) { Cvor *novi = napravi_cvor(broj); Cvor *tekuci = glava; /* slucaj prazne liste. U tom slucaju je glava nove liste upravo novi cvor. */ if(glava == NULL) return novi; /* Ako lista nije prazna, tada se krecemo duz liste sve dok ne dodjemo do poslednjeg cvora (tj. do cvora ciji pokazivac

161

Milena Vujoˇsevi´c–Janiˇci´c

10.5 Dvostruko povezane liste

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

162

Milena Vujoˇsevi´c–Janiˇci´c

10.5 Dvostruko povezane liste

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

163

Milena Vujoˇsevi´c–Janiˇci´c

10.5 Dvostruko povezane liste

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

164

Milena Vujoˇsevi´c–Janiˇci´c

10.5 Dvostruko povezane liste

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

165

Glava 11

Stabla Binarno stablo1 je skup cvorova koji su povezani na sledeci nacin: 1. Svaki cvor moze imati levog i desnog SINA (pri tom svaki od sinova moze biti i izostavljen, ili oba). Kazemo da je dati cvor RODITELJ svojim sinovima. Ako je cvor U roditelj cvoru V, tada pisemo da je U < V . Cvor koji nema ni levog ni desnog sina naziva se LIST. 2. Postoji jedan jedinstveni cvor takav da nema roditelja. Ovaj cvor nazivamo KOREN stabla. 3. U stablu nema ciklusa, tj. ne postoji niz cvorova x1,x2,...,xn, takav da je x1 < x2 < ... < xn < x1. Inace, niz cvorova x1 < x2 < ... < xn nazivamo put u stablu. Kazemo da je cvor U predak cvora V ako u stablu postoji put od U do V. Specijalno, svaki cvor je predak samom sebi. Lako se moze pokazati da je koren predak svih cvorova u stablu. Rastojanje od korena do nekog cvora naziva se visina cvora (koren je visine 0). Maksimalna visina cvora u stablu naziva se visina stabla. Stablo koje nema cvorova naziva se prazno stablo. Za svaki cvor V u stablu mozemo posmatrati stablo koje se sastoji od svih njegovih potomaka (svih cvorova kojima je on predak). Ovo stablo se naziva podstablo sa datim korenom V. Podstablo sa njegovim levim sinom kao korenom nazivamo levim podstablom cvora V, dok podstablo sa njegovim desnim sinom kao korenom nazivamo desnim podstablom cvora V. Ako je neki od njegovih sinova izostavljen, tada kazemo da je odgovarajuce podstablo cvora V prazno stablo. Specijalno, ako je V koren, tada njegovo levo i desno podstablo nazivamo levim i desnim podstablom datog (citavog) stabla. Stablo se moze de nisati 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 tako formiranog stabla. 166

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

NULL NULL

NULL NULL

NULL

NULL NULL

Slika 11.1: Binarno stablo

U programiranju se stabla obicno koriste kao strukture podataka, tako sto svaki cvor sadrzi po jedan podatak odred¯enog tipa. Nacin raspored¯ivanja podataka u stablu zavisi od konkretne primene stabla. S obzirom na rekurzivnu prirodu stabla, uobicajeno je da se u programiranju stabla obrad¯uju rekurzivno.

11.1

Binarno pretraˇ zivaˇ cko stablo

Posebna vrsta stabla su tzv. binarna pretrazivacka stabla. Ova stabla imaju osobinu da za svaki cvor vazi sledece: svi cvorovi njegovog levog podstabla sadrze podatke sa manjom vrednoscu od vrednosti podatka u tom cvoru, dok svi cvorovi njegovog desnog podstabla sadrze podatke sa vecom vrednoscu od vrednosti podatka u tom cvoru. Ovakva organizacija omogu ’cava e kasno pretrazivanje. Primer 11.1 Program demonstrira rad sa binarnim pretrazivackim drvetima ˇciji su podaci celi brojevi. Drvo sadrˇzi cele brojeve sortirane po veliˇcini. Za svaki cvor, levo podstabla sadrzi manje elemente, dok desno podstablo sadrzi vece. #include 1 Tekst

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

167

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

12

21

NULL NULL

5

15

NULL NULL

NULL

inf ix : 5, 12, 14, 15, 17, 21 14

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

NULL NULL

Slika 11.2: Ured¯eno stablo

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

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

169

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

170

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

171

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

prethodnih funkcija */

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

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

172

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

174

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

175

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

176

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

177

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

178

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

179

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

180

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

181

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

182

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

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

183

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

184

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

185

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

186

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

187

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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



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

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

189

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

190

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

191

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

194

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

195

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

196

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

197

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

struktura podataka pogodna za leksikografsku pretragu - u ovom sluˇcaju binarno pretraˇzivaˇcko drvo. Na kraju rada, ˇcvorovi drveta se ”presortiraju” na osnovu broja pojavljivanja. #include #include #include #include



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

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

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

strcpy(koren->etiketa, etiketa); koren->brojac = 1; koren->levi = NULL; koren->desni = NULL; return koren; } /* Rekurzivni pozivi */ if((cmp = strcmp(koren->etiketa,etiketa)) < 0) koren->desni = dodaj_leksikografski(koren->desni, etiketa); else if(cmp > 0) koren->levi = dodaj_leksikografski(koren->levi, etiketa); else koren->brojac++; /* uvecanje brojaca za vec prisutne etikete */ return koren; } /* Funkcija cita ulazni fajl i iz njega izdvaja sve otvorene html etikete (npr. ,
itd, ali ne i , 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 ’brojac >= broj) koren->desni = dodaj_po_broju(koren->desni, etiketa, broj); else if(koren->brojac < broj) koren->levi = dodaj_po_broju(koren->levi, etiketa, broj); return koren; } /* Funkcija pretpostavlja da je novo stablo ili prazno, ili uredjeno prema opadajucem poretku broja pojavljavanja etiketa. Funkcija u takvo stablo rekurzivno dodaje sve etikete iz starog stabla, i vraca koren na tako modifikovano novo stablo. */ Cvor * resortiraj_stablo(Cvor * staro, Cvor * novo) { /* Izlaz iz rekurzije - nemamo sta da dodamo u novo stablo */ if(staro == NULL) return novo;

202

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

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

203

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

/* Oslobadjamo stabla */ oslobodi_stablo(koren); oslobodi_stablo(resortirano); return 0; } Primer 11.7 Efikasnije sortiranje ˇcorova stabla — formira se niz pokazivaˇca na ˇcvorove stabla koji se u skladu sa funkcijom pored¯enja sortiraju pomo´cu biblioteˇcke qsort funkcije. #include #include 204

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

205

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

206

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

njenu duzinu, odnosno -1 ukoliko se naidje na EOF */ int getword(char word[], int lim, FILE* ulaz) { int c, i=0; /* Umesto funkcije getchar koristimo fgetc za rad sa datotekama */ while (!isalpha(c=fgetc(ulaz)) && c!=EOF) ; if (c==EOF) return -1; do { word[i++]=c; }while (ibr_pojavljivanja (*(cvor**)a)->br_pojavljivanja; }

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

207

Milena Vujoˇsevi´c–Janiˇci´c

11.1 Binarno pretraˇzivaˇcko stablo

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

208

Glava 12

Grafovi Graf1 G=(V,E) sastoji se od skupa V cvorova i skupa E grana. Grane predstavljaju relacije izmed¯u cvorova i odgovara paru cvorova. Graf moze biti usmeren (orijentisan), ako su mu grane ured¯eni parovi i neusmeren (neorjentisan) ako su grane neured¯eni parovi. Put u grafu je niz cvorova v1,v2,...,vn, pri cemu u grafu postoje grane (v1,v2), (v2,v3), ..., (vn-1, vn). Ako jos vazi da je v1 = vn, tada se ovakav put naziva ciklus. Iako su liste i stabla specijalni oblici grafova, u programiranju postoji bitna razlika: liste i stabala se prevashodno koriste kao kontejneri za podatke (u svaki cvor se smesta neki podatak odred¯enog tipa), grafovi se pre svega koriste za modeliranje nekih problema iz prakse, kako bi se ti problemi predstavili na apstraktan nacin i resili primenom algoritama nad odgovarajucim grafom. Na primer, cvorovima u grafu se mogu predstaviti raskrsnice u gradu, a granama ulice koji ih povezuju. Tada se zadatak nalazenja najkraceg puta od tacke A do tacke B u gradu moze svesti na nalazenje najkraceg puta od cvora u do cvora v u grafu. Slicno se mogu resavati problemi sa racunarskim mrezama (racunari se predstave cvorovima, a veze med¯u njima granama), itd. Grafovi se mogu predstavljati na vise nacina. Uobicajena su dva nacina predstavljanja grafova. To su matrica povezanosti grafa i lista povezanosti. Matrica povezanosti je kvadratna matrica dimenzije n, pri cemu je n broj cvorova u grafu, takva da je element na preseku i-te vrste i j-te kolone jednak jedinici ukoliko postoji grana u grafu od i-tog do j-tog cvora, inace je nula. Jasno je da ce neusmerenim grafovima odgovarati simetricne matrice. Prednost ovakvog predstavljanja je jednostavnost. Nedostatak ovakvog pristupa je neracionalno koriscenje memorije u slucaju ”retkih” grafova (grafova sa malo grana), gde ce najveci broj elemenata 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 je lista povezanosti. Svakom cvoru se pridruzuje povezana lista, koja sadrzi sve grane susedne tom cvoru. Graf je predstavljen vektorom lista. Svaki elemenat vektora 1 Tekst www.matf.bg.ac.yu/~jtomasevic, i Milana i primeri preuzeti od Jelene Tomasevic, Bankovica www.matf.bg.ac.yu/~milan zasnovano na materijalu Algoritmi, Miodrag Zivkovic i http://www.matf.bg.ac.yu/~filip

209

Milena Vujoˇsevi´c–Janiˇci´c

Grafovi

sadrzi ime (indeks) cvora i pokazivac na njegovu listu cvorova. Ovakav pristup stedi memoriju, a omogucava i e kasno dodavanje novih cvorova i grana. Osnovni problem koji se javlja kod grafova jeste kako polazeci od nekog cvora, kretanjem kroz puteva grafa posetiti sve cvorove. Ovakav postupak se naziva obilazak grafa. Postoje dva osnovna algoritma za obilazak grafa: pretraga u dubinu (DFS, skracenica od depth- rst-search) i pretraga u sirinu (BFS, skracenica od breadthrst-search). Kod DFS algoritma, obilazak zapocinje iz proizvoljnog zadatog cvora r koji se naziva koren pretrage u dubinu. Koren se oznacava kao posecen. Zatim se bira proizvoljan neoznacen cvor r1, susedan sa r, pa se iz cvora r1 rekurzivno startuje pretraga u dubinu. Iz nekog nivoa rekurzije izlazi se kad se naid¯e na cvor v kome su svi susedi vec oznaceni. Primer 12.1 Primer reprezentovanja grafa preko matrice povezanosti. U programu se unosi neorijentisan graf i DFS algoritmom se utvrd¯uju ˇcvrovi koji su dostiˇzni iz ˇcvora 0. #include #include 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; isledeci) if (!posecen[sused->broj]) poseti(sused->broj);

} void oslobodi_graf(cvor_liste* graf[], int broj_cvorova) { int i; for(i=0; i ::= < cifra >  < cifra >  ( 2’ ) < spisak imena > ::= < ime >  , < ime >  . Za proizvoljnu konstrukciju (nisku)  možemo pisati  =   . . . To znači da zapis  predstavlja drugi način zapisa za iteraciju *= A* jednoelementnog skupa A=, i pišemo *=  . . . =  . . . . Zato umesto  možemo pisati i *. Takođe zapis  za proizvoljnu iteraciju možemo upotrebiti umesto . . . . Pomoću gornjuh i donjih indeksa možemo zadati maksimalni i minimalni broj ponavljanja neke konstrukcije. Sreću se i sledeći zapisi za konačne liste elemenata koji se ponavljaju pomoću tri tačke: e1 , e2 , . . . , en

12

Ivan P. Stanimirović

Uvod u programiranje

0. 4. 1. SINTAKSNI DIJAGRAMI Sintaksni dijagrami su jedna vrsta grafičkog metajezika jer koriste grafičke simbole za opis sintakse jezika. Zato su oni pregledniji i čitljiviji od Bekusove notacije. Ovaj jedinstven metajezik je ekvivalentan sa Bekusovom normalnom formom. I proširene Bekusove normalne forme se lako realizuju pomoću sintaksnih dijagrama. Sintaksni dijagram je imenovani orijentisani graf sa jednim ulazom i jednim izlazom sa čvorovima grafa koji predstavljaju terminalne ili neterminalne simbole. Svaki prolaz kroz sintaksni dijagram od ulaza prema izlazu generiše jednu sintaksno pravilnu konstrukciju jezika. Linije (sa strelicama) koje spajaju čvorove grafa realizuju metaoperacije nadovezivanja i razdvajanja (izbora) na prirodan način. Za terminalni simbol t koristimo grafički simbol kružnog ili elipsoidnog oblika. Za neterminalne simbole predviđen je pravougaoni simbol.. Metaoperacija iz Bekusove notacije i proširenja (upotreba zagrada) se realizuju na jedostavan način. U nastavku navodimo ekvivalentne realizacije operacija u oba metajezika. 1. Spajanje (nadovezivanje) : 12. . . n



1

12. . . n 2

n

2. Razdvajanje (izbor) : 1  2  . . .  n

1



2

n 3. Iteracija (pozitivna iteracija) : * =  . . .    . . . 4. Opciona konstrukcija : =  5. Ograničeno ponavljanje :    =  . . .  . . .     = . . .  . . . 

1. Osnovni elementi programskih jezika 1.1. Pseudojezik kao opšti model viših programskih jezika Za definiciju pseudojezika kao opšteg modela viših programskih jezika neophodno je obuhvatiti sledeće četiri fundamentalne komponente:

13

Ivan P. Stanimirović

Uvod u programiranje

(1) Tipovi i strukture podataka koje pseudojezik podržava: - statički skalarni tipovi, - statički strukturirani tipovi, - dinamički tipovi sa promenljivom veličinom, - dinamički tipovi sa promenljivom strukturom. (2) Osnovne kontrolne strukture koje se primenjuju u pseudojeziku: - sekvenca, - selekcije, - ciklusi, - skokovi. (3) Operacije ulaza i izlaza podataka: - ulaz/izlaz podataka za ulazno-izlazne uređaje i datoteke, - konverzija tekstualnog i binarnog formata podataka. (4) Tehnike modularizacije programa: - nezavisne funkcije i procedure, - interne funkcije i procedure, - rekurzivne funkcije i procedure. Razni tipovi podataka neophodni su u programskim jezicima da bi podražavali razne tipove objekata koje srećemo u matematičkim modelima. Podaci mogu biti skalarni ili strukturirani. Podatak je strukturiran ukoliko se sastoji od više komponenti koje se nalaze u precizno definisanom odnosu. Primer strukturiranog objekta je pravougaona matrica realnih brojeva kod koje svaki element predstavlja komponentu koja se nalazi u određenom odnosu sa ostalim komponentama. Podatak je skalaran ukoliko se ne sastoji od jednostavnijih komponenti. Jedan od skalarnih tipova podataka na svim programskim jezicima je celobrojni tip podataka. Lako je uočiti da za svaki tip podataka postoje operacije koje za njih važe, a ne važe za druge tipove podataka. Tako je na primer inverzija matrice operacija koja se ne primenjuje na celobrojne skalare, na isti način kao što se operacija celobrojnog deljenja dva skalara ne može primeniti na matrice. Osnovne kontrolne strukture su izuzetno važna komponenta svakog programskog jezika. Pomoću njih se određuje redosled izvršavanja operacija koje računar obavlja. Osnovni tipovi kontrolnih struktura su sekvenca kod koje se instrukcije obavljaju onim redosledom kojim su napisane u programu, i strukture sa grananjima, ciklusima i skokovima. Grananja se koriste za izbor jednog od više paralelnih programskih segmenata, ciklusi realizuju ponavljanje nekog niza instrukcija, a skokovi služe za kontrolisani izlaz iz petlji, iz programa (kraj rada) i za realizaciju pojedinih osnovnih kontrolnih struktura. Da bi se omogućilo komuniciranje računara sa spoljnim okruženjem potrebne su instrukcije ulaza i izlaza podataka. Pri tome podaci mogu da se učitavaju sa tastature ili iz datoteka i da se prenose na ekran, štampač, ili u datoteke. Pod datotekom (file) podrazumevamo osnovnu organizaciju podataka koja obuhvata proizvoljan broj manjih jednoobrazno strukturiranih celina koje se nazivaju zapisi. Svaki zapis sadrži podatke o jednoj jedinici posmatranja. Na primer, zapis može sadržati strukturirane podatke o motornom vozilu (tip, godina proizvodnje, snaga motora, vlasnik, itd.), dok skup većeg broja ovakvih zapisa predstavlja datoteku motornih vozila. Alfanumerički podaci (slova, cifre i specijalni znaci) se čuvaju u tekst datotekama, a numerički podaci mogu da se čuvaju bilo u tekstualnoj bilo u kompaktnoj binarnoj formi. Za sve podatke postoji format kojim se određuje njihova struktura, a pri nekim prenosima podataka može se automatski vršiti i konverzija formata. Ako bi programi bili formirani kao neprekidni nizovi instrukcija, onda bi kod velikih programa bilo nemoguće razumevanje načina rada celine, i na taj naćin bilo bl znatno otežano održavanje programa. Zbog toga su mehanizmi modularizacije programa od vitalnog značaja za uspeh nekog programskog jezika. Pod modularizacijom programa podrazumevamo razbijanje programa na manje (najčešće nezavisne) celine kod kojih su precizno definisani ulazni i izlazni podaci, kao i postupak kojim se na

14

Ivan P. Stanimirović

Uvod u programiranje

osnovu ulaznih podataka dobijaju izlazni podaci. U ovu grupu spadaju nerekurzivne i rekurzivne funkcije i procedure. Prilikom definicije jezika polazi se od osnovnog skupa znakova, azbuke jezika koja sadrži sve završne simbole (terminalne simbole) jezika. Nad azbukom jezika definišu se ostali elementi jezika, konstante, rezervisane reči, identifikatori od kojih se dalje grade druge složene sintaksne kategorije kao što su na primer opisi, upravljačke naredbe i slično.

1.2. Azbuka jezika Azbuka jezika predstavlja osnovni skup simbola (znakova) od kojih se grade sve sintaksne kategorije jezika. Broj znakova azbuke se obično razlikuje od jezika do jezika i kreće se od najmanje 48 do 90 znakova. Azbuka programskog jezika obično obuhvata skup velikih i malih slova engleske abecede, skup dekadnih cifara i određeni skup specijalnih znakova. Dok je kod starijih programskih jezika bilo uobičajeno da se koriste samo velika slova (na primer FORTRAN IV), danas se skoro redovno dozvoljava i ravnopravno korišćenje malih slova abecede. Azbuke programskih jezika se najviše razlikuju po skupu specijalnih znakova koje obuhvataju. Narednih nekoliko primera azbuka programskih jezika to ilustruje. Azbuka jezika C velika slova: A | B| C | D| E| F|G|H|I|J|K|L|M|N|0| P|Q| R|S|T|U|V|W|X|Y|Z cifre: 0 |1 |2|3|4|5|6|7|8|9 specijalni znaci: + | - | * | / |= | ( | ) | { \ } \ [ | ] \ < | > | ' | " | ! | # | \ | % | & | | | | _ | ^ | ¸ | ~ \ , \ ; | : | ? znak blanko mala slova: a | b | c | d | e| f|g|h| i| j| k|l|m|n|o|p|q|r|s| t|u| v|w|x|y|z

Danas se obično skup specijalnih znakova azbuke programskog jezika standardizuje i svodi na skup znakova međunarodnog standardnog koda ISO7 (ASCII kod). b | ! | " | $ | % | & | ' | ( | ) | * | + | , | - | . | / | : | ; | < | = | > | ? | @ | [ | ] | \ | ^ ili _ | ` | { | } | ~

Češto se pored osnovnog skupa specijalnih znakova koriste i složeni simboli, obično dvoznaci, kao na primer: | ** | >= | | =< | < | := | -> | /* | */ | U nekim programskim jezicima (FORTRAN), zbog nedovoljnog broja odgovarajućih znakova umesto specijalnih znakova koriste se posebne simboličke oznake .EQ., .NE., .GT., .GE., .LT., .LE., kojima se označavaju relacije jednako, različiti, veće, veće ili jednako, manje i manje ili jednako, redom.

1.3. Identifikatori i rezervisane reči Identifikatori su uvedene reči kojima se imenuju konstante, promenljive, potprogrami, programski moduli, klase, tipovi podataka i slično. U svim programskim jezicima postoji slična konvencija za pisanje identifikatora. Identifikatori su nizovi koji se obično sastoje od slova i cifara i obavezno započinju slovom. Ovo ograničenje omogućava jednostavniju implementaciju leksičkog analizatora i razdvajanje identifikatora od drugih sintaksnih kategorija (numeričkih i znakovnih konstanti na primer). U nekim jezicima dozvoljeno je da se i neki specijalni znaci pojave u okviru identifikatora. Najčešće je to crtica za povezivanje "_" kojom se postiže povezivanje više reči u jedan identifikator. Na primer, u jeziku C crtica za povezivanje se može koristiti na isti način kao i slova, što znači da identifikator može da započne ovim znakom. Slede primeri nekih identifikatora: ALFA

A

B1223

Max_vrednost

PrimerPrograma

U jeziku C velika i mala slova se razlikuju. Programski jezik PASCAL ne razlikuje velika i mala slova.

15

Ivan P. Stanimirović

Uvod u programiranje

Dobra je programerska praksa da identfikatori predstavljaju mnemoničke skraćenice. Nizovi znakova azbuke koji u programu imaju određeni smisao nazivaju se lekseme. Leksema može da bude i samo jedan znak. Reč jezika čije je značenje utvrđeno pravilima tog jezika naziva se rezervisana reč. Rezervisane reči mogu da budu zabranjene, kada se ne mogu koristiti kao identifikatori u programu. Takav je slučaj u programskom jeziku C. Međutim, i u jezicima u kojima je to dozvoljeno ne preporučuje se korišćenje ključnih reči kao identifikatora jer može da smanji preglednost programa, a u nekim slučajevima da dovede i do ozbiljnih grešaka u programu. Poznat je, na primer, slučaj greške sa DO naredbom koji je doveo do pada letilice iz satelitskog programa Geminy 19. U programu za upravljanje letilicom stajala je DO naredba napisana kao: DO 10 I = 1.10

umesto ispravnog koda DO 10 I = 1,10.

Greška pri prevođenju međutim nije otkrivena jer je leksički analizator ovu liniju kôda protumačio kao naredbu dodeljivanja DO10I

= 1.10

u kojoj se promenljivoj D010I dodeljuje vrednost 1.10. Greška je otkrivena tek u fazi izvršavanja programa kada je prouzrokovala pad letilice. Rezervisane reči jezika C: auto, break, case, char, const, continue, default, do, double, else, enum, extern, float, for, goto, if, int, long, register, return, short, signed, sizeof, static, struct, switch, typedef, union, unsigned, void, voatile, while.

U poređenju sa ostalim programskim jezicima, C poseduje mali broj službenih reči.

1.4. Konstante Bilo koji niz znakova u programu, posmatran nezavisno od njegovog logičkog značenja, nad kojim se mogu izvršavati određena dejstva (operacije) naziva se podatak. Deo podatka nad kojim se mogu izvršavati elementarne operacije naziva se element podatka. Elementu podatka u matematici približno odgovara pojam skalarne veličine. Podatak je uređeni niz znakova kojim se izražava vrednost određene veličine. Veličina koja u toku izvršavanja programa uvek ima samo jednu vrednost, koja se ne može menjati, naziva se konstanta. Kao oznaka konstante koristi se ona sama. U nekim programskim jezicima (Pascal, Ada, C) postoji mogućnost imenovanja konstante. Konstantama se dodeljuju imena koja se u programu koriste umesto njih. Na taj način nastaju simboličke konstante. Tipovi konstanti koje se mogu koristiti u određenom programskom jeziku određeni su tipovima podataka koje taj jezik predviđa. U jeziku C se koristi više vrsta konstanti i to su: - celobrojne konstante; 2, 3, +5, -123 - karakteri: 'a', 'b', ... ; - stringovi: sekvence karaktera između navodnika. Slede neki primeri različitih vrsta konstanti: Celobrojne dekadne konstante: 1; 50; 153; +55; -55 Realne konstante u fiksnom zarezu: 3.14; 3.0; -0.314; -.314; +.314 Realne konstante u pokretnom zarezu: 3.14E0; -0.314E1; -.314E+0; +.314E-2

Realne konstante dvostruke tačnosti: 16

(FORTRAN, Pascal, Ada, C)

Ivan P. Stanimirović

Uvod u programiranje

Oktalne konstante (C): 0567; 0753; 0104 Heksadecimalne konstante (C): 0X1F;

0XAA;

0X11

Long konstante (C): 123L; 527L;321L;+147L Logičke konstante: true; false

(Pascal, Ada)

.TRUE.; .FALSE.

(FORTRAN)

Znakovne konstante: 'A'; 'B'

(Pascal, Ada, C)

String konstante: "Beograd"; "Alfa 1"

(C)

String konstante: ‘Beograd’; ‘Alfa 1’

(Pascal)

Simboličke konstante: ZERO; ZEROS; SPACE

(COBOL)

Pi, E -4/5

(MATHEMATICA) (MATHEMATICA)

Racionalni brojevi: 2/3;

1.5. Promenljive Veličine čije se vrednosti menjaju u toku izvršavanja programa nazivaju se promenljive. Promenljivoj se u programu dodeljuje ime, i u svakom trenutku ona je definisana svojom vrednošću. Kažemo da je svaka promenljiva u programu povezana sa tri pojma: imenom - identifikatorom promenljive. referencom - pokazivačem koji određuje mesto promenljive u memoriji i vrednošću - podatkom nad kojim se izvršavaju operacije. Veza između imena, reference i vrednosti promenljive može se predstaviti sledećim dijagramom:

ime referenca vrednost Ovaj dijagram u slučaju imenovane konstante dobija oblik:

ime

vrednost

Na primer naredbu dodeljivanja X:=3.141592 čitamo: X dobija vrednost 3.141592, pri čemu imamo u vidu da je X simboličko ime memorijske lokacije gde se u toku izvršavanja programa pamti vrednost 3.141592. Pri tome je potrebno imati na umu sledeće pojmove: • vrednost 3.141592, koja predstavlja vrednost promenljive X, • adresu memorijske lokacije u kojoj se pamti vrednost 3.141592, • ime promenljive X, identifikator koji se u datom programu koristi kao ime promenljive koja ima datu brojnu vrednost.

1.6. Komentari U svim programskim jezicima postoji mogućnost proširenja izvršnog koda programa komentarima kojima se kod dodatno pojašnjava. Ovi komentari se u toku prevođenja programa ignoršu od strane kompilatora i ne ulaze u sastav izvršnog koda koji se generiše prevođenjem programa. Međutim komentari su veoma važan deo programa kojim se podešava njegova dokumentarnost, i bitno utiču na efikasnost analize programa. Konvencija za zapisivanje komentara se razlikuje od jezika od jezika. Slede primeri komentara u nekim programskim jezicima: /* Ovo je primer komentara u jeziku C */ // Kratak C++ komentar u okviru jednog reda

17

Ivan P. Stanimirović

Uvod u programiranje

1.7. Struktura programa Globalna struktura programa zavisi od toga da li jezik zahteva deklaraciju tipova promenljivih kao i da li su u jeziku zastupljeni stariji koncepti sa implicitnim definicijama tipova promenljivih ili je zastupljen noviji koncept sa eksplicitnim definicijama.

1.7.1. Struktura programa u C Programi pisani u C jeziku imaju strukturu bloka, ali se za ograničavanje bloka koriste vitičaste zagrade umesto zagrada begin i end. Zastupljen je takođe stariji koncept bloka, poznat iz jezika Algol 60 i PL/1, gde se opisi elemenata koji pripadaju jednom bloku nalaze unutar zagrada { i }, za razliku od novijeg koncepta koji se koristi u jeziku Pascal, kod kojih opisi elemenata bloka prethode zagradi begin kojom se otvara blok. Svaki blok u C-u takođe može da sadrži opise promenljivih i funkcija. C program se može nalaziti u jednom ili više fajlova. Samo jedan od tih fajlova (glavni programski modul) sadrži funkciju main kojom započinje izvršenje programa. U programu se mogu koristiti funkcije iz standardne biblioteke. U tom slučaju se može navesti direktiva pretprocesora oblika #include .

česta je direktiva oblika #include kojom se uključuju funkcije iz fajla stdio.h (standard input/output header). Glavni program, main(), takođe predstavlja jednu od funkcija. Opis svake funkcije se sastoji iz zaglavlja i tela funkcije. U ovom slučaju, zaglavlje funkcije je najjednostavnije, i sadrži samo ime funkcije i zagrade (). Iza zaglavlja se navodi telo funkcije koje se nalazi između zagrada { i }. Između ovih zagrada se nalaze operatori koji obrazuju telo funkcije. Svaki prost operator se završava znakom ';' a složeni operator se piše između zagrada { i }. U jeziku C sve promenljive moraju da se deklarišu. Opšti oblik jednostavnog programa je: void main() {

}

2. TIPOVI PODATAKA Jedan od najznačajnijih pojmova u okviru programskih jezika je pojam tipa podataka. Atribut tipa određuje skup vrednosti koje se mogu dodeljivati promenljivima, format predstavljanja ovih vrednosti u memoriji računara, skup osnovnih operacija koje se nad njima mogu izvršavati i veze sa drugim tipovima podataka. Na primer, promenljivoj koja pripada celobrojnom tipu mogu se kao vrednosti dodeljivati samo celi brojevi iz određenog skupa. Nad tako definisanim podacima mogu se izvršavati osnovne aritmetičke operacije sabiranja, oduzimanja, mnozenja, deljenja, stepenovanja, kao i neke specifične operacije kao što je određivanje vrednosti jednog broja po modulu drugog. Koncept tipova podataka prisutan je, na neki način, već kod simboličkih asemblerskih jezika gde se za definiciju tipa koriste implicitne definicije preko skupa specijalnih znakova kojima se određuju podaci različitog tipa. Neka je zadat skup T sačinjen od n proizvoljnih apstraktnih objekata: T:={v1,...,vn}, n>1. Ukoliko su svi objekti istorodni, u smislu da se u okviru nekog programskog jezika na njih može primenjivati jedan određeni skup operatora, onda se T naziva tip podataka, Ako za promenljive x i y uvek važi xT i yT onda su one tipa T. To u okviru programa formalno definišemo iskazom

18

Ivan P. Stanimirović

Uvod u programiranje

DEFINE x, y : T.

Vrednost je bilo koji entitet kojim se može manipulisati u programu. Vrednosti se mogu evaluirati, zapamtiti u memoriji, uzimati kao argumenti, vraćati kao rezultati funkcija, i tako dalje. Različiti programski jezici podržavaju različite tipove vrednosti. Jedna od klasifikacija osnovnih tipova podataka prikazana je na slici.

Tipovi podataka su podeljeni u dve osnovne grupe: statički tipovi i dinamički tipovi. Pod statičkim tipovima (ili tipovima sa statičkom strukturom podrazumevamo tipove podataka kod kojih je unapred i fiksno definisana unutrašnja struktura svakog podataka, a veličina (t.j. memorijska zapremina) fiksno se definiše pre (ili u vreme) izvršavanja programa koji koristi podatke statičkog tipa. Statički tipovi podataka obuhvataju skalarne i strukturirane podatke. Pod skalarnim tipovima podrazumevamo najprostije tipove podataka čije su vrednosti skalari, odnosno takve veličine koje se tretiraju kao elementarne celine i za koje nema potrebe da se dalje razlažu na komponente. U tom smislu realne i kompleksne brojeve tretiramo kao skalare u svim programskim jezicima koji ih tretiraju kao elementarne celine (neki jezici nemaju tu mogućnost pa se tada kompleksni brojevi tretiraju kao struktura koja se razlaže na realni i imaginarni deo; na sličan način mogao bi se realni broj razložiti na normalizovanu mantisu i eksponent). Pod strukturiranim tipovima podataka podrazumevamo sve složene tipove podataka koji se realizuju povezivanjem nekih elementarnih podataka u precizno definisanu strukturu. U ulozi elementarnih podataka obično se pojavljuju skalari ili neke jednostavnije strukture. Kompozitna vrednost ili struktura podataka (data structure) jeste vrednost koja je komponovana iz jednostavnijih vrednosti. Kompozitni tip jeste tip čije su vrednosti kompozitne. Programski jezici podržavaju mnoštvo kompozitnih vrednosti: strukture, slogove, nizove, algebarske tipove, objekte, unije, stringove, liste, stabla, sekvuencijalni fajlovi, direktni fajlovi, relacije, itd. Skalarni tipovi podataka mogu biti linearno uređeni ili linearno neuređeni. Linearno uređeni tipovi podataka su tipovi kod kojih se vrednosti osnovnog skupa T preslikavaju na jedan interval iz niza celih brojeva, t.j. za svaki podatak xT zna se redni broj podatka. Stoga svaki podatak izuzev početnog ima svog prethodnika u nizu, i slično tome, svaki podatak izuzev krajnjeg ima svog sledbenika u nizu.

19

Ivan P. Stanimirović

Uvod u programiranje

Pod dinamičkim tipovima podataka podrazumevamo tipove podataka kod kojih se veličina i/ili struktura podataka slobodno menja u toku obrade. Kod dinamičkih tipova sa promenljivom veličinom podrazumevamo da je struktura podataka fiksna, ali se njihova veličina dinamićki menja tokom obrade tako da se saglasno tome dinamički menjaju i memorijski zahtevi. Na primer, dopisivanjem novih zapisa u sekvencijalnu datoteku veličina datoteke raste uz neizmenjenu strukturu. Kod dinamičkih tipova sa promenljivom strukturom unapred je fiksno definisan jedino princip po kome se formira struktura podataka dok se sama konkretna struktura i količina podataka u memoriji slobodno dinamički menjaju.

Statička i dinamička tipizacija Pre izvršenja bilo koje operacije, moraju se proveriti tipovi operanada da bi se sprečila greška u tipu podataka. Na primer, pre izvršenja operacije množenja dva cela broja, oba operanda moraju biti proverena da bi se osiguralo da oni budu celi brojevi. Slično, pre izvršenja neke and ili or operacije, oba operanda moraju biti proverena da bi se osiguralo da oni budu tipa boolean. Pre izvršenja neke operacije indeksiranja niza, tip operanda mora da bude array (a ne neka prosta vrednost ili slog). Ovakva provera se naziva proverom tipa (type checks). Provera tipa mora da bude izvršena pre izvršenja operacije. Međutim, postoji izvestan stepen slobode u vremenu provere: provera tipa se može izvršiti ili u vremenu kompilacije (compile-time) ili u vremenu izvršenja programa (at run-time). Ova mogućnost leži u osnovi važne klasifikacije programskih jezika na statički tipizirane (statically typed) i dinamički tipizirane (dynamically typed). U nekom statički tipiziranom jeziku, svaka varijabla i svaki izraz imaju fiksni tip (koji je ili eksplicitno postavljen od strane programera ili izveden od strane kompajlera). Svi operandi moraju biti proverenog tipa (type-checked) u vremenu kompilovanja programa (at compile-time). U dinamički tipiziranim jezicima, vrednosti imaju fiksne tipove, ali varijable i izrazi nemaju fiksne tipove. Svaki put kada se neki operand izračunava, on može da proizvede vrednost različitog tipa. Prema tome, operandi moraju imati proveren tip posle njihovog izračunavanja, ali pre izvršenja neke operacije, u vremenu izvršenja programa (at run-time). Mnogi jezici visokog nivoa su statički tipizirani. SMALLTALK, LISP, PROLOG, PERL, i PYTHON jesu primeri dinamički tipiziranih jezika. S druge strane, moderni funkcionalni jezici (kao ML, HASKELL, MATHEMATICA) omogućavaju da sve vrednosti, uključujući i funkcije, imaju sličnu obradu. Izbor između statičke i dinamičke tipizacije je pragmatičan: • Statička tipizacija je efikasnija. Dinamička tipizacija zahteva (verovatno ponovljenu) proveru tipova u vremenu izvršenja programa (run-time type checks), što usporava izvršenje programa. Statička tipizacija zahteva jedino proveru tipa u vremenu kompilacije programa (compile-time type checks), čija je cena minimalna (i izvršava se jedanput). Osim toga, dinamička tipizacija primorava sve vrednosti da budu etiketirane (tagged) (da bi se omogućila provera u vreme izvršenja), a ovakvo označavanje povećava upotrebu memorijskog prostora. Statička tipizacija ne zahteva ovakvo označavanje. • Statička tipizacija je sigurnija: kompajler može da proveri kada program sadrži greške u tipovima. Dinamička tipizacija ne omogućava ovakvu sigurnost. • Dinamička tipizacija obezbeđuje veliku fleksibilnost, što je neophodno za neke aplikacije u kojima tipovi podataka nisu unapred poznati. U praksi veća sigurnost i efikasnost statičke tipizacije imaju prevagu nad većom fleksibilnošću dinamičke tipizacije u velikoj većini aplikacija. Većina programskih jezika je statički tipizirana. Na osnovu toga kako je postavljen koncept tipova podataka, programski jezici mogu da se svrstaju u dve grupe: na programske jezike sa slabim tipovima podataka i na jezike sa jakim tipovima podataka.

20

Ivan P. Stanimirović

Uvod u programiranje

2.1. Koncept jakih tipova podataka Koncept jakih tipova podataka obuhvata nekoliko osnovnih principa: • Tip podataka određuju sledeći elementi:

-

skup vrednosti, format registrovanja podataka, skup operacija koje se nad podacima mogu izvršavati, skup funkcija za uspostavljanje veza sa drugim tipovima podataka.

• Sve definicije tipa moraju da budu javne, eksplicitne. Nisu dozvoljene implicitne definicije tipova. • • • •

Objektu se dodeljuje samo jedan tip. Dozvoljeno je dodeljivanje vrednosti samo odgovarajućeg tipa. Dozvoljene su samo operacije obuhvaćene tipom. Tip je zatvoren u odnosu na skup operacija koji obuhvata. Ove operacije se mogu primenjivati samo nad operandima istog tipa. Mešoviti izrazi nisu dozvoljeni. • Dodeljivanje vrednosti raznorodnih tipova moguće je samo uz javnu upotrebu funkcija za transformaciju tipa. Koncept jakih tipova povećava pouzdanost, dokumentarnost i jasnoću programa. Kako se zahteva eksplicitna upotreba operacija za transformaciju tipa, onda je nedvosmisleno jasno da je određena transformacija na nekom mestu namerna i potrebna. Ovaj koncept omogućava da se informacija o tipu može koristiti u fazi kompilovanja programa i na taj način postaje faktor pouzdanosti programa.

2.2. Koncept slabih tipova U slučaju jezika sa slabim tipovima podataka informacija o tipu promenljive koristi se, i korektna je samo na mašinskom nivou, u fazi izvršenja programa. Ovako postavljen koncept podrazumeva sledeće mogućnosti: (1) Operacija koja se od strane kompilatora prihvati kao korektna, na nivou izvornog koda programa, može da bude potpuno nekorektna. Razmotrimo sledeći primer: char c; c = 4;

Promenljiva c definisana je da pripada tipu char, što podrazumeva da joj se kao vrednosti dodeljuju znaci kao podaci. Međutim umesto korektnog dodeljivanja c = '4', promenljivoj c je dodeljena vrednost broja 4 kao konstante celobrojnog tipa. Kod jezika sa slabim tipovima podataka kompilator ne otkriva ovu grešku i informaciju o tipu koristi samo na mašinskom nivou kada promenljivoj c dodeljuje vrednost jednog bajta memorijske lokacije u kojoj je zapisana konstanta 4. Očigledno je da ovako postavljen koncept tipova može da dovede do veoma ozbiljnih grešaka u izvršavanju programa koje se ne otkrivaju u fazi kompilovanja programa. (2) Koncept slabih tipova podrazumeva određeni automatizam u transformaciji tipova podataka u slučaju kada se elementi različitih tipova nalaze u jednom izrazu čija se vrednost dodeljuje promenljivoj određenog tipa. Razmotrimo sledeći primer: float x, y; int i, j, k; i = x; k = x-j ;

Promenljive x i y su realne (tipa float), a i, j i k celobrojne (tipa int). Naredbom i=x; vrši se dodeljivanje vrednosti tipa float promenljivoj celobrojnog tipa. Kod jezika sa slabim tipovima ovo dodeljivanje je dozvoljeno iako se pri tome x svodi na drugi format i pravi greška u predstavljanju njegove vrednosti. Kod ovako postavljenog koncepta tipova, da bi se napisao korektan program

21

Ivan P. Stanimirović

Uvod u programiranje

potrebno je tačno poznavati mehanizme transformacije tipova. U drugoj naredbi iz primera (k = x-j;) od broja x koji je tipa float treba oduzeti broj j, tipa int i rezultat operacije dodeliti promenljivoj tipa int. Da bi smo bili sigurni u korektnost rezultata potrebno je da znamo redosled transformacija koje se pri tome izvršavaju, odnosno da li se prvo x prevodi u int i onda izvršava oduzimanje u skupu celih brojeva i vrednost rezultata dodeljuje promenljivoj tipa int ili se j prevodi u tip float, izvršava oduzimanje u skupu realnih brojeva, a zatim rezultat prevođi u tip int i dodeljuje promenljivoj k. Koncept slabih tipova podataka dopušta puno slobode kod zapisivanja izraza u naredbama dodeljivanja; međutim cena te slobode je nejasan program sa skrivenim transformacijama, bez mogućnosti kontrole i korišćenja informacije o tipu u fazi kompilovanja programa.

2.3. Ekvivalentnost tipova Šta ekvivalentnost tipova označava zavisi od programskog jezika. (Sledeća diskusija podrazumeva da je jezik statički tipiziran.) Jedna moguća definicija ekvivalentnosti tipova jeste strukturna ekvivalentnost (structural equivalence): T1 ≡ T2 ako i samo ako T1 i T2 imaju isti skup vrednosti. Strukturna ekvivalentnost se tako naziva zato što se ona može proveriti poređenjem struktura tipova T1 i T2. (Nepotrebno je, a u opštem slučaju i nemoguće, da se prebroje sve vrednosti ovih tipova.) Kada kompilator jezika sa jakim tipovima podataka treba da obradi naredbu dodeljivanja oblika x := izraz

on vrši dodeljivanje samo u slučaju ekvivalentnosti tipa promenljive sa leve strane dodeljivanja i rezultata izraza na desnoj strani naredbe, osim u slučaju kada je na desnoj strani celobrojni izraz a na levoj strani promenljiva nekog realnog tipa. Eksplicitnom ekvivalentnošću tipova postiže se veća pouzdanost jezika. U ovom slučaju nisu potrebne posebne procedure po kojima bi se ispitivala strukturna ekvivalentnost. Međutim, kada je potrebno vrednost promenljive ili izraza dodeliti promenljivoj koja mu ne odgovara po tipu ovaj koncept zahteva korišćenje funkcija za transformisanje tipova.

2.4. Elementarni tipovi podataka U okviru svakog programskog jezika, sistem tipova podataka zasniva se na skupu osnovnih tipova podataka nad kojima se dalje definišu izvedeni tipovi, podtipovi, strukturni tipovi i specifični apstraktni tipovi podataka. Skup osnovnih tipova podataka se obično svodi na tipove podataka za rad sa elementarnim numeričkim podacima (celi i realni brojevi), znakovnim podacima (pojedinačni znaci ASCII koda) i logičkim vrednostima (true i false).

2.4.1. Celobrojni tipovi (Integer ili int) Celobrojni tip podataka (tip INTEGER) Podaci celobrojnog tipa pripadaju jednom lntervalu celih brojeva koji obuhvata pozitivne i negativne brojeve i koji se obično označava na sledeći način: T := {minint, minint+1, ... , -1, 0, 1, ,.. , maxint-1, maxint } .

Ovde je najmanji broj označen sa minint, a najveći sa maxint (od engl. maximum integer i minimum integer) pri čemu ove veličine nisu fiksne već zavise od implementacije i prema tome variraju od računara do računara. Od nekoliko načina binarnog kodiranja celih brojeva izdvojićemo metod potpunog komplementa koji se najčešće sreće u praksi. Kod ovog postupka brojevi se binarno predstavljaju pomoću sledeće nbitne reči: n-1 S

3

22

2

1

0

Ivan P. Stanimirović

Uvod u programiranje

Bit najstarijeg razreda označen sa S (sign) predstavlja predznak broja. Ako je S=0 onda je je broj nenegativan, a ako je S=1 onda je broj negativan. Nula se označava sa nulama u svim bitovima: 0000 0000 0000 0000 Obično postoji osnovni (standardni) tip INTEGER ili int, koji se zavisno od realizacije odnosi na određeni opseg celih brojeva. Nekad je to opseg koji odgovara formatu jedne polureči ili formatu jedne reči. U odnosu na ovaj osnovni celobrojni tip češto postoji mogućnost definisanja i drugih celobrojnih tipova koji se odnose na neki kraći ili prošireni format.

2.4.2. Realni tip (float ili real) Promenljive ovih tipova uzimaju za svoje vrednosti podskupove skupa realnih brojeva. U programskim jezicima postoji više vrsta podataka realnog tipa, koji se razlikuju po tačnosti predstavljanja podataka. Realni tip podataka obuhvata jedan konačan podskup racionalnih brojeva ograničene veličine i tačnosti. Naravno, može se odmah postaviti pitanje zbog čega se koriste nazivi "realni tip" i "realni broj" za nešto što u opštem slučaju nije u stanju da obuhvati ni iracionalne brojeve ni beskonačne periodične racionalne brojeve. Ipak, to je terminologija koja je prihvaćena u praksi i opravdava se time što je (float ili REAL) tip podataka koji se najbliže približava pojmu realnog broja. Sa druge strane, lako je razumeti da su sve memorijske lokacije konačne dužine, pa stoga i brojni podaci koji se u njih smeštaju moraju biti konačne dužine i tako po prirodi stvari otpadaju iracionalni brojevi. Potrebe prakse nisu na ovaj način ni malo ugrožene jer se dovoljna tačnost rezultata može postići i sa veličinama konačne dužine. Tip float najčešće obuhvata brojne vrednosti iz sledećih podintervala brojne ose: -maxreal

-minreal

0 minreal

maxreal

Ovde minreal označava najmanju apsolutnu vrednost veću od nule, koja se može predstaviti na raćunaru, a maxreal predstavlja najveću apsolutnu vrednost koja se može predstaviti na raćunaru. Realni brojevi iz intervala (-minreal, minreal) se zaokružuju i prikazuju kao 0, realni brojevi iz intervala (-, -maxreal) i (maxreal, +) ne mogu se predstaviti u memoriji računara, a - i + se kodiraju specijalnim kodovima.

2.4.3. Logički tip podataka Logički tipovi podataka postoje kao osnovni tipovi podataka u svim novijim jezicima. Obično nose naziv LOGICAL (FORTRAN) ili BOOLEAN (Pascal, Ada). Obuhvataju samo dve vrednosti true i false, nad kojima su definisane osnovne logičke operaclje not, and, or i xor. Takođe, važi i uređenost skupa vrednosti ovog tipa tako da je false < true. Izrazi u kojima se primenjuju logičke promenljive i konstante nazivaju se logički izrazi. Ako se logičke konstante označavaju sa false i true onda podrazumevamo da svi izrazi moraju biti sačinjeni striktno od logičkih veličina. Ha primer, izraz z := (x>0) and ( (y=1) or (y0) + 2*(y>0) + (z>0) , koji je takođe besmislen kod logičkih konstanti false i true, u slučaju numeričkog kodiranja T={0,1} redovno ima i smisla i upotrebnu vrednost kao generator veličina 0,1,2,3,4,5,6,7.

2.4.4. Znakovni tipovi Korisnik računara komunicira sa računarom preko ulaznih i izlaznih uređaja i tu je bitno da se pojavljuju podaci u formi koja je čitljiva za čoveka. To znači da se komuniciranje obavlja pomoću

23

Ivan P. Stanimirović

Uvod u programiranje

znakova iz određene azbuke koja redovno obuhvata abecedno poređana velika i mala slova, cifre, specijalne znake i kontrolne znake. Pod specijalnim znacima se podrazumevaju svi oni znaci koji se javljaju na tastaturama i mogu odštampati, ali nisu ni slova ni cifre (na tastaturi sa kojom je pisan ovaj tekst specijalni znaci su ! # S % & *()_-+=:"; '?,/.). Pod kontrolnim znacima podrazumevaju se znaci koji se ne mogu odštampati (ili prikazati na ekranu terminala), već služe za upravljanje radom ulazno/izlaznog uređaja (na primer štampača). U ovu grupu spadaju specijalni znaci za pomeranje papira, znak koji izaziva zvučni signal na terminalu i drugi). Da bi se znaci razlikovali od simboličkih naziva promenljivih obično se umeću između apostrofa (na nekim programskim jezicima umesto apostrofa se koriste znaci navoda). Tako se, na primer, podrazumeva da A predstavlja simbolički naziv promenljive, dok 'A' predstavlja binarno kodirano prvo slovo abecede. U praksi se primenjuje nekoliko metoda za binarno kodiranje znakova. Najpoznatiji metod je američki standard ASCII (American Standard Code for Information Interchange). Ovim smenama su neki manje važni znaci iz skupa ASCII znakova zamenjeni sa specifičnim jugoslovenskim znacima (na pr., umesto ASCII znakova \ | { } [ ] ~ @ i ^ u srpskoj varijanti se pojavljuju znaci Đ đ š ć š Ć č ž Ž i Č). Neki terminali imaju mogućnost podesivog izbora skupa znakova tako da korisnik može po potrebi izabratl američku ili jugoslovensku varijantu skupa ASCII znakova. Prva 32 znaka u ASCII skupu su kontrolni znaci. Nekl od njlh imaju jedinstvenu interpretaciju kod svih uređaja, a kod nekih interpretacija se razlikuje od uređaja do uređaja. Ovde pominjemo sledeće: (Bell) = zvučni signal (Line Feed) = prelazak u naredni red (Form Feed) = prelazak na narednu stranu (Carriage /Return) = povratak na početak reda (Escape) = prelazak u komandni režim

BEL LF FF CR ESC

Skup ASCII znakova je baziran na sedmobitnim znacima, pa prema tome obuhvata ukupno 128 znakova. Kako se radi o linearno uređenom skupu svaki znak ima svoj redni broj i ti brojevi su u opsegu od 0 do 127. Funkcija koja za svaki znak daje njegov redni broj (ordinal number) označava se sa ord. Argument ove funkcije je tipa CHARACTER, a vrednost funkcije je tipa CARDINAL. Za slučaj ASCII skupa imamo da važi sledeće: ord('0') = 48, ord('A') = 65. ord('a')=97. Inverzna funkcija funkciji ord, koja od rednog broja znaka formira znak, (character) je funkcija chr: chr(48) = '0', chr(65) = 'A', chr(97) = 'a' . Ako je c promenljiva tipa CHARACTER, a n promenljiva tipa CARDINAL onda važi chr(ord(c) ) = c , ord(chr(n) ) = n .

2.5. Tipovi podataka u jeziku C Programski jezik C se svrstava u jezike sa slabim tipovima podataka iako su eksplicitne definicije tipa obavezne. Mogu se koristiti samo unapred definisani tipovi podataka uz mogućnost da im se daju pogodna korisnička imena i na taj način poveća dokumentarnost programa. Mehanizam tipa je opšti i odnosi se i na funkcije i na promenljive. Tipovi podataka u jeziku C mogu se globalno podeliti na osnovne i složene (struktuirane). Osnovni tipovi podataka su celobrojni (int), realni (float), znakovni (char), nabrojivi (enumerated) i prazan (void). Ovi tipovi podataka se koriste u građenju složenih tipova (nizova, struktura, unija, itd.). U sledećoj tabeli su prikazani osnovni tipovi podataka ovog jezika sa napomenom o njihovoj uobičajenoj primeni. Tip

Memorija u bajtovima

Opseg

Namena

char

1

0 do 255

ASCII skup znakova i mali brojevi

24

Ivan P. Stanimirović

Uvod u programiranje

signed char enum

1 2

-128 do 127 -32.768 do 32.767

ASCII skup znakova i veoma mali brojevi Uređeni skup vrednosti

int

4

-2.147.483.648 do 2.147.483.647

Mali brojevi, kontrola petlji

unsigned int short int

4 2

0 do 4.294.967.295 -32.768 do 32.767

Veliki brojevi i petlje Mali brojevi, kontrola petlji

long

4

-2.147.483.648 do 2.147.483.647

Veliki brojevi

unsigned long

4

0 do 4.294.967.295

Astronomska rastojanja

float

3,4*10

double

4 8

long double

10

-38

do 3,4*10

-308

-4932

1,7*10 3,4*10

38

Naučne aplikačije (tačnost na 6 decimala)

do 1,7*10

308

Naučne aplikačije (tačnost na 16 decimala)

do 3,4*10

4932

Naučne aplikačije (tačnost na 19 decimala)

U C-u postoji skup operatora za rad sa binarnim sadržajima koji su prikazani u tabeli koja sledi. Značenje

Operator && || ^ >> =40) { printf("%c",ds); printf("%c",ps); n-=40; } while(n>= 10) { printf("%c",ds); n-=10; } if(n==9) { printf("%c",je); printf("%c",ds); n-=9; } if(n>= 5) {printf("%c",pe); n-=5; } if(n==4) {printf("%c",je); printf("%c",pe); n-=4; } while(n>=1) { printf("%c",je); n--; } }

Primer. Najbliži prost broj datom prirodnom broju. #include int prost(int n) { int i=2,prosti=1; while ((i "); scanf("%f%f%f", &a,&b,&c); rezultati(a,b,c,&x1, &x2, &tip); } } float diskriminanta(float a, float b, float c) { return(b*b-4.0*a*c); } void solution(float a,float b,float c, float *x,float *y,int *t) { float d; d=diskriminanta(a,b,c); if(d>0){*t=0; *x=(-b+sqrt(d))/(2*a);*y=(-b-sqrt(d))/(2*a); } else if(d==0) { *t=1; *x=*y=-b/(2*a);} else { *t=2; *x=-b/(2*a); *y=sqrt(-d)/(2*a); } } void rezultati(float a,float b,float c, float *x, float *y, int *t) {solution(a,b,c,x,y,t); if(*t==0) {printf("Resenja su realna i razlicita\n"); printf("x1=%f x2=%f\n",*x,*y);} else if(*t==1) {printf("Resenja su realna i jednaka\n"); printf("x1=x2=%f\n",*x);} else { printf("Resenja su konjugovano-kompleksna\n"); printf("x1 = %f + i* %f\n",*x,*y); printf("x2 = %f -i* %f\n",*x,*y); } }

Primer. Napisati proceduru za deljenje dva cela broja na proizvoljan broj decimala. Deljenik, delilac i broj decimala zadati u posebnoj proceduri. #include #include void unos(int *, int*, int *); void deljenje(int, int, int); main() { int n,i,bdec, brojilac, imenilac; clrscr(); printf("Koliko puta? "); scanf("%d", &n); for(i=1; i='0' && *s='0' && *s0) printf("\n %f + i*%f\n",p.prvi,p.drugi); else printf("\n %f – i*%f\n",p.prvi,fabs(p.drugi)); } struct kom zbir(struct kom p,struct kom q) /* U C-jeziku vrednost funkcije MOZE DA BUDE struct tipa */ { struct kom priv; priv.prvi=p.prvi+q.prvi;priv.drugi=p.drugi+q.drugi; return(priv); } struct kom proiz(struct kom p,struct kom q) { struct kom priv; priv.prvi=p.prvi*q.prvi-p.drugi*q.drugi; priv.drugi=p.prvi*q.drugi+p.drugi*q.prvi; return(priv); }

Ovaj zadatak se može uraditi uz korišćenje typedef izraza. Kompleksni brojevi se reprezentuju novim tipom kompl. #include #include typedef struct { float prvi,drugi; } kompl; void upis(kompl *p); void ispis(kompl p); kompl zbir(kompl p,kompl q); kompl proiz(kompl p,kompl q); void main() { kompl a,b,c; upis(&a); upis(&b); c=zbir(a,b); ispis(c); c=proiz(a,b); ispis(c); } void upis(kompl *p) { float x,y; printf(" Daj dva broja p->prvi=x;p->drugi=y; }

:\n");

scanf("%f%f",&x,&y);

void ispis(kompl p) { printf("\n %f *i+ %f\n",p.prvi,p.drugi); } kompl zbir(kompl p,kompl q) { kompl priv; priv.prvi=p.prvi+q.prvi;priv.drugi=p.drugi+q.drugi; return(priv); } kompl proiz(kompl p,kompl q) { kompl priv; priv.prvi=p.prvi*q.prvi-p.drugi*q.drugi; priv.drugi=p.prvi*q.drugi+p.drugi*q.prvi; return(priv); }

Primer. Tačka u ravni je definisana kao strukturom Tacka koja sadrži dva broja tipa double. Krug je definisan strukturom koja sadrži centar tipa Tacka i poluprečnik tipa double. Napisati funkciju koja ispitatuje da li se dva kruga seku. U funkciji main je dato n krugova. Sa ulaza se učitava n, a u

194

Ivan P. Stanimirović

Uvod u programiranje

narednih n redova su zadati centar i poluprečnik svakog kruga. Ispisati redne brojeve svaka dva kruga koji se seku. #include #include typedef struct {double x,y; } Tacka; typedef struct { Tacka O; double R; } Krug; int intersect(Krug a, Krug b) { double d; d=sqrt(pow(a.O.x-b.O.x,2)+ pow(a.O.y-b.O.y,2)); return (dfabs(a.R-b.R)); } void main() { int i,j,n; Krug a[100]; scanf("%d",&n); for(i=1;iR); } int Intersect(Krug K1, Krug K2) { double d=sqrt(pow(K1.O.x-K2.O.x,2)+ pow(K1.O.y-K2.O.y,2)); return ((dfabs(K1.R-K2.R))); } void main() { int i,j,n; Krug K[5]; scanf("%d",&n); for(i=0; im=x.n; y->n=x.n; y->bn=x.bn; for(i=0; isp[i].i=x.sp[i].j; y->sp[i].j=x.sp[i].i; y->sp[i].aij=x.sp[i].aij; } } void pisimatricu(matrica x) { int i; for(i=0; inaziv); gets(p->naziv); } void PisiPraznik(praznik p) { puts(p.naziv); printf("\n%d.%d.%d.\n\n",p.d.dan,p.d.mesec,p.d.godina); } void main() { int i,n; praznik a[30]; scanf("%d",&n); printf("Zadati podatke za %d praznika:\n",n); for(i=0;ip1.n)p.a[i]=p2.a[i]; else p.a[i]=p1.a[i]+p2.a[i]; while(p.n>=0 && p.a[p.n]==0)p.n--; return p; } Poli razlika(Poli p1, Poli p2) { Poli p; int i; p.n=(p1.n>p2.n)? p1.n : p2.n; for(i=0;ip2.n)p.a[i]=p1.a[i]; else if(i>p1.n)p.a[i]=-p2.a[i]; else p.a[i]=p1.a[i]-p2.a[i]; while(p.n>=0 && p.a[p.n]==0)p.n--; return p; } Poli proizvod(Poli p1, Poli p2) { Poli p; int i,j; p.n=p1.n+p2.n; for(i=0;i=0; i--){ printf("%.2lf", p.a[i]); if(i>0)putchar(','); putchar('}'); } void main() { Poli p1, p2, p3; while(1) { p1=citaj(); p2=citaj(); printf("P1 = "); pisi(p1); putchar('\n'); printf("P2 = "); pisi(p2); putchar('\n'); printf("P1+P2 = "); p3=zbir(p1,p2); pisi(p3); putchar('\n'); printf("P1-P2 = "); pisi(razlika(p1,p2)); putchar('\n');

164

}

Ivan P. Stanimirović

Uvod u programiranje

printf("P1*P2 = "); pisi(proizvod(p1,p2)); putchar('\n'); printf("P1/P2 = "); pisi(kolicnik(p1,p2, &p3)); putchar('\n'); printf("P1%%P2 = "); pisi(p3); putchar('\n'); putchar('\n'); } }

6.7.4. Unije Unija je tip podataka koji može da sadrži (u raznim situacijama) objekte različitih tipova. Unije određuju način manipulisanja različitim tipovima podataka u istoj memorijskoj oblasti. Svrha njihovog postojanja je ušteda memorije. One su analogne slogovima promenljivog tipa u Pascal-u, a sintaksa im je zasnovana na strukturama jezika C. Svrha unije je da postoji samo jedna promenljiva koja može da sadrži bilo koju od vrednosti različitih tipova. Unije se razlikuju od struktura po tome što elementi unije koriste isti memorijski prostor, i što se u svakom trenutku koristi samo jedan element unije. Primer. Pretpostavimo da u tabeli simbola nekog kompajlera, konstanta može da bude int, float ili char. Najveća ušteda memorije se postiže ako je vrednost ma kog elementa tabele memorisana na istom mestu, bez obzira na tip. U našem slučaju je potrebna sledeća deklaracija union tag { int ival; float fval; char *sval; }; union tag u;

Ekvivalentna deklaracija je data kako sledi: union tag { int ival; float fval; char *sval; } u;

Promenljivoj koja je deklarisana kao unija prevodilac dodeljuje memorijski prostor dovoljan za memorisanje onog člana unije koji zauzima najveći memorijski prostor. Programer mora da vodi računa o tome koji tip se trenutno memoriše u uniji, jer je važeći tip onaj koji je najskorije memorisan. Članovi unije se selektuju identično članovima strukture: unija.clan

ili pointer_unije->clan.

Ako je int tip tekućeg člana unije u, njegova vrednost se prikazuje izrazom printf("%d\n",u.ival);

ako je aktivni član tipa float pišemo printf("%f\n",u.fval);

a ako je tipa char printf("%s\n",u.sval);

Elementima strukturne promenljive u vrednosti se mogu dodeliti, na primer, iskazima u.ival=189; ili u.fval=0.3756; ili u.sval="string";

Primer. Jednostavna unija koja sadrži jedno celobrojno i jedno realno polje. void main() { union { int i; float f; } x;

165

Ivan P. Stanimirović

Uvod u programiranje

x.i=123; printf("x.i=%d x.f=%f\n",x.i,x.f); x.f=12.803; printf("x.i=%d x.f=%f\n",x.i,x.f); }

Unije mogu da se pojavljuju u strukturama i nizovima. Kombinacijom strukture i unije grade se promenljive strukture. Primer. Tablica simbola struct { char*name; int flags; int utype; union { int ival; float fval; char *sval; } u; } symtab[100];

Sada možemo pristupiti članu ival i-tog elementa tablice simbola pomoću izraza symtab[i].u.ival;

Prvi znak stringa sval i-tog elementa tablice simbola se dobija jednim od sledeća dva ekvivalentna izraza: *symtab[i].u.sval; symtab[i].u.sval[0];

Unija se može inicijalizovati samo pomoću vrednosti koja odgovara tipu njenog prvog člana. Stoga se unija opisana u prethodnom primeru može inicijalizovati samo pomoću celobrojne vrednosti. Primer. Osnovne geometrijske figure se mogu okarakterisati sledećom strukturom: struct figura { float povrsina, obim; int tip; union { float r; float a[2]; float b[3]; } geom_fig; } fig;

/* Zajednicki elementi */ /* Oznaka aktivnog elementa */ /* Poluprecnik kruga */ /* Duzine strana pravougaonika */ /* Duzine strana trougla */

Primer. Napisati program za izračunavanje rastojanja između dve tačke u ravni, pri čemu postoji mogućnost da svaka od tačaka bude definisana u Dekartovom ili polarnom koordinatnom sistemu. Obuhvaćeni su svi slučajevi: kada su obe tačke zadate Dekartovim koordinatama, jedna od tačaka Dekartovim, a druga polarnim koordinatama i slučaj kada su obe tačke definisane polarnim koordinatama. #include #include typedef enum { Dekartove, Polarne }TipKor; typedef struct { double x,y; } Dek; typedef struct { double r,fi; } Pol; typedef struct { TipKor tip; union { Dek DK; Pol PK; };

166

Ivan P. Stanimirović

Uvod u programiranje

} koordinate; void ucitaj(koordinate *Q) { int ch; double u,v; printf("Tip koordinata? 1 za Dekartove, 2 za polarne: "); scanf("%d", &ch); printf("Koordinate? "); switch (ch) { case 1: Q->tip=Dekartove; scanf("%lf%lf", &u, &v); Q->DK.x=u; Q->DK.y=v; break; case 2: Q->tip=Dekartove; scanf("%lf%ld", &u,&v); Q->PK.r=u, Q->PK.fi=v; break; } } void main() { koordinate A,B; double d; ucitaj(&A); ucitaj(&B); switch(A.tip) { case Dekartove: switch(B.tip) { case Dekartove: d=sqrt(pow(A.DK.x-B.DK.x,2)+pow(A.DK.y-B.DK.y,2)); break; case Polarne: d=sqrt(pow(A.DK.x-B.PK.r*cos(B.PK.fi),2)+ pow(A.DK.y-B.PK.r*sin(B.PK.fi),2)); break; } break; case Polarne: switch(B.tip) {case Dekartove: d=sqrt(pow(A.PK.r*cos(A.PK.fi)-B.DK.x,2)+ pow(A.PK.r*sin(A.PK.fi)-B.DK.y,2)); break; case Polarne: d=sqrt(pow(A.PK.r*cos(A.PK.fi)-B.PK.r*cos(B.PK.fi),2)+ pow(A.PK.r*sin(A.PK.fi)-B.PK.r*sin(B.PK.fi),2)); break; } break; } printf("%.4lf\n",d); }

Primer. Napisati program za sumiranje površina n geometrijskih figura. Figura može biti krug (određen dužinom poluprečnika), pravougaonik (određen dužinama susednih stranica) ili trougao (određen dužinama svojih stranica). #include #include void main() { struct pravougaonik { double a,b;}; struct trougao { double a,b,c; };

167

Ivan P. Stanimirović

Uvod u programiranje

struct figura { int tip; union { double r; struct pravougaonik p; struct trougao t; }; } figure[50]; int i,n; double pov=0,s; scanf("%d",&n); for(i=0; i ili = od vredriosti z-a\n" ); printf ( "Vrednost x-a je %d\n", x ); printf ( "Vrednost z-a je %d\n", z ); } } Prepisati rezultat:

Zadatak 4: obrasca:

Napisati program na C jeziku za rešavanje kvadratne jednačine x2 + x - 2 = 0 korišćenjem

x1, 2 

 b  b 2  4ac 2a

i štampanje vrednosti za x1 i x2. #include #include void main() { double a,b,c; double x1,x2,pom; a = 1; b = 1; c = -2; pom = sqrt(b*b-4*a*c); x1 = ( (-b) + pom ) / 2 * a; x2 = ( (-b) - pom ) / 2 * a; printf("Vrednost prvog korena x1 = %f\n",x1); printf("Vrednost drugog korena x2 = %f\n",x2); } Opisati rezultate rada programa :

2

Visoka tehnička škola strukovnih studija u Nišu Laboratorijske vežbe iz Programskih jezika I Zadatak 5: Napisati isti program ali u opštem obliku kada se veličine a, b i c učitavaju sa tastature. Napisati izvorni kod programa:

Zadatak 6: Napisati program na C jeziku za izračunavanje ukamaćene vrednosti ako je poznata mesečna kamatna stopa, period oročavanja u mesecima i iznos glavnice. Štampati dobijene rezultate i proveriti matematičku postavku programa. Napomena: Koristiti pow funkciju iz biblioteke math.h koja izračunava x na y. Kamata se računa kao glavnica * (1+kamata/100)period. #include #include void main( ) { double stopa, period, glavnica; printf( "Unesite mesecnu kamatnu stopu: " ) ; scanf( "%lf", &stopa ) ; /* ulaz u pokretnom zarezu */ /* konverzija u procente */ stopa = stopa / 100.0; printf( "Unesite glavnicu: " ); scanf( "%lf", &glavnica ); printf( "Unesite vreme orocavanja u mesecima: " ); scanf( "%lf", &period ) ; printf( "Ukamacena vrednost je = %.2f\n", glavnica * pow( (1.0+stopa), period)); } Proveriti izlaz iz programa za sledeće vrednosti: Kamatna stopa: 1, glvnica: 100, oročavnje: 3 meseca. Koliko će iznositi krajnja vrednost? Zadatak 7: Objasnite zašto su ovi izrazi netačni: a) #include ;

b) int main(int arg1){

return arg1-1

}

c) int 2nd value=10;

d) #define MESSAGE = "Happy new year!" printf("%s",MESSAGE); 3

Visoka tehnička škola strukovnih studija u Nišu Laboratorijske vežbe iz Programskih jezika I

VEŽBA 4 – for petlja, while petlja, makroi For petlja ulaz

izraz 1 izraz 3 T izraz 2

naredba

N izlaz Upravljačke informacije kod for petlje, za razliku od while petlje, su smeštene na jednom mestu i to na vrhu iteracije. Petlja for je definisana, što znači da unapred znamo koliko puta će petlja da se izvrši, a njena osnovna konstrukcija je:  Inicijalizacija – inicijalizacije vrednosti brojača. i = 0  Uslov – na primer upoređenje brojača sa graničnom vrednošću. i < 10  Inkrementirnje – ažuriranje brojača pri svakoj iteraciji. i ++ Prema tome, opšti oblik naredbe for je: for ( izraz1, izraz2, izraz3 ) naredba uz napomenu da neki od izraza u for petlji može ostati prazan i tada se za taj izraz smatra da ima vrednost true.. Zadatak 1 Napisati program na C jeziku za prikaz neparnih brojeva manjih ili jednakih 20 korišćenjem for petlje.

1

Visoka tehnička škola strukovnih studija u Nišu Laboratorijske vežbe iz Programskih jezika I

While petlja Ulaz

T izraz

naredba

N

Izlaz Upravljačku strukturu while karakterišu četiri osobine:  petlja se ponavlja sve dok izraz koji se testira ne postane netačan ( logička 0 );  petlja je sa ulaznim uslovom : odluka o još jednom prolasku kroz telo petlje se donosi pre izvršenja naredbi petlje;  while petlja mora sadr`avati promenu vrednosti izraza tako da postane la`an posle odre|enog broja iteracija, kako petlja ne bi postala beskonačna;  jedna naredba se posmatra kao deo upravljačke strukture while petlje, bilo ona prosta ili složena. Zadatak 2. Napisati program na C jeziku za izračunavanje zbira celih brojeva upotrebom while petlje. Izračunavanje se prekida kada se unese ‘0’; void main() { long sum = 0L; int num; printf("Unesite broj za sumiranje Ili 0 za izlaz\n"); scanf("%ld", &num); while (num != 0) { sum = sum+num; printf("Unesite naredni broj za sabiranj ili 0 za quite :::::::: %d \n ", num); scanf("%ld", &num); } printf("Zbir unetih brojeva je %ld.\n", sum); }

Upisati rezultate rada programa :

2

Visoka tehnička škola strukovnih studija u Nišu Laboratorijske vežbe iz Programskih jezika I Zadatak 3. Napisati program u C jeziku za permutovanje cifara celog broja - while iskaz: (npr. 54321 u 12345 ) Postupak: celi broj 54321 delimo po modulu 10 - rezultat je 1 celi broj 54321 delimo sa 10 - rezultat je 5432 celi broj 5432 delimo po modulu 10 - rezultat je 2 celi broj 5432 delimo sa 10 - rezultat je 543 celi broj 543 delimo po modulu 10 - rezultat je 3 celi broj 543 delimo sa 10 - rezultat je 54 celi broj 54 delimo po modulu 10 - rezultat je 4 celi broj 54 delimo sa 10 - rezultat je 5 celi broj 5 delimo po modulu 10 - rezultat je 5 Petlja se prekida jer broj postaje nula !!! #include void main() { int broj; printf("Ukucajte ceo broj ? "); scanf("%d", &broj ); printf("Permutovani broj je "); while ( broj ) { printf("%d", broj % 10); broj = broj/10; } printf("\n"); } Rezultat za uneti broj 8612 je:

Zadatak 4. Napisati program na C jeziku za određivanje srednje vrednosti n celih pozitivnih brojeva –pomoću while petlje. Testirati program za vrednosti n=4, brojevi su redom 34.345, 234.2876, 45.98, 453.32. Koliki je rezultat?

Zadatak 5. Razmotrite izraz : double ans = 18.0/squared(2+1). Izračunati vrednost izraza ako je funkcija makro squared() definisana kao: A. #define squared(x) x*x

B. #define

squared(x)

(x*x)

C. #define

squared(x)

(x)*(x)

D. #define

squared(x)

((x)*(x))

3

Visoka tehnička škola strukovnih studija u Nišu Laboratorijske vežbe iz Programskih jezika I Zadatak 6. Napisati algoritam na programskom jeziku C koji vrednosti 3 promenljive rotira za k mesta u levo. Vrednosti promenljivih i vrednost promenljive k zadaje korisnik. Prikazati vrednost promenljivih nakon rotacije.

Zadatak 7 Šta je mrtva petlja?Napisati primer mrtve petlje za for i while naredbe i objasniti.

4

Visoka tehnička škola strukovnih studija u Nišu Laboratorijske vežbe iz Programskih jezika I

VEŽBA 5 – do while petlja, switch case Petlja sa ulaznim uslovom do while U slučaju do while petlje obavezno izvršavanje bar jedne iteracije se postiže tako što je upravljački izraz petlje na samom dnu petlje. Tako se uslov petlje proverava nakon izvršenih naredbi tela petlje. Strukturni dijagram toka može se prikazati kao: ulaz

naredba

T

izraz N izlaz

Zadatak 1. Napisati program za izrašunavanje n! primenom do while structure: Rešenje: #include void main() /* Program za izracunavanje faktorijela */ { int i, fak; long n ; i = 1; n = 1; printf("lzracunavanje n!\nUkucajte broj ? "); scanf("%d", &fak); do{ n *= i; i++; } while (i