Professional Documents
Culture Documents
DINAMIČKE
STRUKTURE PODATAKA
Principe na kojima se
zasnivaju dinamičke strukture
podataka najlakše je objasniti na
primjeru dinamičke
implementacije apstraktnog tipa
podataka poznatog pod nazivom
stek ili stog (engl. stack), o
kojem smo već govorili.
Podsjetimo se da je stek
kontejnerska struktura podataka
zasnovana na LIFO (Last In
First Out) principu, odnosno
podatak koji se posljednji
stavlja na stek ujedno je i
podatak koji se prvi skida sa
steka. U osnovi implementacije
gotovo svih dinamičkih
struktura podataka leže
specijalne pomoćne strukture
podataka nazvane čvorovi (engl.
nodes) o kojima smo već
govorili ranije. To su strukture
podataka tipa strukture ili klase
koja obavezno među svojim
atributima sadrži jedan ili više
pokazivača (ili referenci) na
neku instancu iste strukture ili
klase. Na primjer, deklaracija
jednog čvora mogla bi izgledati
recimo ovako (atribut
“element” čuva vrijednost
pohranjenu u čvoru, dok atribut
“veza” predstavlja pokazivač
na neki drugi čvor, odnosno
predstavlja “vezu” sa drugim
čvorom):
struct Cvor {
int element;
Cvor *veza;
};
template <typename
Tip>
class Stek {
struct Cvor {
Tip element;
Cvor *veza;
Cvor(const Tip
&element, Cvor *veza)
:
element(element),
veza(Veza) {}
};
Cvor *gdje_je_vrh;
void Unisti();
void Kopiraj(const
Stek &s);
public:
Stek() :
gdje_je_vrh(0) {}
Stek(const Stek
&s) { Kopiraj(s); }
~Stek()
{ Unisti(); }
Stek &operator
=(const Stek &s);
bool Prazan()
const { return
gdje_je_vrh == 0; }
void Stavi(const
Tip &element) {
gdje_je_vrh =
new Cvor(element,
gdje_je_vrh);
}
Tip Skini();
};
template <typename
Tip>
Tip
Stek<Tip>::Skini() {
if(gdje_je_vrh ==
0) throw "Stek je
prazan!\n";
Tip element =
gdje_je_vrh->element;
Cvor *prethodni =
gdje_je_vrh->veza;
delete
gdje_je_vrh;
gdje_je_vrh =
prethodni;
return element;
}
template <typename
Tip>
void
Stek<Tip>::Unisti() {
while(!Prazan())
Skini();
}
Da nismo implementirali
destruktor, korisnik klase
“Stek” bi bio dužan da uvijek
sam isprazni stek prije nego što
odgovarajuća instanca klase
“Stek” prestane postojati. U
suprotnom bi došlo do curenja
memorije.
Primijetimo da je memorijska
slika steka kreiranog pozivom
metode “Kopiraj”
reflektirana kao slika u ogledalu
memorijske slike izvornog
steka, ali to ništa ne mijenja
njegovu funkcionalnost. U
skladu sa opisanim
objašnjenjem, konstruktor
kopije klase “Stek” mogao bi
izgledati ovako:
template <typename
Tip>
void
Stek<Tip>::Kopiraj(co
nst Stek &s) {
Cvor *prethodni,
*tekuci =
s.gdje_je_vrh;
gdje_je_vrh = 0;
while(tekuci != 0)
{
Cvor *novi = new
Cvor(tekuci->element,
0);
if(gdje_je_vrh
== 0) gdje_je_vrh =
novi;
else prethodni-
>veza = novi;
tekuci = tekuci-
>veza;
prethodni =
novi;
}
}
Kako su realizacije
konstruktora kopije i
preklopljenog operatora dodjele
za klasu “Stek” relativno
složene, često se u literaturi i
praksi susreću razne
implementacije klase “Stek” u
kojima su konstruktor kopije i
preklopljeni operator dodjele
samo deklarirani ali ne i
implementirani, čime se
eksplicitno zabranjuje kopiranje
i međusobno dodjeljivanje
primjeraka ove klase. Ipak, na
taj način onemogućavamo
prenošenje objekata tipa
“Stek” po vrijednosti u
funkcije, i što je još gore,
vraćanje objekata tipa “Stek”
kao rezultata iz funkcije.
Međutim, ukoliko nam to nije
potrebno, takvo polurješenje
može sasvim zadovoljiti. U
svakom slučaju, i takvo
polurješenje je mnogo bolje od
rješenja koje se zasniva na tome
da konstruktor kopije i
preklopljeni operator dodjele ne
deklariramo nikako. U tom
slučaju bi se koristili
podrazumijevani konstrukor
kopije i podrazumijevani
operator dodjele, a već smo
detaljno govorili o tome kakve
probleme ovo uzrokuje kad god
klasa sadrži pokazivače na
dinamički alocirane objekte
(pogotovo u kombinaciji sa
destruktorom).
stack<int> s;
s.push(5);
s.push(2);
s.push(7);
s.push(6);
s.push(3);
while(!s.empty()) {
cout << s.top() <<
endl;
s.pop();
}
s.Stavi(&neki_student
);
Moguće je napraviti
kontejnersku klasu koja se
sintaksno koristi kao da čuva
objekte, a zapravo čuva
pokazivače na njih.
Razmotrimo, na primjer,
sljedeću klasu nazvanu
“IndirektniStek”:
template <typename
Tip>
class Stek {
struct Cvor {
Tip *pok;
Cvor *veza;
Cvor(Tip *pok,
Cvor *veza) :
pok(pok), veza(veza)
{}
};
Cvor *gdje_je_vrh;
public:
IndirektniStek() :
gdje_je_vrh(0) {}
bool Prazan()
const { return
gdje_je_vrh == 0; }
void Stavi(Tip
&element) {
gdje_je_vrh =
new Cvor(&element,
gdje_je_vrh); }
const Tip
&Skini();
};
template <typename
Tip>
const Tip
&IndirektniStek<Tip>:
:Skini() {
if(gdje_je_vrh ==
0) throw "Stek je
prazan!\n";
Tip *pok =
gdje_je_vrh->pok;
Cvor *prethodni =
gdje_je_vrh->veza;
delete
gdje_je_vrh;
gdje_je_vrh =
prethodni;
return *pok;
}
IndirektniStek<Studen
t> s;
...
s.Stavi(neki_student)
;
U prikazanoj implementaciji,
radi jednostavnosti prikaza i
uštede u prostoru, nismo
definirali destruktor,
konstruktor kopije i operator
dodjele (mada treba voditi
računa da su “jednostavnost” i
“ušteda u prostoru” često samo
dobra isprika za pravi argument
– lijenost). Međutim, ovdje je
važno da uočimo izvjesne
detalje. Prvo, formalni
parametar “element” u
metodi “Stavi” deklariran je
kao referenca, i to na
nekonstantni objekat. Prilično je
razumljivo zbog čega je
korištena referenca. Da nismo
koristili referencu, odnosno da
se koristi prenos parametra po
vrijednosti, formalni parametar
“element” bio bi kopija
stvarnog parametra
proslijeđenog u metodu, tako da
bi adresni operator “&” uzeo
adresu te kopije, a ne objekta
koji je prenesen u metodu (u
slučaju prenosa po referenci,
formalni parametar
“element” i stvarni parametar
faktički predstavljaju isti
objekat). Međutim, razmotrimo
zbog čega nismo koristili
referencu na konstantni objekat,
kao što obično radimo. Na prvi
pogled, mogli bismo koristiti
referencu na konstantni objekat,
s obzirom da nigdje ne
mijenjamo sadržaj referiranog
objekta. Prvi, i manje bitan
razlog, je što se pokazivačima
na nekonstantne objekte (kakav
je pokazivač “pok” deklariran
unutar čvora) ne smiju
dodjeljivati adrese konstantnih
objekata, jer bi se onda putem
takvog pokazivača mogao
promijeniti sadržaj konstantnog
objekta (inače, skup pravila
koja određuju koje su dodjele
zabranjene zbog činjenice da bi
njihova primjena mogla dovesti
do promjene sadržaja
konstantnih objekata poznata su
pod nazivom pravila o
konzistenciji konstantnosti).
Ovo bi se moglo lako riješiti
tako što bismo sam pokazivač
“pok” deklarirali kao
pokazivač na konstantan
objekat (što sasvim ima smisla,
jer stek nikada ne mijenja
sadžaj objekta kojeg čuva).
Drugi, mnogo bitniji razlog
zbog kojeg formalni parametar
“element” nije referenca na
konstantni objekat leži u
činjenici da na taj način stvarni
parametar mora biti l-vrijednost
(npr. neka promjenljiva) a ne
proizvoljan izraz, s obzirom da
se referenca na nekonstantni
objekat ne može vezati za
privremene objekte koji nastaju
kao rezultat izračunavanja
izraza. Međutim, zbog čega smo
uveli ovo ograničenje?
Pretpostavimo da smo dozvolili
da se kao stvarni argument
upotrijebi proizvoljan izraz.
Tada bi se na steku pohranila
adresa privremenog objekta koji
sadrži izračunati izraz.
Međutim, ovaj privremeni
objekat se automatski uništava
čim se završi kompletno
izvršavanje izraza unutar kojeg
je stvoren, tako da će se nakon
toga na steku čuvati viseći
pokazivač! Ilustrirajmo ovo na
konkretnom primjeru.
Pretpostavimo da konstruktor
klase “Student” prima kao
parametre ime i prezime
studenta, kao i broj indeksa.
Tada bi, ukoliko bi parametar
metode “Stavi” bila referenca
na konstantni objekat, sljedeća
naredba bila sasvim sintaksno
ispravna:
s.Stavi(Student("Pero
Perić", 1234));
template <typename
Tip>
class Stek {
struct Cvor {
Tip &ref;
Cvor *veza;
Cvor(Tip &ref,
Cvor *veza) :
ref(ref), veza(veza)
{}
};
Cvor *gdje_je_vrh;
public:
IndirektniStek() :
gdje_je_vrh(0) {}
bool Prazan()
const { return
gdje_je_vrh == 0; }
void Stavi(Tip
&element) {
gdje_je_vrh =
new Cvor(element,
gdje_je_vrh); }
const Tip
&Skini();
};
template <typename
Tip>
const Tip
&IndirektniStek<Tip>:
:Skini() {
if(gdje_je_vrh ==
0) throw "Stek je
prazan!\n";
Tip &ref =
gdje_je_vrh->ref;
Cvor *prethodni =
gdje_je_vrh->veza;
delete
gdje_je_vrh;
gdje_je_vrh =
prethodni;
return ref;
}
Verzije klase
“IndirektniStek” koje
čuvaju pokazivače odnosno
reference na objekte
funkcionalno su potpuno
identične, i na programeru je da
izabere stil koji mu se više
sviđa. Međutim, ove dvije
verzije ipak nisu funkionalno
identične sa klasom “Stek” čiji
čvorovi čuvaju čitave kopije
objekata koji se stavljaju na
stek. Naime, pretpostavimo da
smo “gurnuli nekog studenta na
stek” naredbom poput
s.Stavi(neki_student)
;
queue<int> q;
q.push(5);
q.push(2);
q.push(7);
q.push(6);
q.push(3);
while(!q.empty()) {
cout << q.top() <<
endl;
q.pop();
}
ispisati
5, 2, 7, na6 iekran
3 slijedu brojeva
(svaki novom
redu).
dohvata Naime,
element metoda “top”
kojiposljednji,
je prvi
stavljen
kao u
u slučajured (a ne
steka). Također,
metoda
koji je prvi stavljen u red.element
“pop” uklanja
Stek strukture
važne i red spadaju u veoma
podataka, koje
se mnogo
algoritmima. koriste
Međutim, u raznim
obje ove
strukture
ograničene, podataka su prilično
s obziromelemenata
da je u
njima
moguće dodavanje
samo na kraj, a
uzimanje
moguće podataka
samo sa je (jednog
kraja također
ili
li drugog,
se o zavisnoiliod redu).
steku toga radiU
primjenama
stekovi u kojima
ili redovi, se
oni setakokoriste
koriste
upravo
to nena takav način,
predstavlja da
bitno
ograničenje
treba da (npr. ukoliko
pristupamo nam
elementu
koji
smo nije posljednji
stavili u nekuelement koji
strukturu
podataka,
nam ustvari toi zapravo
nije govori
potreban da
stek,
nego
podataka nekakoja to druga struktura
omogućava). S
druge
potrebne strane,i čestofleksibilnije
su nam
dinamičke
Stek i red, strukture
na način podataka.
kako su
implementirani
predstavljaju specijalan ovdje,
slučaj
dinamičkih
koje se nazivajustruktura podataka
jednostruko
povezane
linked liste
lists), s (engl.
obzirom single
da se
sastoje
je svaki od niza
čvor čvorova
povezan saod kojih
jednim
susjednim
napraviti i čvorom.
znatno Moguće je
fleksibilniju
jednostruko
omogućava povezanu
dodavanje listu,novih
koja
elemenata
unalisti, kao na
i proizvoljno
pristup mjesto
elementima
U proizvoljnoj
nastavku ćemo poziciji
razviti ujednu
listi.
verziju
pruža generičke
upravo klaseovakve
koja
mogućnosti.
klase, koju ćemo Deklaracija
nazvati ove
prosto
“Lista”, izgleda ovako:
template <typename
Tip>
class Lista {
struct Cvor {
Tip element;
Cvor *veza;
Cvor(const Tip
&element, Cvor *veza)
:
element(element),
veza(veza) {}
};
Cvor *pocetak,
*kraj, *aktuelni;
int velicina,
aktuelni_indeks;
public:
Lista() :
pocetak(0), kraj(0),
velicina(0) {}
Lista(const Lista
&lista);
~Lista();
Lista &operator
=(const Lista
&lista);
bool Prazna()
const { return
velicina == 0; }
int Duzina() const
{ return velicina; }
void
DodajNaPocetak(const
Tip &element);
void
DodajNaKraj(const Tip
&element);
void Umetni(int
indeks, const Tip
&element);
void Izbaci(int
indeks);
Tip &operator []
(int indeks);
void
Ponavljaj(void(*akcij
a)(Tip &));
};
Ostavimo
implementacione zadetalje sada
po
strani,
sadrži i pogledajmo
interfejs samo šta
klase.konstruktor,
Na prvom
mjestu,
konstruktor tu su
kopije, destruktor i
preklopljeni
koje operator
treba klasa
da posjeduje dodjele,
svaka
pristojna
dinamičku koja koristi
Dalje, tu sualokaciju memorije.
metode “Prazna”
iispituju
“Duzina”,
da li koje
je respektivno
lista prazna,
odnosno
elemenata koliko
(klase lista
“Stek” sadržii
“Red”
“Duzina”, nisu posjedovale
zbog činjenice metodu
da u
primjenama
stekovi u
i redovi kojima
ne se koriste
treba znati
koliko
odnosno u nekom
red sadrži trenutku stek
elemenata).
Metode “DodajNaPocetak”
inovi“DodajNaKraj” dodaju
odnosno element
kraj liste, nadok početak
metodau
“Umetni”
listu umeće
na “Izbaci” element
proizvoljnuizbacuje poziciju.
Metoda
liste element na navedenoj iz
poziciji.
operator Tu je i preklopljeni
indeksiranja “[]” koji
će omogućiti
elementima liste dapristupamo
proizvoljnim nao
isti način
elementima kao da
niza. se radi
Konačno,
predviđena
metoda je
“Ponavljaj” i interesantna
koja
obavlja
pokazivačem akciju
na definiranu
funkciju koji
joj
nad jesvakim
prenesen kao parametar
elementom liste.
Način upotrebe
objašnjen kasnije. ove metode biće
Pređimo sada na
implementacione detalje. Rad
klase opisuju atributi
“pocetak”, “kraj” i
“velicina” koji respektivno
sadrže pokazivače na početak
odnosno kraj liste, kao i broj
elemenata u listi, te atribute
“aktuelni” i
“aktuelni_indeks”, čija će
uloga biti uskoro razjašnjena, a
pomoću kojih se postižu
izvjesni trikovi koji bitno
povećavaju efikasnost klase
(vidjećemo da se u načelu sve
moglo postići samo pomoću
atributa “pocetak”, ali uz
gubitak efikasnosti). Veze
između čvorova ćemo realizirati
tako da svaki čvor sadrži
pokazivač na sljedeći čvor.
Stoga bi se dodavanje elementa
na početak liste principijelno
moglo realizirati na isti način
kao i stavljanje elemenata na
stek. Tako bismo i radili da je
atribut “pocetak” jedini
atribut koji opisuje rad liste.
Međutim, ukoliko dodajemo
čvor na početak prazne liste,
kreirani čvor je ujedno i prvi i
posljednji, tako da je u tom
slučaju potrebno također
inicijalizirati pokazivač “kraj”
da pokazuje na isti element
(istom prilikom je potrebno
inicijalizirati i atribute
“aktuelni” i
“aktuelni_indeks”, čija će
uloga postati jasna kasnije).
Također je pri svakom
dodavanju novog čvora
potrebno povećati atribut
“velicina” za 1. Stoga bi
metoda “DodajNaPocetak”
mogla izgledati ovako:
template <typename
Tip>
void
Lista<Tip>::DodajNaPo
cetak(const Tip
&element) {
pocetak = new
Cvor(element,
pocetak);
if(velicina == 0)
{
aktuelni = kraj
= pocetak;
aktuelni_indeks
= 0;
}
else
aktuelni_indeks++;
velicina++;
}
S obzirom da u atributu
“velicina” vodimou
evidenciju
listi, o broju
implementacija čvorova
metodei
“Duzina”
izvedena je trivijalna,
je unutar je deklaracije
klase.
metodu Interesantno
“Duzina” damogli
smo
napisati i bez uvođenja
“velicina”, tako štoatributa
bismo
prosto
“pocetak”krenuli od
i listi, pokazivača
prebrojali koliko
čvorova
sve dok ima
ne u
dođemo prateći
do čvoraveze
iza
kojeg ne slijedi
Konkretnije, niti jedan čvor.
metodu
“Duzina”
ovako: mogli smo napisati
template <typename
Tip>
int
Lista<Tip>::Duzina()
{
int brojac(0);
for(Cvor *pok =
pocetak; pok != 0;
pok = pok->Veza)
brojac++;
return Brojac;
}
template <typename
Tip>
void
Lista<Tip>::DodajNaKr
aj(const Tip
&element) {
if(velicina == 0)
DodajNaPocetak(elemen
t);
else {
kraj = kraj-
>veza = new
Cvor(element, 0);
velicina++;
}
}
Po
efikasnosti,cijenu
mogli smosmanjene
proći i
bez
kraj pokazivača “kraj”,
listeod početka jer
moguće i pronaćije
polazeći
veze između čvorova. prateći
U tom
slučaju, implementacija
“DodajNaKraj” metode
mogla bi
izgledati ovako:
template <typename
Tip>
void
Lista::DodajNaKraj(co
nst Tip &element) {
if(velicina == 0)
DodajNaPocetak(elemen
t);
else {
Cvor *pok;
for(pok =
pocetak; pok->veza !=
0; pok = pok->veza);
pok->veza = new
Cvor(element, 0);
velicina++;
}
}
Uz pretpostavku
metodu “Izbaci” da uklanja
koja imamo
element
trivijalan.izDovoljno
liste, destruktor
je izbacivatije
jedan
dok po
se jedan
lista element
ne iz liste,
isprazni.
Najlakše
prvi je stalno
element (tj. izbacivati
element sa
indeksom
konvenciju 0 ukoliko
da usvojimo
indeksiranje
elemenata počinjenizova):
u slučaju običnih od nule, kao
template <typename
Tip>
Lista<Tip>::~Lista()
{
while(!Prazna())
Izbaci(0);
}
S obzirom da
metodu imamo napisanu
“DodajNaKraj”,
konstruktor
preklopljenog kopije i izvedba
operatora dodjele
su
Potrebnotakođer
je proći jednostavni.
kroz cijelu
izvornu
na listu i
kraj odredišnesvaki čvor
liste dodati
(upotrebno
slučaju
dodjele, prethodno je
obrisati postojeću izvornu listu):
template <typename
Tip>
Lista<Tip>::Lista(co
nst Lista &lista) :
pocetak(0), kraj(0),
velicina(0) {
for(Cvor *pok =
lista.pocetak; pok !=
0; pok = pok->veza)
DodajNaKraj(pok-
>element);
}
template <typename
Tip>
Lista<Tip>
&Lista<Tip>::operator
=(const Lista<Tip>
&lista) {
if(&lista ==
this) return *this;
while(!Prazna())
Izbaci(0);
for(Cvor *pok =
lista.pocetak; pok !=
0; pok = pok->veza)
DodajNaKraj(pok-
>element);
return *this;
}
Implementaciju
funkcije za operatorske
preklapanje
operatora
ćemo indeksiranja
objasniti “[]”
prije
implementacije
“Umetni” imetode
“Izbaci”, metoda
jer na
će
se ove dvije
implementaciju oslanjati
ove operatorske
funkcije.
da se Najjednostavniji
realizira ova način
operatorska
funkcija
početka je
liste da krećući
prateći od
veze
pronađemo
rednim brojem,čvor sa
i da iz zadanim
njega
očitamo
Primijetimo traženi
da je element.
potrebno
vratiti
nađeni kao rezultat
element, referencu
jer će na
jedino
tako biti moguće
indeksiranja nađe da se rezultat
sa kaolijeve
strane
je operatora
moguće u dodjele,
slučaju što
običnih
nizova:
template <typename
Tip>
Tip
&Lista<Tip>::operator
[](int indeks) {
if(indeks < 0 ||
indeks >= velicina)
throw "Indeks izvan
opsega!\n";
Cvor *pok =
pocetak;
for(int i = 0; i <
indeks; i++) pok =
pok->veza;
return pok-
>element;
}
Međutim, moguće je postići
mnogo veću efikasnost.
Zamislimo, na primjer, da smo
prvo trebali da pristupimo 50-
tom elementu liste, a nakon toga
52-gom elementu. Uz prethodnu
implementaciju, prilikom
pristupa 52-gom elementu
ponovo bismo potragu za
čvorom koji sadrži ovaj element
krenuli od početka liste, iako se
ovaj čvor nalazi svega dva
čvora ispred 50-tog čvora kojem
smo maločas pristupali! Ovim
se nameće ideja da je pri
svakom pristupu nekom čvoru
pametno čuvati njegovu adresu i
redni broj u nekim atributima. U
slučaju da je indeks čvora
kojem želimo da pristupimo
veći od indeksa posljednjeg
čvora kojem smo pristupali (tj.
ako se traženi čvor nalazi ispred
posljednjeg čvora kojem smo
pristupali), potragu za
njegovom lokacijom možemo
započeti upravo od posljednjeg
čvora kojem smo pristupali, a
ne od početka liste. Međutim, u
slučaju da je indeks čvora
kojem želimo da pristupamo
manji od indeksa posljednjeg
čvora kojem smo pristupali,
potraga se mora započeti od
početka liste (jer nema načina
da na osnovu adrese čvora
saznamo adresu njegovog
prethodnika). Stoga su uvedeni
već pomenuti atributi
“aktuelni” i
“aktuelni_indeks” koji
redom čuvaju adresu i redni
broj posljednjeg čvora kojem se
pristupalo. Uvođenjem ovih
atributa, implementacija
operatorske funkcije za operator
“[]” mogla bi izgledati ovako:
template <typename
Tip>
Tip
&Lista<Tip>::operator
[](int indeks) {
if(indeks < 0 ||
indeks >= velicina)
throw "Indeks izvan
opsega!\n";
if(indeks <
aktuelni_indeks) {
aktuelni_indeks
= 0; aktuelni =
pocetak;
}
for(;
aktuelni_indeks <
indeks;
aktuelni_indeks++)
aktuelni =
aktuelni->veza;
return aktuelni-
>element;
}
Pretpostavimo dalje da je
između drugog i trećeg
elementa potrebno ubaciti novi
element, čija je vrijednost 8.
Nakon kreiranja novog čvora,
dovoljno je povezati pokazivače
tako da dobijemo situaciju kao
na sljedećoj slici:
template <typename
Tip>
void
Lista<Tip>::Umetni(in
t indeks, const Tip
&element) {
if(indeks == 0)
DodajNaPocetak(elemen
t);
else if(indeks ==
velicina)
DodajNaKraj(element);
else {
operator []
(indeks - 1);
aktuelni->veza =
new Cvor(element,
aktuelni->veza);
velicina++;
}
}
template <typename
Tip>
void
Lista<Tip>::Izbaci(in
t indeks) {
if(velicina == 0)
throw "Lista je
prazna!\n";
velicina--;
Cvor *za_brisanje;
if(indeks == 0) {
za_brisanje =
pocetak;
aktuelni =
pocetak =
za_brisanje->veza;
aktuelni_indeks
= 0;
}
else {
operator []
(indeks - 1);
za_brisanje =
aktuelni->veza;
aktuelni->veza =
za_brisanje->veza;
if(indeks ==
velicina) kraj =
aktuelni;
}
delete
za_brisanje;
}
Ostala je
metode još implementacija
“Ponavljaj”. Ova
metoda
čitavu prosto prolazi
listu, i funkciju kroz
na svaki element
primjenjuje
parametrom “Akcija”: zadanu
template <typename
Tip>
void
Lista<Tip>::Ponavljaj
(void(*akcija)(double
&)){
for(Cvor *pok =
pocetak; pok != 0;
pok = pok->veza)
akcija(pok-
>element);
}
for(int i = 0; i <
lista.Duzina(); i++)
cout << lista[i] << "
";
void Ispis(double
&element) {
cout << element <<
" ";
}
lista.Ponavljaj(Ispis
i);
Ovakav jer
efektniji, pristup
se je mnogo
akcija izvodi
neposredno
liste kojima nad elementima
se pristupa
sekvencijalno, prateći
pokazivače,
pozivanjem bez potrebe
operatorske za
funkcije
svakako zatroši
operator “[]”, bez
vrijeme, što
obzira
funkcija štoznatno
je ova optimizirana
operatorska
pamćenjem
posljednjeg pozicije
elementa i indeksa
kojem
smo
mnogo pristupali.
stvari sa Pored
listom toga,
bi se
moglo raditi
“Ponavljaj” pomoću
čak i da metode
u listi
nismo
operator uopće
“[]”. implementirali
Može čega se
postaviti
funkcija pitanje zbog
koja seu prenosi kao
parametar
“Ponavljaj” metodu
svoj parametar
prihvata
nekonstantnipo referenci
objekat). (i tosmo
Ovo na
uradili
metoda da bismo“Ponavljaj”
omogućili da
eventualno
elementima obaviZamislimo,
liste. izmjene nad na
primjer,
udvostručimo da sve želimo
elemente dau
listi.
svakakoJedno mogućepetlje:
korištenje rješenje je
for(int i = 0; i <
lista.Duzina(); i++)
lista[i] *= 2;
void Dupliraj(double
&element) {
element *= 2;
}
Lista<double> lista;
for(;;) {
double broj;
cout << "Unesi
broj (0 za kraj): ";
cin >> broj;
if(broj == 0)
break;
for(int i = 0; i <
lista.Duzina() &&
lista[i] < broj; i+
+);
lista.Umetni(i,
broj);
}
cout << "Sortirani
spisak brojeva glasi:
";
lista.Ponavljaj(Ispi
si);
Lista je jedna
najviše od najkorisnijih
korištenih strukturai
podadaka
programerskim u problemima.
brojnim
Kako je lista
struktura mnogo
podataka i općenitija
od steka i
od reda
vidjeli, (koji
mogu se, kao
smatrati što
kao smo
njene
podvarijante),
“Red” je, u klase “Stek”
slučaju da nami
problem
zahtijeva koji
njihovu rješavamo
upotrebu,
moguće implementirati
jednostavno pomoću veoma
klase
“Lista”.
implementacijaJedna od
moglamogućih bi
izgledati ovako:
template <typename
Tip>
class Stek {
Lista<Tip> lista;
public:
bool Prazan() {
return
lista.Prazna(); }
void Stavi(const
Tip &element)
{ lista.DodajNaPoceta
k(element); }
Tip Skini() {
double element =
lista[0];
lista.Izbaci(0);
return element;
}
};
template <typename
Tip>
class Red {
Lista<Tip> lista;
public:
bool Prazan() {
return
lista.Prazna(); }
void Ubaci(const
Tip &element)
{ lista.DodajNaKraj(e
lement); }
Tip Izvadi() {
double element =
lista[0];
lista.Izbaci(0);
return element;
}
};