You are on page 1of 44

1

18 Samoreferentne strukture i liste



Naglasci:

samoreferentne strukture

jednostruko vezana lista

dvostruko vezana lista

operacije s listom

sortirane liste

implementacija ADT STACK pomou jednostruko vezane liste

implementacija ADT QUEUE pomou jednostruko vezane liste

implementacija ADT DEQUEUE pomou dvostruko vezane liste
2
18.1 Samoreferentne strukture i lista
Karakteristika je strukturnih tipova da lanovi strukture mogu biti bilo kojeg prethodno definiranog tipa.
To znai da struktura ne moe sadravati "samu sebe", jer se ona tek definira. Dozvoljeno je pak da struktura
sadri pokaziva na "sebe". Takve strukture se nazivaju samoreferentne ili rekurzivne strukture.


struct node
{
int elem;
struct node * next;
}




struct node N1, N2, N3;
struct node *List;
Mogue je inicirati pokaziva List tako da pokazuje na bilo koji od ova tri objekta, pr.
List = &N1;
Poto objekti N1, N2 i N3 sadre lan next, koji je pokaziva tipa struct node, moe se uspostaviti veza
izmeu objekata, tako da se pokazivau next jednog objekta pridijeli adresa drugog objekta. Primjerice,
sljedeim naredbama se definira sadraj i veza sa drugim objektom.

N1.elem = 1;
N2.elem = 2;
N3.elem = 3;
N1.next = &N2; /* prvi povezujemo s drugim */
N2.next = &N3; /* drugi povezujemo s treim */
N3.next = NULL; /* trei element ostavimo nepovezanim */

3



Slika 18.1 Vezana lista

Ovakova struktura se naziva vezana lista. Sastoji se od meusobno povezanih objekata koji se nazivaju
vorovi (eng. node). Prvi vor vezane liste se obino naziva glava liste. Njegovu adresu se biljei u
pokazivau List. Posljednji vor se naziva kraj liste. Pokaziva next tog vora se uvijek postavlja na
vrijednost NULL, to slui kao oznaka kraja liste.

Strukturalna definicija: Vezana lista skup elemenata, koji je realiziran na nain da je svaki
element dio vora, koji sadri i vezu sa sljedeim vorom. Prazna lista ne sadri ni jedan vor.

Apstraktna rekurzivna definicija: Lista je linearna struktura podataka koja sadri nula ili vie
elemenata, a ima dvije karakteristike:
1. Lista moe biti prazna
2. Lista se sastoji od elementa liste iza kojeg slijedi lista elemenata

Lista se moe realizirati kao niz elemenata ili kao vezana lista. Listi, koja se formira kao niz elemenata,
jednostavno se pristupa elementima liste pomou indeksa niza. Znatno je kompliciraniji postupak ako treba
umetnuti ili izbrisati element liste. Jo je jedna bitna razlika izmeu niza i vezane liste. Nizovi zauzimaju
fiksnu veliinu memorije, a veliina vezane liste (i zauzee memorije), je odreena brojem elemenata liste.
Lista je dinamika struktura podataka, u nju se podaci umeu i briu. Zbog svojstva da slui prikupljanju
podataka esto se kae da lista predstavlja kolekciju elemenata.
4
18.2 Operacije s vezanom listom
Najprije e biti pokazano kako se formira lista cijelih brojeva. Polazi se od definirane strukture node,
koja sadri jedan element kolekcije i vezu kojom se pristupa elementima kolekcije, u sljedeem obliku:

typedef int elemT;
typedef struct node Node;
typedef Node *LIST;

struct node
{
elemT elem; /* element liste */
Node *next; /* pokazuje na slijedei vor */
}
Tip Node oznaava vor liste, a tip LIST oznaava pokaziva na vor liste. Deklaracija
LIST List = NULL;
inicijalizira varijablu List koja, prema apstraktnoj definiciji, predstavlja praznu listu, a programski
predstavlja pokaziva koji na vor koji sadri glavu liste. Zvat emo ga pokaziva liste.
Proces stvaranja vezane liste poinje stvaranjem vora vezane liste. Formira se alociranjem memorije za
strukturu node.

List = malloc(sizeof (Node));


List->elem = 5;

Upitnik oznaava da nije definiran sadraj pokazivaa List->next. Uobiajeno je da se on postavi na
vrijednost NULL, jer pokazuje na nita. Time se ujedno oznaava kraj liste (po analogiju sa stringovima,
gdje znak '\0' oznaava kraj stringa). To simboliki prikazujemo slikom:
5

List->next = NULL;

U ovu kolekciju moe se dodati jo jedan element, sljedeim postupkom. Prvo, se formira vor kojem
pristupamo pomou pokazivaa q.

Node *q=malloc(sizeof(Node));
q->elem =7;

Zatim se ova dva vora poveu sljedeim naredbama:

q->next = List;



List = q;


Novi element je postavljen na poetak liste. Pokaziva q vie nije potreban jer on sada ima istu vrijednost
kao pokaziva List (q se koristi samo kao pomoni pokaziva za formiranje vezane liste). Na ovaj se nain
moe formirati lista s proizvoljnim brojem lanova. Poetak liste (ili glava liste) je zabiljeen u pokazivau
kojeg se tradicionalno naziva List. Kraj liste se je obiljeen s NULL vrijednou next pokazivaa krajnjeg
vora liste.
U ovom primjeru lista je formirana tako da se novi vor postavlja na glavu liste. Kasnije e biti pokazano
kako se novi vor moe dodati na kraj liste ili umetnuti izmeu dva vora liste.
6

18.2.1 Formiranje vora liste

U radu s listom, za formiranje novog vora liste koristit e se funkcija newNode().Ona prima argument
tipa elemT, a vraa pokaziva na alocirani vor ili NULL ako se ne moe izvriti alociranje memorije.

Node *newNode(elemT x)
{
Node *n = malloc(sizeof(Node));
if(n != NULL) n->elem = x;
return n;
}
Takoer, potrebno je definirati funkciju freeNode(), kojom se oslobaa memorija koju zauzima vor.

void freeNode(Node *p)
{
/* ako se element liste formira alociranjem memorije
* tada ovdje treba dodati naredbu za dealociranje
* elementa liste.
*/
free(p); /* dealociraj vor liste */
}

Uoite da implementacija ove funkcije ovisi o definiciji elementa liste. Ako se element liste formira
alociranjem memorije, primjerice, ako je element liste dinamiki string, tada u ovu funkciju treba dodati
naredbu za dealociranje elementa liste.

7
18.2.2 Dodavanje elementa na glavi liste

Prethodno opisani postupak formiranja liste, na nain da se novi vor umee na glavu liste,
implementiran je u funkciji koja se naziva add_front_node().


void add_front_node(LIST *pList, Node *n)
{
if(n != NULL) /* izvri samo ako je alociran element */
{
n->next = *pList;
*pList = n;
}
}

Prvi argument funkcije je pokaziva na pokaziva liste. Ovakvi prijenos argumenta je nuan jer se u
funkciji mijenja vrijednost pokazivaa liste. Drugi argument je pokaziva vora koji se unosi u listu.

Dodavanje elementa x u listu List sada se vri jednostavnim pozivom funkcije:

add_front_node(&List, newNode(x));

Sloenost ove operacije je O(1).
8
18.2.3 Brisanje vora na glavi liste
Brisanje vora glave liste je jednostavna operacija. Njome vor, koji slijedi iza glave liste, (List-
>next) postaje glava liste, a trenutni vor glave liste (n) se dealocira iz memorije. To ilustrira slika 18.2.



Node *n = List;
List = List->next;
freeNode(n);


Slika 18.2 Brisanje s glave liste

Postupak brisanja glave liste se formalizira funkcijom delete_front_node().

void delete_front_node(LIST *pList)
{
Node *n = *pList;
if(n != NULL) {
*pList = *pList->next;
freeNode(n);
}
}

Uoite da se i u ovoj funkciji mijenja vrijednost pokazivaa glave liste, stoga se u funkciju prenosi pokaziva
na pokaziva liste. Sloenost ove operacije je O(1).
9
18.2.4 Brisanje cijele liste
Brisanje cijele liste se vri funkcijom delete_all_nodes(), na nain da se briu svi vorovi sprijeda.

void delete_all_nodes( LIST *pList )
{
while (*pList != NULL)
delete_front_node(pList);
}

18.2.5 Obilazak liste
Ako je poznat pokaziva liste uvijek se moe odrediti pokaziva na sljedei element pomou next
pokazivaa.
Node *ptr = List->next;
Dalje se sukcesivno moe usmjeravati pokaziva ptr na sljedei element liste naredbom
ptr = ptr->next;
Na taj se nain moe pristupiti svim elementima liste. Taj postupak se zove obilazak liste (eng. list
traversing). Obilazak liste zavrava kada je ptr == NULL.

Primjer: Pretpostavimo da elimo ispisati sadraj liste redoslijedom od glave prema kraju liste. To moemo
ostvariti sljedeim programom:
Node *p = List; /* koristi pomoni pokaziva p */
while (p != NULL) /* ponavljaj do kraja liste */
{
printf("%d\n", p->elem); /* ispii sadraj elementa */
p = p->next; /* i postavi pokaziva na */
/* sljedei element liste */
}

10

U ovom primjeru na sve elemente liste je primijenjena ista funkcija. Postupak kojim se na sve elemente
liste djeluje nekom funkcijom moe se poopiti funkcijom list_for_each() u sljedeem obliku:

void list_for_each(LIST L, void (*pfun)(Node *))
{
while( L != NULL) {
(*pfun)(L);
L = L->next;
}
}
Prvi argument ove funkcije je lista, a drugi argument ove funkcije je pokaziva na funkciju, koja se
primijenjuje na sve elemente liste. To moe biti bilo koja void funkcija kojoj je argument tipa Node *.
Definiramo li funkciju:


void printNode(Node *n) { printf("%d\n", n->elem);}


tada poziv funkcije:


list_for each(L, printNode);

ispisuje sadraj cijele liste

11
Kada je potrebno izvriti obilazak liste od kraja prema glavi liste, ne moe se primijeniti iterativni
postupak. U tom sluaju se moe koristiti rekurzivna funkcija reverse_list_for_each().

void reverse_list_for_each(LIST L, void (*pfun)(Node *))
{
if (L == NULL) return;
reverse_list_for_each(L->next, pfun);
(*pfun)(L);
}
Obilazak liste je nuan i kada treba odrediti posljednji vor liste. To vri funkcija last_node().

Node *last_node(LIST L)
{/*vraa pokaziva na krajnji vor liste*/

if(L == NULL) return NULL;
while ( L->next != NULL)
L = L->next;
return L;
}

Broj elemenata koji sadri lista daje funkcija num_list_elements().

int num_list_elements(LIST L)
{
int num = 0;
while ( L != NULL) {
num++;
L = L->next;
}
return num; /* broj elemenata liste */
}
12
18.2.6 Traenje elementa liste

esto je razlog za obilazak liste traenje elementa liste. U sluaju kada je element liste prostog skalarnog
tipa moe se koristiti funkciju find_list_element().


/* funkcija: find_list_element
* ispituje da li lista sadri element x
* Argumenti:
* x element kojeg se trai
* List pokaziva na glavu liste
* Vraa:
* pokaziva na vor koji sadri x, ili NULL ako x nije u listi
*/


Node *find_list_element(LIST L, elemT x)
{
while( L != NULL && p->elem != x )
L = L->next;
return L;
}

Pretraivanje liste ima sloenost O(n), gdje je n broj elemenata liste.
13
18.2.7 Dodavanje vora na kraju liste
Dodavanje vora na kraju liste vri se na sljedei nain:
Ako lista jo nije formirana, tj. ako je List==NULL,
koristi se postupak opisan u funkciji add_front_node().
Ako je List != NULL, tada
1. Obilaskom liste odredi se pokaziva krajnjeg vora liste. Taj pokaziva, nazovimo ga p, ima
karakteristiku da je p->next == NULL.
2. Zatim se p->next postavi na vrijednost pokazivaa vora kojeg se dodaje u listu.
3. Poto dodani vor predstavlja novi kraj liste njega se zakljuuje s NULL .

Ovaj postupak je realiziran funkcijom add_back_node();
void add_back_node(LIST *pList, Node *n)
{
if(n == NULL) /* Izvrava se samo ako je */
return; /* alociran vor. */
if(*pList == NULL) { /* Ako lista nije formirana */
*pList = n; /* iniciraj pokaziva */
n->next = NULL;
}
else {
LIST p = *pList;
while ( p->next != NULL) /* 1. odredi krajnji vor */
p = p -> next;
p ->next = n; /* 2. dodaj novi vor */
n->next = NULL; /* 3. oznai kraj liste */
}
}
Ovu funkciju se koristi po istom obrascu kao i funkciju add_front_node(), tj. novi element (x) se dodaje
naredbom:
add_back_node(&List, newNode(x));
14
18.2.8 Umetanje i brisanje vora unutar liste

Postupak umetanja ili brisanja vora n ilustrira slika 18.3



Slika 18.3 Umetanje i brisanje vora unutar vezane liste

Brisanje vora koji slijedi iza vora p ( na slici, to je vor n) vri se naredbama:
n = p->next; /* odredi sljedei */
p->next = n->next;
freeNode(n);
Ako treba izbrisati vor n, potrebno je odrediti vor p, koji mu prethodi.
p = /* odredi vor koji prethodi voru n*/
p->next = n->next;
freeNode(n);
Umetanje vora n iza vora p se provodi naredbama:
n->next = p->next;
p->next = n;
Umetanje vora n iza vora x provodi se tako da se najprije odredi vor p koji prethodi voru x, a
zatim se provodi prethodni postupak.
15
Operaciju kojom se odreuje vor koji prethodi voru n realizira se funkcijom get_previous_node(),
koja vraa pokaziva na prethodni vor, ili NULL ako ne postoji prethodni vor.

Node *get_previous_node(LIST List, Node *n )
{
Node *t, *pre;
t = pre = List; /* start od glave */
while( (t != n) /* dok ne pronae n */
&& (t->next != NULL ))
{
pre = t; /* pamti prethodni */
t = t->next ;
}
return (pre); /* vrati prethodni */
}
Sada se postupak brisanja vora moe realizirati funkcijom delete_node(). Njome se iz liste brie vor
n.

void delete_node(LIST *pList, Node *n)
{
if(*pList == NULL || n == NULL)
return;
if(*pList == n) { /* ako n pokazuje glavu */
*pList = n->next; /* odstrani glavu */
}
else {
Node *pre = get_previous_node(*pList, n );
pre->next = n->next;
}
freeNode(n); /* dealociraj vor */
}
16
18.2.9 Brisanje vora na kraju liste

Brisanje vora na kraju liste je jednako komplicirana operacija kao i brisanje unutar liste, jer se i u ovom
sluaju mora odrediti vor koji mu prethodi. To je realizirano funkcijom delete_back_node().
/* Funkcija: delete_back_node
* odstranjuje vor na kraju liste
* Argumenti:
* pList - pokaziva na pokaziva liste.
*/

void delete_back_node(LIST *pList)
{
Node *pre, back; /* pre prethodni */
if (*pList == NULL) /* back krajnji */
return;
back = pre = *pList; /* start od glave */
while(back->next != NULL ) { /* pronai kraj liste*/
pre = back; /* zapamti prethodni */
back = back->next ;
}

if(back == *pList) /* ako je krajnji = glava */
*pList == NULL; /* napravi praznu listu */
else /* inae */
pre->next = NULL; /* prethodni postaje krajnji*/
freeNode(back); /* dealociraj vor */
}

17
Primjer: U program testlist.c testiraju se prije opisane funkcije.

void printNode(Node *n)
{ if(n) printf( "%d ", n->elem ); }

void printList(LIST List)
{
if( L == NULL ) printf( "Lista je prazna\n" );
else list_for_each(L, printNode);
printf( "\n" );
}

int main( )
{
LIST List;
int i;

/* obvezno iniciraj listu */
List = NULL ;

/* formiraj listu s 10 cijelih brojeva */
for( i = 0; i < 10; i++ ) {
add_front_node(&List, newNode(i));
printList(List);
}

/* izbrii prednji i strani element */
delete_front_node(&List);
delete_back_node(&List);
printList(List);

if(find_list_element(List, 5) != NULL)
printf( "pronadjen element :%d\n", 5 );

if(find_list_element(List, 9) == NULL)
18
printf( "Nije pronadjen element :%d\n", 9 ) ;

add_back_node(&List, newNode(9));
printList(List);
delete_all_nodes(&List) ;
printList(List);
return 0;
}

Nakon izvrenja dobije se ispis:
0
1 0
2 1 0
3 2 1 0
4 3 2 1 0
5 4 3 2 1 0
6 5 4 3 2 1 0
7 6 5 4 3 2 1 0
8 7 6 5 4 3 2 1 0
9 8 7 6 5 4 3 2 1 0
8 7 6 5 4 3 2 1
pronadjen element :5
Nije pronadjen element :9
8 7 6 5 4 3 2 1 9
Lista je prazna
19
18.3 to moe biti element liste
Uzmimo za primjer da u listi treba zapisati podatke o studentima: 1) njihovo ime i 2) ocjenu. To se moe
realizirati na dva naina:
Prvi je nain da se vor liste formira tako da sadri vie razliitih tipova podataka, primjerice:
typedef struct snode StudentNode;
typedef StudentNode *STUDENTLIST;

typedef struct snode
{
StudentNode *next;
char *ime;
int ocjena;
} StudentNode;
Drugi je nain da se element liste definira strukturom koja ima vie lanova, primjerice:
typedef struct student_info {
char *ime;
int ocjena;
}Student;

struct snode
{
StudentNode *next; /* pokazuje na slijedei vor */
Student elem; /* element liste tipa Student */
};

U oba sluaja lista se formira istim postupkom kao i lista stringova, jedino je potrebno modificirati funkcije
newNode(), freeNode(), Find() i Print(). Primjerice, funkcija newNode() u ovom sluaju ima dva
argumenta: ime i ocjenu, a realizira se na sljedei nain.
20
18.4 Lista sa sortiranim redoslijedom elemenata
U dosadanjim primjerima podaci su bili svrstani u listi redoslijedom kojim su i uneseni u listu. esto je
pak potrebno da podaci u listi budu u sortiranom redoslijedu. To se moe postii na dva naina. Prvi je nain
da se izvri sortiranje liste, a drugi je nain da se ve pri samom unoenju podataka ostvari sortirani
redoslijed elemenata. S obzirom da je namjena liste da slui kao kolekcija u koju se esto unose i odstranjuju
elementi, ovaj drugi pristup se ee koristi.
Izrada liste s podacima o imenu i ocjeni studenta je tipian primjer koritenja liste u kojem je poeljno
imati sortiran redoslijed elemenata. Sada e biti prikazana izvedba modula za manipuliranje s listom
studenata.
Prema uputama za formiranje modula, iznesenim u poglavlju 9, modul za listu studenata e se formirati od
sljedeih datoteka:


1. datoteka specifikacije modula ("studentlist.h") sadri deklaracije funkcija i struktura koje se koriste za
rad s listom.
2. datoteka implementacije ("studentlist.c") sadri definicije varijabli i implementaciju potrebnih
funkcija.
3. datoteka primitivnih funkcija za manipuliranje listom ("listprim.c") koje su definirane u prethodnom
poglavlju. Ova datoteka e se koristiti iskljuivo kao #include datoteka u datoteci "studentlist.c". Sve
funkcije iz ove datoteke su oznaene prefiksom static, to znai da e biti vidljive samo u datoteci
"studentlist.c".
4. Testiranje modula se provodi programom studlist-test.c.

Datoteka specifikacije "studentlist.h"
#ifndef _STUDENTLIST_H_
#define _STUDENTLIST_H_

/* sortirana lista za evidenciju studenata */

21
typedef struct stnode StudentNode;
typedef StudentNode *STUDENTLIST;

struct stnode {
StudentNode *next;
char *ime;
int ocjena;
};

void StudentL_insertSorted (STUDENTLIST *pList,
char *ime, int ocjena);
/* U listu umee podatke o studentu: ime i ocjenu
* Ako ime ve postoji, zamjenuje se vrijednost ocjene.
* Lista je uvijek sortirana prema imenu studenta
*/

StudentNode *StudentL_find(STUDENTLIST L, char *s);
/* Trai vor liste u kojem je ime jednako stringu s
* Vraa pokaziva na vor, ili NULL ako nije pronaen string
*/

void StudentL_deleteNode(STUDENTLIST *pList, StudentNode *n);
/* Odstranjuje vor liste na kojeg pokazuje n */

void StudentL_delete(STUDENTLIST *pList) ;
/* Odstranjuje sve vorove liste */

int StudentL_size(STUDENTLIST List);
/* Vraa broj elemanate liste */

void StudentL_print(STUDENTLIST L );
/* Ispisuje sadraj liste */

#endif

22
Datoteka implementacije "studentlist.c"

Izvedba svih funkcija e biti izvrena po poznatom obrascu, jedino je potrebno objasniti algoritam za
sortirano umetanje vorova liste. On glasi:

Algoritam: Sortirano umetanje vora liste (ime,ocjena)
Ako je lista prazna,
Formiraj vor s zadanim imenom i ocjenom
i stavi ga na poetak liste
inae
Nai zadnji vor (i njemu prethodni) u kojem je ime leksikografski manje
ili jednako zadanom imenu, ili kraj liste

Ako vor s zadanim imenom ve postoji,
Zamijeni ocjenu
inae
Formiraj vor s zadanim imenom i ocjenom.
Ako je dosegnut kraj liste
Dodaj na kraj liste
inae
Umetni iza prethodnog
ili stavi na poetak liste ako je prethodni == NULL


#include <stdio.h>
#include <stdlib.h>
#include "studentlist.h"

static StudentNode *newNode(char *ime, int ocjena)
{
StudentNode *n = malloc(sizeof(StudentNode));
23
if(n != NULL)
{
n->ime=strdup(ime);
n->ocjena = ocjena;
}
return n;
}

static void freeNode(StudentNode *n)
{
if(n) { free(n->ime); free(n); }
}

#define LIST STUDENTLIST
#define Node StudentNode

#include "listprim.c"

#undef LIST
#undef Node

int StudentL_size(STUDENTLIST List)
{ return num_list_elements(List);
}

void StudentL_deleteNode(STUDENTLIST *pList, StudentNode *n)
{ delete_node(pList, n);
}

void StudentL_delete(STUDENTLIST *pList)
{ delete_all_nodes(pList);
}

24
void StudentL_insertSorted (STUDENTLIST *pList, char *ime, int ocjena)
{
StudentNode *tmp = *pList;
StudentNode *n, *pre = NULL;
int cmp;
if (!*pList) { /* ako je lista prazna, formiraj prvi vor */
n= newNode (ime, ocjena);
n->next=NULL;
*pList = n;
return;
}

/* nai zadnji vor (i njemu prethodni) u kojem je ime manje
ili jednako zadanom imenu, ili kraj liste */

cmp = strcmp(ime, tmp->ime);
while ((tmp->next) && (cmp > 0)) {
pre = tmp;
tmp = tmp->next;
cmp = strcmp(ime, tmp->ime);
}
/* ako ime ve postoji, zamijeni ocjenu */
if(cmp == 0) {
tmp->ocjena = ocjena;
return;
}

/*inae dodaj novi vor */
n= newNode (ime, ocjena);
n->next=NULL;

/* ako je dosegnut kraj liste, dodaj na kraj liste */
if ((tmp->next == NULL) && (cmp > 0)) {
tmp->next = n;
return;
}
25
/*ili umetni iza prethodnog*/
if (pre != NULL) {
pre->next = n;
n->next = tmp;
}
/*ili stavi na poetak liste ako je prethodni == NULL*/
else {
n->next = *pList;
*pList = n;
}
}

StudentNode *StudentL_find(STUDENTLIST L, char *ime)
{
while( L != NULL && strcmp(L->ime, ime) )
L = L->next;
return L;
}

void PrintStInfo(StudentNode *n )
{ printf( "Ime:%s, ocjena:%d \n", n->ime, n->ocjena );
}

void StudentL_print(STUDENTLIST L )
{
if( L == NULL ) printf( "Lista je prazna\n" );
else list_for_each(L, PrintStInfo);
printf( "\n" );
}

26
Testiranje modula se provodi programom studlist_test.c.
/* Datoteka: studlist_test.c */
#include <stdio.h>
#include <stdlib.h>
#include "studentlist.h"

int main( )
{
STUDENTLIST List, n;
int i;

List = NULL ;

StudentL_insertSorted (&List, "Bovan Marko", 5);
StudentL_insertSorted (&List, "Radic Jure", 2);
StudentL_insertSorted (&List, "Marin Ivo", 3);
StudentL_insertSorted (&List, "Bovan Marko", 2);

printf("Lista ima %d elemenata\n", StudentL_size(List));
StudentL_print(List );

n = StudentL_find(List, "Bovan Marko");
if(n != NULL)
StudentL_deleteNode(&List, n);

StudentL_print(List);
StudentL_delete(&List) ;

return 0;
}
Program se kompilira komandom:
c:> cl studlist_test.c studentlist.c
Nakon izvrenja, dobije se sljedei ispis:
27
Lista ima 3 elemenata
Ime:Bovan Marko, ocjena:2
Ime:Marin Ivo, ocjena:3
Ime:Radic Jure, ocjena:2

Ime:Marin Ivo, ocjena:3
Ime:Radic Jure, ocjena:2


Zadatak: Objasnite zato u prethodnom ispisu lista poetno ima tri elementa, iako je u programu etiri puta
primijenjena funkcija StudentL_insertSorted().


Zadatak: Napiite program u kojem korisnik unosi rezultate ispita (ime i ocjenu studenta) u sortiranu listu.
Unos zavrava kada se otkuca prazno ime ili ocjena 0. Nakon toga treba rezultate ispita upisati u tekstualnu
datoteku, na nain da se u jednom retku ispie redni broj, ime i ocjena studenta.
28
18.5 Implementacija ADT STACK pomou linearne liste
U poglavlju 16 izvrena je implementacija ADT STACK pomou niza elemenata. Stog se moe efikasno
implementirati i pomou linearne liste. Uzmemo li da je specifikacija ADT STACK ista kao i specifikacija ADT
za implementaciju pomou niza u datoteci "stack.h", koja je opisana u poglavlju 16, implementacija se moe
napraviti na nain kako je opisano u datoteci "stack-list.c".
Sada STACK predstavlja pokaziva na strukturu stack, koja ima samo jedan element, pokaziva na vor
linearne liste. Taj pokaziva je nazvan top, i on pokazuje na glavu liste. Operacija Push() je implementirana
kao umetanje elementa na glavu liste, a pop() kao brisanje elementa s glave liste.
/* Datoteka: stack-list.c */
/* Implementacija ADT STACK pomou vezane liste */
#include <stdlib.h>
#include <assert.h>
#include "stack.h"

/* typedef int stackElemT; definiran u stack.h*/
/* typedef struct stack *STACK; definiran u stack.h*/

struct node {
stackElemT elem;
struct node *next;
};

struct stack {
struct node *top;
};

static void stack_error(char *s)
{
printf("\nGreska: %s\n", s);
exit(1);
}

STACK stack_new(void)
29
{/*alocira ADT STACK*/
STACK S = malloc(sizeof(struct stack));
if(S != NULL) S->top = NULL;
return S;
}

void stack_free(STACK S)
{/*dealocira ADT STACK*/
struct node *n;
assert(S != NULL);
if( !stack_empty(S))
for (n = S->top; n != NULL; n = n->next)
free(n);
}

int stack_empty(STACK S)
{/* vraa != 0 ako je stog prazan*/
assert(S != NULL);
return ( S->top==NULL);
}

unsigned stack_count(STACK S)
{/*vraa broj elemenata na stogu*/
unsigned num = 0;
struct node *n;
assert(S != NULL);
if( !stack_empty(S))
for (n = S->top; n != NULL; n = n->next)
num++;
return num;
}

stackElemT stack_top(STACK S)
{/*vraa vrijednost elementa na vrhu stoga*/
assert(S != NULL);
return S->top->elem;
30
}

stackElemT stack_pop(STACK S)
{
/* Skida vor s vrha stoga */
stackElemT el;
struct node *n;
assert(S != NULL);
if (stack_empty(S)) stack_error("Stog je prazan");
n = S->top;
el = n->elem;
S->top = n->next;
free(n);
return el;
}

void stack_push(STACK S, stackElemT el)
{
/* Ubacuje vor na vrh stoga*/
struct node *n;
assert(S != NULL);
n = malloc(sizeof(struct node));
if (n != NULL) {
n->elem = el;
n->next = S->top;
S->top = n;
}
else
printf(" Nema dovoljno memorije!\n");
}
Testiranje ove implementacije se provodi s programom stack-test.c, koji je opisan u poglavlju 16. Jedina
razlika je da se u u tom programu umjesto ukljuenja datoteke "stack-arr.c" ukljui datoteka "stack-list.c".

31

18.6 Implementacija ADT QUEUE pomou vezane liste
Red se moe jednostavno implementirati i pomou vezane liste. Za implementacija reda pomou vezane
liste koristit e se prethodna specifikaciju ADT-a QUEUE (danu u datoteci "queue.h"). U ovoj
implementaciji QUEUE predstavlja pokaziva na strukturu queue, koja ima dva lana: front i back. To su
pokazivai na prednji i stranji element liste. Operacijom put() stavlja se element na kraj liste, a operacijom
get() skida se element s glave liste.



/* Queue realiziran pomou vezane liste*/
#include <assert.h>
#include "queue.h"

/* typedef int queueElemT; */
/* typedef struct queue *QUEUE; */

struct node {
queueElemT elem;
struct node *next;
};

struct queue {
struct node * front;
struct node * back;
};

32
QUEUE queue_new(void)
{
/*alocira ADT QUEUE*/

QUEUE Q = malloc(sizeof(struct queue));
if(Q != NULL)
{ Q->front = Q->back = NULL; }
return Q;
}

void queue_free(QUEUE Q)
{
/*dealocira ADT QUEUE*/
struct node *n;
assert(Q != NULL);
while ((n = Q->front) != NULL ) {
Q->front = n ->next;
free(n);
}
free(Q);
}

int queue_empty(QUEUE Q)
{
/* vraa != 0 ako je red prazan*/

assert(Q != NULL);
return Q->front == NULL;
}

int queue_full(QUEUE Q)
{
return 0;
}


33

void queue_put(QUEUE Q, queueElemT el)
{
/* stavlja element el u red Q*/

struct node * n;
assert(Q != NULL);
n = malloc(sizeof(struct node));
if (n != NULL) {
n->elem = el;
n->next = NULL;
if (queue_empty(Q))
Q->front = n;
else
Q->back->next = n;
Q->back = n;
}
}

queueElemT queue_get(QUEUE Q)
{
/* vraa element iz reda Q*/

queueElemT el;
struct node * n;
assert(Q != NULL);
n = Q->front;
el = n->elem;
Q->front = n->next;
if (Q->front == NULL)
Q->back = NULL;
free(n);
return el;
}


34

unsigned queue_count(QUEUE Q)
{/*vraa broj elemenata reda*/
unsigned num = 0;
struct node *n;
assert(Q != NULL);
if( !queue_empty(Q))
for (n = Q->front; n != NULL; n = n->next)
num++;
return num;
}


void queue_print(QUEUE Q)
{
int i;
struct node *n;
assert(Q != NULL);
n = Q->front;
if( Q->front == NULL )
printf( "Red je prazan\n" );
else do {
printf( "%d, ", n->elem );
n = n->next;
} while( n != NULL );
printf( "\n" );
}

Testiranje ove implementacije se provodi s programom queue-test.c, opisanim u pogavlju 16, tako da se u
njemu umjeto ukljuenja datoteke "queue-arr.c" ukljui datoteka "queue-list.c".
35
18.7 Dvostruko vezana lista
Ukoliko se vor liste formira tako da sadri dva pokazivaa, next - koji pokazuje na sljedei vor i prev -
koji pokazuje na prethodni vor, dobije se dvostruko vezana lista.
typedef int elemT;
typedef struct node Node;
typedef Node *DLIST;

struct node {
elemT elem; /* element liste */
Node *next; /* pokazuje na sljedei vor */
Node *prev; /* pokazuje na prethodni vor */
}



1. Pokaziva next krajnjeg elementa i pokaziva prev glave liste su jednaki NULL.
2. Ovakvu listu se moe iterativno obilaziti u oba smjera, od glave prema kraju liste (koritenjem
pokazivaa next) i od kraja prema poetku liste (koritenjem pokazivaa prev).
3. Umetanje unutar liste i brisanje iz liste se provodi jednostavnije i bre nego kod jednostruko vezane
liste, jer je u svakom voru poznat pokaziva na prethodni vor.
4. U odnosu na jednostruko vezanu listu, dvostruko vezana lista zauzima vie memorije (zbog pokazivaa
prev) .
5. Ako se vri umetanje i brisanje vorova samo na poetku liste, tada je bolje rjeenje koristiti
jednostruko vezanu listu.

36
Kroz dva primjera bit e pokazano kako se implementira dvostruko vezana lista.


Primjer: Prikana je implementacija funkcija za umetanje vora na glavi liste (dlist_add_front_node) i
odstranjivanje vora na glavi liste (dlist_delete_front_node). Uoite da je potrebno izvriti neto vie
operacija nego kod jednostruko vezane liste.


void dlist_add_front_node(DLIST *pList, Node *n)
{
if(n != NULL) /* izvri samo ako je alociran vor */
{
n->next = *pList;
n->prev = NULL;
if(*pList != NULL)
(*pList)->prev = n;
*pList = n;
}
}



void dlist_delete_front_node(DLIST *pList)
{
Node *tmp = *pList;
if(*pList != NULL) {
*pList = (*pList)->next;
if(*pList != NULL)
(*pList)->prev = NULL;
freeNode(tmp);
}
}

37
Primjer: Prikazana je implementacija funkcija za brisanje vora unutar liste (dlist_delete_node).
Uoite da se ova operacija izvrava efikasnije nego kod primjene jednostruko vezane liste.


void dlist_delete_node(DLIST *pList, Node *n)
{
if(*pList == NULL || n == NULL)
return;
if(*pList == n) { /* ako n pokazuje glavu */
*pList = n->next; /* odstrani glavu */
*plist->prev = NULL;
}
else {
n->prev->next = n->next;
if(n->next != NULL)
n->next->prev = n->prev;
}
freeNode(n); /* dealociraj vor */
}




Zadatak: Napiite funkciju za brisanje i umetanje vora na kraju liste. Za testiranje implementacije koristite
isti program kao kod jednostruko vezane liste.


Napomena: Funkcije za formiranje vora, brisanje liste i traenje elementa liste su iste kao kod jednostruko
vezane liste.
38
18.8 Generiki dvostrani red - ADT DEQUEUE
Dvostruka se lista moe iskoristiti za efikasnu implementaciju dvostranog reda (eng. double ended queue) u
obliku ADT DEQUEUE.

Temeljne operacije s dvostranim redom - DEQUEUE su:

front() - vraa element na glavi liste
push_front(el) - stavlja element el na glavu liste
pop_front() - skida element s glave liste
back() - vraa element s kraja liste
push_back(el) - stavlja element el na kraj liste
pop_back() - skida element s kraja liste
find(el) - vraa pokaziva na traeni element ili null ako element nije u listi
delete(el) - brie element el ako postoji u listi
for_each(fun) - primjenjuje funkciju fun na sve elemente liste
size() - vraa broj elemenata u listi
empty() - vraa nenultu vrijednost ako je red prazan, inae vraa 0

Cilj je izvriti generiku implementaciju ADT DQUEUE, tako da se on moe primijeniti na bilo koji tip
elemenata reda. Zbog toga e se u implementaciji ADT-a koristiti sljedee strukture podataka:



Ovo e biti napravljeno na laboratoriskim vjebama!






39



typedef struct dnode {
void *elem;
DNode *prev;
DNode *next;
}DNode;

typedef struct dequeue Dequeue;
typedef struct dequeue *DEQUEUE;

typedef int (*CompareFuncT)(void *, void *);
typedef void *(*CopyFuncT)(void *);
typedef void (*FreeFuncT)(void *);


struct dequeue {
DNode *front; /*pokaziva prednjeg vora liste */
DNode *back; /*pokaziva stranjeg vora liste */
int size; /*broj elemenata u listi */
CompareFuncT CompareElem; /*funkcija za usporedbu elemenata */
CopyFuncT CopyElem; /*funkcija za kopiranje i alociranje*/
FreeFuncT FreeElem; /*funkcija za dealociranje elementa */
};
vor liste je opisan strukturom DNode. Sadri dva pokazivaa koji pokazuju na prethodni i sljedei vor
liste te pokaziva na element liste koji je deklariran kao void*. Tom pokazivau se moe pridijeliti adresa
bilo koje strukture podataka, ali void pokazivai ne sadre informaciju o operacijama koje se mogu provoditi
s podacima na koje oni pokazuju. Zbog toga e biti potrebno da korisnik definira tri pokazivaa na funkcije;
o CompareElem - pokaziva funkcije za usporedbu dva elementa (poput strcpy),
o CopyElem - pokaziva funkcije za alociranje i kopiranje elementa (poput strdup) i
o FreeElem - pokaziva funkcije za dealociranje memorije koju element liste zauzima (popot free).
40
Ovi se pokazivai biljeei u strukturi Dequeue, koja je temelj za definiranje ADT DEQUEUE. Tip ovih
funkcija je definiran s tri typedef definicije. U structuri Dequeue biljee se i pokazivai na prednji (front) i
stranji element liste (back) te broj elemenata u listi (size). Sadraje ove strukture se odreuje pri
inicijalizaciji ADT-a funkcijom dequeue_new(). Ta i ostale funkcije opisane su u datoteci "dequeue.h".

Datoteka specifikacije - "dequeue.h":
typedef struct dequeue *DEQUEUE;

typedef int (*CompareFuncT)(void *, void *);
typedef void *(*CopyFuncT)(void *);
typedef void (*FreeFuncT)(void *);

DEQUEUE dequeue_new(CompareFuncT Compare, CopyFuncT Copy, FreeFuncT Free);
/* Konstruktor ADT DEQUEUE
* Free - pokaziva funkcije za dealociranje elementa liste
* Copy - pokaziva funkcije za alociranje elementa liste
* Compare - pokaziva funkcije za usporedbu elemenata liste
* vraa 0 ako su elementi jednaki,
* <0 ako je prvi element manji od drugoga
* >0 ako je prvi element vei od drugoga
* Ako su svi pokazivai funkcija jednaki nuli,
* tada se podrazumijeva se da je element liste cijeli broj.
* Primjer koritenja:
* Za rad s listom stringova konstruktor je
* DEQUEUE d = dequeue_new(strcmp,strdup, free);
* Za rad s listom cijelih brojeva konstruktor je
* DEQUEUE d = dequeue_new(0,0,0);
* i tada se void pokazivai tretiraju kao cijeli brojevi
*/

void dequeue_free(DEQUEUE d); /* Destruktor DEQUEUE d */

int dequeue_size(DEQUEUE d); /* Vraa veliunu DEQUEUE d*/

41
int dequeue_empty(DEQUEUE d);
/* Vraa 1 ako je DEQUEUE d prazan, inae vraa 0*/

void *dequeue_front(DEQUEUE d);
/* Vraa pokaziva elementa na glavi DEQUEUE d
* ili cijeli broj ako lista sadri cijele brojeve
*/
void dequeue_push_front(DEQUEUE d, void *el);
/* Stavlja element el na glavu DEQUEUE d*/

void dequeue_pop_front(DEQUEUE d);
/* Skida element s glave DEQUEUE d*/

void *dequeue_back(DEQUEUE d);
/* Vraa pokaziva elementa kraja DEQUEUE ili cijeli broj ako lista sadri int */

void dequeue_push_back(DEQUEUE d, void *el);
/* Stavlja element el na kraj DEQUEUE d*/

void dequeue_pop_back(DEQUEUE d);
/* Skida element s kraja DEQUEUE d*/

void *dequeue_find(DEQUEUE d, void *el);
/* Vraa pokaziva elementa liste koji je jednak elementu el */

int dequeue_delete(DEQUEUE d, void *el);
/* Brie element el. Ako postoji vraa 1, inae vraa 0 */

void dequeue_for_each(DEQUEUE d, void (*func)(void *));
/* Primjenjuje funkciju func na sve elemente DEQUEUE d*/


42
Zadatak: Generiki pristup koji je koriten za definiranje ADT DEQUEUE moe se iskoristiti za definiranje
ADT koji slui za rad sa skupovima. Definirajte ADT SET kojim se, kao i u matemetici, moe raditi sa
skupovima pomou sljedeih operacija:



ADT SET
empty(S)
size(S)
insert(S, x)
member(S, x)
delete(S, x)
for_each(S, fun)
intersection(S
1
, S
2
)
union(S
1
, S
2
)
difference(S
1
, S
2
)

- vraa nenultu vrijednost ako je S prazan skup
- vraa broj elemenata u skupu S, tj. vraa |S|
- stavlja element x u skup S, ako x S. Nakon toga je x S
- vraa true ako je x S
- brie element x ako postoji u skupu S
- primjenjuje funkciju fun na sve elemente skupa S
- vraa skup S koji je jednak presjeku skupova S
1
S
2
- vraa skup S koji je jednak uniji skupova S
1
S
2
- vraa skup S koji je jednak razlici skupova S
1
\ S
2




Za implementaciju ovih operacija dovoljno je koristiti jednostruko vezanu listu.

43
Zadatak: esto se kolekcija elemenata, koja, za razliku od skupa, moe sadravati vie istovrsnih elemenata,
naziva vrea (eng. BAG). Realizirajte ADT BAG pomou vezane liste. Svakom elementu liste pridodajte
atribut kojeg se obino naziva referentni izbroj (eng. referent count). Primjerice, moete za vor liste
koristiti sljedeu strukturu:
struct bag_node {
int ref_count;
void *element;
struct bag_node *next;
}
Referentni izbroj pokazuje koliko je istih elemenata u vrei.
Realizirajte generiki ADT BAG koji podrava sljedee operacije:
ADT BAG
empty(B)
size(B)
insert(B, x)





find(B, x)

delete(B, x)




- vraa nenultu vrijednost ako je vrea prazna
- vraa broj elemenata u vrei
- stavlja element x u vreu B po sljedeem algoritmu:
ako u vrei ve postoji element vrijednoti x, tada
uveavaj x.ref_count
inae,
kopiraj i alociraj element x u vreu,
postavi x.ref_count na vrijednost 1.
- vraa vrijednost x.ref_count koji znai koliko ima istovrsnih
elemenata vrijednosti x, ili 0 ako element x nije u vrei
- brie element x ako postoji u vrei, po sljedeem algoritmu:
smanji x.ref_count za jedan.
ako je x.ref_count jednak nuli, tada
dealociraj memoriju koju zauzima x
vrati x.ref_count

44
18.9 Zakljuak

Vezana lista je dinamika strukutura. Poveava se i smanjuje prema zahtjevima korisnika.

Liste su kolekcije sa sekvencijalnim pristupom, za razliku od nizova koje se koristi kao kolekcije sa
sluajnim pristupom.

Apstraktno, liste predstavljaju kolekciju elemenata. Operacije umetanja i brisanja elemenata kolekcije
provode se jednostavnije s vezanom listom, nego je to sluaj s nizovima. Umetanje i brisanje elementa unutar
jednostruko vezane liste je relativno spora operacija jer se u njoj mora odrediti i poloaj elementa koji
prethodi mjestu umetanja. Znatno je bre umetanje elemenata na glavi liste.

Vezane liste se mogu slikovito usporediti s vlakom kojem su vagoni povezani jedan za drugim. Iz jednog
vagona se moe prijei samo u susjedni vagon. Vagoni se mogu dodavati tako da se spoje na postojeu
kompoziciju ili da se umetnu unutar kompozicije vlaka. Dodavanje vagona je jednostavna operacija, a
umetanje vagona unutar kompozicije je sloena operacija.

Kada je nuno vriti esto umetanje i brisanje vorova unutar liste, tada je povoljno koristiti dvostruko
vezanu listu.

Koritenjem tehnike definiranja apstraktnih tipova pokazano je da se pomou liste mogu efikasno
realizirati apstraktni tipovi STACK, QUEUE i DEQUEUE. U implementaciji ADT DEQUEUE pokazano je
kako se mogu stvarati generike kolekcije podataka.

You might also like