2 Modul 2 - (Länkade) Listor 2.1 Jämförelse mellan "verkliga" och "virtuella" listor Verklig Ta ett blankt papper Skriv en punkt Lägg till fler punkter Läser vad som står vid en punkt Stryker över/suddar en punkt Kollar hur mycket som skrivits in på pappret. Kolla om listan är tom. Leta efter en viss punkt Slänger papperet när listan inte behövs längre. Virtuell Skapa ny lista (exempelvis som ett objekt av en klass) Lägg till ett element Som ovan Hämtar elementet på en viss position i listan Ta bort ett element från listan på en viss position. Fråga listan hur många element som finns inlagda. Fråga listan om den är tom. Sök i listan efter ett visst element. Raderar hela listan. 2.1.1 Hur skulle dessa funktioner kunna översättas till operationer för en lista som ska implementeras som en klass i C++? Här är en första grov skiss. class List public: List(); ~List(); //Lägg till ett element void insert(element e); //konstruktor, Skapa ny lista //destruktor, Raderar hela listan. // Hämtar elementet på en viss position i listan Element getelement(int position); // Ta bort ett element från listan på en viss position void remove(int position); // Fråga listan hur många element som finns inlagda int size(); ; // Fråga listan om den är tom bool isempty(); //o s v private: //...
2.2 Länkade listor Länkade listor: är dynamiska (blir större el. mindre vid behov) är uppbyggda av noder som innehåller data plus en pekare. kräver sekventiell genomgång tar aldrig mer plats än nödvändigt kräver extra utrymme i noden för en pekare som kan peka på nästa nod. data data data. pekare pekare pekare
2.3 insert Ofta vill man kunna sätta in ett element på vilken position som helst i en lista. insert(position, item) (OBS! Pseudokod) Allmänt Lägger till elementet "item" på position "position" i listan. Förutsättning "position" måste vara en giltig position i listan. Vilka positioner är giltiga? Det måste finnas ledigt utrymme i listan om den är begränsad. Är det vanligt att en länkad lista är begränsad? Anmärkning: Dessa förutsättningar utgör egentligen förvillkor till operationen listinsert. Ni har stött på förvillkor i kursen Programmeringstenik. Vi ska nu börja tillämpa kontrakt, där förvillkor och eftervillkor ingår, mer systematiskt. Effekt Resultatet är att det nya elementet "item" lagts in på positionen "position". Det element som eventuellt befann sig där tidigare samt efterföljande element har förskjutits en position upp. Anmärkning: Den effekt som beskrivs kan endast garanteras såvida förutsättningarna är uppfyllda enligt ovan. Detta utgör eftervillkoret. Exempel 1 Exempel på listinsert. Före: mjölk, ägg, smör, äpplen, bröd, kyckling Operation: listinsert(4, nötter) Efter: mjölk, ägg, smör, nötter, äpplen, bröd, kyckling
2.4 Olika implementationer av listor Om vi talar om en lista generellt har detta inget att göra med hur den kan representeras. En ADT lista säger heller inget om hur listan ska representeras. ADTn säger att det finns en abstrakt datatyp som heter lista och denna har alltid vissa operationer knutna till sig. Operationer som kan utföras på listan. Om vi ska representera en lista som mjukvara säger vi att vi ska implementera en lista. Det finns olika sätt att implementera en lista som en mjukvarumodul. Nedan beskrivs några specifika implementationer för att representera en lista som mjukvara. Array - Fördelar - Lätt att implementera - Effektiv för sortering, sökning, hämta element. Varför? - Nackdelar - Ineffektiv för att lägga till och ta bort element. Varför? - Bestämd storlek (innebär overhead och problem vid full lista) - En del minne går outnyttjat. Länkad lista - Fördelar - Dynamisk, d v s växer vid behov, inga problem med att listan blir full. Enda begränsningen är det minne som finns tillgängligt i datorn. - Inget outnyttjat minne. - Effektiv för att lägga till och ta bort element. Varför? - Nackdelar - Liten overhead för pekare till varje nod, detta är dock i de flesta fall försumbart. - Lite svårare att implementera, minneshanteringen måste skötas manuellt. - Ineffektiv för sortering, sökning, hämta element. Varför? Fil - direktindexering (på något lagringsmedium) - Fördelar - Dynamisk. Ingen overhead annat än klusterstorlek (minsta skrivbara enhet på disken.) - Bestående, d v s den finns kvar på mediet då programmet avslutats. - Nackdelar - Alla operationer är hemskt ineffektiva. Varför? 2.4.1 Användande av listor i mjukvara Vilken implementation av listan ska man välja? Det beror av de behov man har. Exempel på frågeställningar är: Är det OK om listan är begränsad eller inte? Vilka operationer ska utföras mest? Man vill ju ha ett så effektivt användande som möjligt. Måste listan kunna sparas på ett beständigt medium? Nästan aldrig väljer man en filbaserad lista som är direktindexerad eftersom man måste accessa filen för varje operation. I stället är det troligt att man tillhandahåller funktionalitet i gränssnittet för listan för att kunna skiva/läsa hela listan på en gång till/från filen.
2.4.2 Gränssnittet för en lista Gränssnittet för en lista är det som klienten ser då denne ska använda listan. Då vi använder C++ för att implementera listan motsvaras gränssnittet av de operationer som finns i klassen under public-delen. Oavsett hur listan är implementerad ska gränssnittet se likadant ut. Om jag som klient använder listan är jag ointresserad av hur den är implementerad. Representationen av listan ska inte lysa igenom då man tittar på de operationer man kan använda!
Exempel 2 Allokering och borttagning av en nod i en länkad lista class Node friend class List; ; private: int data; Node* next; class List public: //... void addelement(int element); void removeelement(int position); //... private: Node* getnode(int position); Node* head; //Pointer to the first node in the list. int size; ; void List::addElement(int element) Node* newnode = new Node(); //create a new node newnode -> data = element; //add element to the node //Insert the node wherever you want in the list. //I recommend you to always insert the node first in the //list. //newnode -> next should be set to point to the next //node in the list. size += 1; void List::removeElement(int position) //Find the position of the node you want to remove. Node *nodetoremove = getnode(position); //Connect the nodes before and after the node to remove. delete nodetoremove; nodetoremove = 0; Angående klassen Node Ett objekt av klassen Node utgör en nod i den länkade listan. Om listan innehåller t ex 5 element kommer listan att bestå av 5 noder som är länkade. Därför innehåller varje nod en next-pekare (Node* next;) som kan peka på nästa nod. Förutom pekaren finns också en
variabel (int data;) som ska hålla elementet. I exemplet ovan betyder det att listan lagrar heltal. Klassen Node ser lite speciell ut i och med att den inte har några operationer, men det beror på att den endast existerar för att utgöra just en nod i en länkad lista, den ska inte användas till något annat. Därför kan den länkade listan istället få tillgång till de privata medlemmar som finns i noden. Denna tillåtelse får listan tack vare deklarationen friend class List; i klassen Node. Du behöver inte förstå varför just nu. Du behöver heller inte förstå hur friend fungerar. Acceptera bara tills vidare att klassen List kommer åt de privata medlemmarna i klassen Node. Det är endast för klassen Node som vi kommer att använda denna konstruktion m h a friend. Du kommer inte att vid något tillfälle behöva nyttja friend då du skriver dina klasser.
2.5 Funktioner för länkade listor 2.5.1 insert / add / sätt in / lägg till före efter ny nod ny nod 2.5.2 delete / remove / ta bort / före efter nod som ska bort 2.5.3 leta / find / search / look-up / hitta tillfällig pekare sökt element
2.6 Listhuvud För att kunna nå en länkad lista måste man veta var den börjar. Man sätter ett "handtag" på första elementet och detta handtag brukar kallas listhuvud eller head. Detta är en pekare till första elementet i listan. Listhuvudet pekar alltid på första elementet i listan. data data data head next next next
2.7 Insättning av en nod Algoritm: 1. Hitta rätt plats för insättning 2. Skapa en ny nod och lägg ny data i den 3. Koppla in den nya noden i listan genom att flytta pekare. En tillfällig pekare som pekar på den nod som föregår platsen där den nya noden ska sättas in behövs. Här kallas den prev, som är en förkortning av previous. head 5 10 100 next next next 7 prev next newptr newptr->next = prev->next; prev->next = newptr;
2.8 Insättning av en nod (forts.) Insättning först i listan är ett specialfall. head 5 100 next next 3 next newptr->next = head; head = newptr; newptr
2.9 Borttagning av en nod För borttagning krävs två tillfälliga pekare cur (current) och prev (previous). Algoritm: 1. Hitta noden som ska tas bort 2. Koppla ifrån noden genom att flytta pekare 3. Frigör minnet som användes av noden. bort head 5 8 10 next next next prev cur prev->next = cur->next; delete cur; cur = 0;
2.10 Borttagning av nod (forts.) Borttagning av första noden är ett specialfall. head 5 10 next next next cur head=head->next; delete cur; cur = 0; Eftersom denna operation påverkar head-pekaren får man se till att inte tappa bort den.
2.11 Visning av en listas innehåll Visningen av en listas innehåll är en funktion som varje klient får implementera själv. Det är inte generellt att inkludera en sådan funktion som en del av listans gränssnitt eftersom man måste utnyttja en bestämd utdataström, t ex cout. Exempel //=========================================== // displaylist: visar innehållet i en länkad lista // pre: true // post: Alla element i listan "list" är visade. //------------------------------------------------------------------ void displaylist(list& list) for(int i = 1; i <= list.size(); i += 1) cout << list.getelement(i) << endl;
2.12 Varianter på länkad lista Förutom den "vanliga" länkade listan som är enkellänkad finns två vanliga varianter: Dubbellänkad lista Cirkulär lista (enkel- eller dubbellänkad) Läs kursivt om dessa i boken. Vad har en dubbellänkad lista för fördel gentemot en enkellänkad lista?
2.13 Exempel på en enkel arraybaserad lista implementerad m h a en klass /****************List.h*******************/ class List public: //Pre: true //Post: An empty list has been created List(); //Pre:!isFull() //Post: 'element' has been added to the end of the list void add(int element); //Pre: 1 <= position <= size() //Post: result = the element at position "position" int getelement(int position); //Pre: true //Post: result = true if the list is full,false otherwise bool isfull(); //Pre: true //Post: result = the size of the list int size(); ; private: enumsize = 100; // used to initialize the size of the // array int list_elements[size]; int _size; /****************List.cpp*******************/ #include "List.h" List::List() _size = 0; void List:: add(int element) list_elements[_size] = element; _size += 1; int List::getElement(int position) return list_elements[position - 1]; bool List::isFull()
return (_size == SIZE); int List::size() return _size; /****************driver.cpp*******************/ #include "List.h" //Drivrutin för att lägga till ett element //Pre: true //Post: If the list "alist" was not full, an element selected by the //user has been added to it. void addelement(list& alist) int element = getuserinput(); if(!alist.isfull()) //Kontrollera förvillkoret alist.add(element); //Drivrutin för att skriva ut listan //Pre: true //Post: The elements of the list "alist" has been displayed. void printlist(list& alist) //print the list for(int i = 0 ; i < mylist.size(); i++) cout << mylist.getelement(i) << endl; int main() List mylist; int userchoice; do printmenu(); userchoice = matain(); switch(userchoice) case 1: addelement(mylist); break; case 2: printlist(mylist); break; //... while(...); return 0;