Tietorakenteet ja algoritmit Elegantti toteutus funktiolle insert_to_list_end Alkion lisäys sisällön mukaan järjestettyyn listaan (insert_to_list) Linkatun listan yleisyys alkiotyypin suhteen source-tasolla Linkatun listan yleisyys alkiotyypin suhteen konekielitasolla Pinon toteutus linkattuna rakenteena Jonon toteutus linkattuna rakenteena Muita linkattuja rakenteita 1
Elegantti toteutus funktiolle insert_to_list_end /* The most efficient and simple iterative solution. There is no special treatment for the first element */ void insert_to_list_end_3(tlist *list, Titem data) { Tpointer new_node, *aux; new_node = (Tpointer) malloc(sizeof(tnode)); new_node->item = data; new_node->next = NULL; aux = list; while (*aux) aux = &((*aux)->next); *aux = new_node; Tässä listaa edustaa edelleen yksi osoitin. 2
How to insert an item into an ordered linked list // Määrittelyt typedef char Titem; typedef struct node *Tpointer; typedef struct node { Titem item; Tpointer next; Tnode; typedef Tpointer TorderedList; // Initialisointi void initialize (TorderedList *plist) { *plist = NULL; // Iteratiivinen versio 2 void insert_to_list(torderedlist *plist, Titem data) { Tpointer new_node; Tpointer *pointer; new_node = (Tpointer) malloc(sizeof(tnode)); new_node->item = data; pointer = plist; while( *pointer!= NULL && data > (*pointer)-> item ) pointer = &((*pointer) ->next); new_node->next = *pointer; *pointer = new_node; // Iteratiivinen versio 1 void insert_to_list(torderedlist *plist, Titem data) { Tpointer new_node, pointer; new_node = (Tpointer) malloc(sizeof(tnode)); new_node->item = data; if (*plist == NULL data < (*plist)-> item ) { new_node -> next = *plist; *plist = new_node; else { pointer = *plist; while( pointer->next!= NULL && data > pointer->next-> item ) pointer = pointer ->next; // pointer osoittaa nyt uutta edeltävää nodea new_node->next = pointer->next; pointer->next = new_node; 3
Linkatun listan source-tason yleisyys 1 Kaikki toteutetut listan funktiot ovat riippumattomia alkiotyypistä sourcekooditasolla (poikkeuksena print_list funktio, katso seuraava sivu). Esimerkki. Merkkien sijasta pitäisi listata henkilöitä. Henkilötietoja ovat nimi, ikä ja paino. Määritellään henkilötiedot sisältävä tietue Thlo kuten aikaisemmin: typedef struct { char nimi[31]; int ika; float paino; Thlo; Jos nyt määritellään typedef Thlo Titem; Niin kaikki listan toteutuksen kohdat pysyvät identtisinä (solmun ja listan tietomäärittelyt sekä funktioiden toteutukset). 4
Linkatun listan source-tason yleisyys 2 Poikkeuksena on siis print_list funktio, jossa on vikana se, että yksittäistä alkiota käsiteltäessä on käytetty hyväksi tietoa sen sisäisestä esityksestä. Tämä yleisyyden pilaaminen olisi voitu estää pitämällä myös alkiota ADT:na ja tulostamalla se operaatiofunktiolla print_item (printf, scanf, vertailut ja sijoitus ongelmallisia). Edellä kuvattu toteutuksen yleisyys alkiotyypin suhteen tarkoittaa sitä, että toteutus on source-kooditasolla riippumaton alkiotyypistä. Uusi lista saadaan määrittelemällä uusi alkiotyyppi ja kääntämällä listan toteutus uudelleen. Jos samassa ohjelmassa tarvitaan kahta listaa eri alkiotyypeille, tarvitaan listan operaatiofunktioista kaksi kopiota, joissa erona on vain funktionimet ja tyyppinimet parametrilistassa (C++:ssa riittää eroksi funktioiden ylikuormituksen takia vain erot tyyppinimissä). 5
Linkatun listan source-tason yleisyys 3 Esimerkki. // Merkkien lista typedef struct char_node { char item; //merkkien lista struct char_node *next; Tchar_node; typedef Tchar_node *Tchar_node_pointer; typedef struct { Tchar_node_pointer first; Tchar_node_pointer last; Tchar_list; // Henkilöiden lista // Muutokset sinisiin kursiivikohtiin 6
Linkatun listan source-tason yleisyys 4 Tämän jälkeen tarvitaan uudet kopiot funktioista, joissa siis vain nimimuutoksia. Kaikki muutokset ovat koneellisia ja ne voidaan tehdä seuraavilla tavoilla: 1) Copy/Paste ja Search/Replace toiminnalla 2) Tekemällä makro, joka generoi toteutuksen uudelle listalle 3) Tekemällä kaikki operaatiofunktiot template funktioiksi, jolloin kääntäjä generoi funktiototeutukset automaattisesti (C++). 4) Määrittelemällä koko lista ns. template-luokaksi, jolloin kääntäjä generoi automaattisesti myös tietomäärittelyt (C++). Huomautus. Tämä on sama asia, joka käytiin läpi taulukolla toteutetun listan yhteydessä. 7
Linkatun listan binääritason yleisyys 1 Toinen tapa toteuttaa yleisyyttä on säilöä itse tiedon sijasta tiedon osoittimia. Silloin linkatun listan solmuun tallennettavan tiedon tyyppi on void*. Tällaisen toteutuksen etuna on, että sama ja yksi objektikoodi listan toteutuksesta riittää. Haittana on, että sovelluksessa pitää erikseen huolehtia säilöttävästä tiedosta, koska se ei tallennu säiliöön. Esimerkki seuraavalla sivulla. 8
Linkatun listan binääritason yleisyys 2 Esimerkki. typedef void *Titem; //Titem on nyt geneerinen osoitin //Listan interface typedef struct node *Tpointer; typedef struct node { Tämä on nyt ainoa Titem item; Tpointer next; Tnode; typedef struct { Tpointer first; Tpointer last; Tlist; void initialize_list (Tlist *list); void insert_to_list_end(tlist *list, Titem data); void print_list (Tlist list); void cleanup_list(tlist *list); muutos joka tarvitaan osassa 9 esitettyyn listan toteutukseen!! Näiden funktioiden toteutuksetkaan osan 9 sivuilla 11 12 eivät muutu!! (Vain se print_list oli ongelma.) 9
Linkatun listan binääritason yleisyys 3 Esimerkki edellisellä sivulla olevan listan käytöstä: int main (void) { Tlist hlolist, charlist; Thlo hlo; // handle Tchar chr; // handle initialize_list(&hlolist); initialize_list(&charlist); hlo = CreateHlo( Matti, 23, 62.0); chr = CreateChar( a ); insert_to_list_end(&hlolist, hlo); insert_to_list_end(&charlist, chr); hlo = CreateHlo( Maija, 22, 51.0); chr = CreateChar( b ); insert_to_list_end(&hlolist, hlo); insert_to_list_end(&charlist, chr); C-kääntäjä tekee tyyppimuunnokset (void*)hlo (void*)chr Tilanne merkkilistan osalta muistissa on seuraava: a b Huomautus. Esimerkkiin on yhdistetty myös handle ajattelu. 10
Linkatun rakenteen ja taulukon vertailu Ominaisuus Muistin varaus Tiedon haku Järjestyksen ilmaisutapa Linkattu lista Ei ylivuotoa. Kaikki koneen vapaa muisti on käytettävissä. Ei myöskään siirtoja. Vain peräkkäishaku mahdollista. Järjestys osoitetaan linkeillä. Taulukko Muisti varattava kokonaan ennen kuin käyttö alkaa, jolloin muisti voi loppua kesken tai sitä on varattuna liikaa (tiedon siirto) Myös suorahaku on mahdollista, kun järjestysnumero tiedetään. Järjestys ilmaistaan sijainnilla muistissa. Operaatioiden vaatima prosessoriteho Lisäykset väliin ja poistot välistä ovat helppoja. Lisäyksissä ja poistoissa väliin tarvitaan tietojen siirtoa. Ohjelmointi Vaatii vähän harjoittelua. On helppoa. 11
Pinon toteutus dynaamisesti linkatulla listalla 1 Myös pino voidaan toteuttaa dynaamisesti linkatulla listalla. Etuna on, että tietojen siirtoa ei tarvita tilankasvatuksessa ja tilaa riittää. Toteutus saadaan tehokkaaksi kun linkatussa listassa alku ajatellaan pinon huipuksi. Silloin lisäys huipulle ja huipulta poisto saadaan molemmat tehokkaiksi ja pinon esittämiseen tarvitaan vain yksi osoitin. Solmun määrittelyt pidetään kuten ennen ja tyyppi Tstack määritellään seuraavasti: typedef Tpointer Tstack; void initialize_stack(tstack* stack); void push((tstack* stack, Titem item); Tboolean pop((tstack* stack, Titem* item); Esimerkkitoteutukset funktioille initialize_stack ja push ovat seuraavalla sivulla. 12
Pinon toteutus dynaamisesti linkatulla listalla 2 void initialize_stack(tstack* stack) { *stack = NULL); void push(tstack* stack, Titem data) { Tpointer newnode; newnode = (Tpointer) malloc(sizeof(tnode)); newnode->item = data; newnode->next = *stack; *stack = newnode; Huomautus 1. Sama funktio toimii myös vaikka pino olisi tyhjä. Huomautus 2. Täydellisessä ratkaisussa olisi testi muistin varauksen onnistumisesta. Epäonnistumisen yhteydessä push palauttaisi siitä tiedon (esim NOT_OK). b a c 13
Jonon toteutus dynaamisesti linkatulla listalla 1 Jonokin voidaan toteuttaa dynaamisesti linkatulla listalla. Etuna nytkin on, että tietojen siirtoa ei tarvita tilankasvatuksessa eikä muulloinkaan ja tilaa riittää. Toteutus saadaan tehokkaaksi, kun pidetään yllä ensimmäisen ja viimeisen solmun osoitetta. Silloin enqueue operaatio on kuten insert_to_list_end aikaisemmassa esimerkissämme. Operaatio dequeue taas on kuin operaatio pop pinolle sillä erotuksella, että ensimmäisen solmun osoite on tietueessa first kentässä. Solmun määrittelyt pidetään kuten ennen ja tyyppi Tqueue määritellään seuraavasti: typedef struct {Tpointer first; Tpointer last; Tqueue; void initialize_queue(tqueue* queue); void enqueue((tqueue* queue, Titem item); Tboolean dequeue((tqueue* queue, Titem* item); Esimerkkitoteutukset funktioille initialize_queue ja dequeue ovat seuraavalla sivulla. 14
Jonon toteutus dynaamisesti linkatulla listalla 2 void initialize_queue(tqueue* queue) { queue->first = NULL; queue->last = NULL; Tboolean dequeu(tqueue* queue, Titem* data) { Tpointer aux; if (queue->first!= NULL) { *data = queue->first->item; aux = queue->first; queue->first = aux -> next free aux; return OK; return NOT_OK; a b c 15