lekc18 lista slides

44
 1 18 Samoreferentne strukture i liste Naglasci:  samoreferentne strukture   jednostruk o vezana li sta  dvostruko vezana lista  operacije s listom  sortirane liste  implementacija ADT STACK pomoću jednostruko vezane liste  implementacija ADT QUEUE pomoću jednostruko vezane liste  implementacij a ADT DEQUEUE pomoću dvostruko vezane liste

Upload: damir-semanovic

Post on 13-Oct-2015

15 views

Category:

Documents


0 download

DESCRIPTION

Lista

TRANSCRIPT

  • 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 #include #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 #include #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 #include #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 #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 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(S1, S2) union(S1, S2) difference(S1, S2)

    - 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 S1 S2 - vraa skup S koji je jednak uniji skupova S1 S2 - vraa skup S koji je jednak razlici skupova S1 \ S2

    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.