Föreläsning 6: Introduktion av listor



Relevanta dokument
Föreläsning 4: Poster

Tommy Färnqvist, IDA, Linköpings universitet

TDIU01 - Programmering i C++, grundkurs

6 Rekursion. 6.1 Rekursionens fyra principer. 6.2 Några vanliga användningsområden för rekursion. Problem löses genom:

Föreläsning 7. Träd och binära sökträd

Föreläsning 5: Introduktion av pekare

Tentamen Grundläggande programmering

Träd, binära träd och sökträd. Koffman & Wolfgang kapitel 6, avsnitt 1 4

Objektorienterad programmering i Java

Programmering för språkteknologer II, HT2014. Rum

Tentamen OOP

Exempel på ett litet Ada-program

Sätt att skriva ut binärträd

Föreläsning 3.1: Datastrukturer, en översikt

5 Grundläggande in- och utmatning

Tentamen Programmeringsteknik II och NV2 (alla varianter) Skriv bara på framsidan av varje papper.

Dugga Datastrukturer (DAT036)

Föreläsning 13 och 14: Binära träd

Uppgift (poäng) 1 (2) 2 (3) 3 (4) 4 (4) 5 (3) 6 (4) 7 (6) 8 (6) 9 (8) Summa

Föreläsning 2 Objektorienterad programmering DD1332. Typomvandling

Att använda pekare i. C-kod

Tentamen TEN1 HI

Uppgift 1 ( Betyg 3 uppgift )

Programmeringsteknik med C och Matlab

Övningar Dag 2 En första klass

Algoritmanalys. Genomsnittligen behövs n/2 jämförelser vilket är proportionellt mot n, vi säger att vi har en O(n) algoritm.

Sockets: server. with Ada.Command_Line; use Ada.Command_Line; with Ada.Exceptions; use Ada.Exceptions; with Ada.Text_IO; use Ada.

Objektorienterad programmering D2

Grunderna i stegkodsprogrammering

Grundläggande programmering med C# 7,5 högskolepoäng

TDDC76 - Programmering och Datastrukturer

Tentamen'('Datastrukturer,'algoritmer'och'programkonstruktion.'

Repetition i Python 3. Exemplen fac. Exemplen fac motivering. Exemplen fac i Python

Objektsamlingar i Java

Omtentamen (del 1, 6 högskolepoäng) i Programkonstruktion och datastrukturer (1DL201)

Visual Basic, en snabbgenomgång

Omgivningar. Omgivningar är viktiga eftersom de avgör vilka namn som är synliga och därmed dessas innebörd och de värden som är förknippade med dem.

TDDC76 - Programmering och Datastrukturer

Tentaupplägg denna gång

Tentamen för kursen Objektorienterad programvaruutveckling GU (DIT010)

Föreläsning 3-4 Innehåll. Diskutera. Metod. Programexempel med metod

SMD 134 Objektorienterad programmering

TDP004. Minne och pekare. Eric Elfving Institutionen för datavetenskap

Tentaupplägg denna gång

Föreläsning 1 & 2 INTRODUKTION

Tentamen, EDAA20/EDA501 Programmering

Föreläsning 3-4 Innehåll

KOMPLETTERANDE HEMTENTAMEN TDDB53

Grundläggande datalogi - Övning 1

Datastrukturer, algoritmer och programkonstruktion (DVA104, VT 2015) Föreläsning 6

Funktioner, Procedurer och Parametrar. Funktioner, Procedurer och Parametrar... Funktioner, Procedurer och Parametrar procedurspecifikation

1 Funktioner och procedurell abstraktion

Föreläsning 5-6 Innehåll. Exempel på program med objekt. Exempel: kvadratobjekt. Objekt. Skapa och använda objekt Skriva egna klasser

TDDC74 Lab 02 Listor, sammansatta strukturer

Ickelinjära ekvationer

Föreläsning 5-6 Innehåll

Introduktion till algoritmer - Lektion 3 Matematikgymnasiet, Läsåret Lektion 3

Lösningsförslag till tentamen i EDA011/EDA017 Programmeringsteknik för F, E, I, π och N 27 maj 2008

MMA132: Laboration 1 Introduktion till MATLAB

Datalogi, grundkurs 1. Lösningsförslag till tentamen

Föreläsning 4: Kombinatorisk sökning

Textsträngar från/till skärm eller fil

Försättsblad till skriftlig tentamen vid Linköpings Universitet

Föreläsning 10. ADT:er och datastrukturer

Föreläsning 13. Rekursion

Introduk+on +ll programmering i JavaScript

RödGrön-spelet Av: Jonas Hall. Högstadiet. Tid: minuter beroende på variant Material: TI-82/83/84 samt tärningar

if (n==null) { return null; } else { return new Node(n.data, copy(n.next));

Översikt 732G11 PROGRAMMERING 1. Personal. Kursens mål. Litteratur. Kursens innehåll

Tentamen Datastrukturer (DAT037)

Inledande programmering med C# (1DV402) 27+15=42 1 (22)

TDDC74 Programmering, abstraktion och modellering DUGGA 2

Typsystem. Typsystem... Typsystem... Typsystem... 2 *

Länkade strukturer, parametriserade typer och undantag

Exempel på listor (klassen ArrayList). Ett exempel med fält. Avbildning är en speciell typ av lista HashMap.

DSV A Procedurell programmering 5 poäng Tentamen

Typsystem. DA2001 (Föreläsning 23) Datalogi 1 Hösten / 19

Snabbguide Visma Compact API Version 5.1 Copyright Visma Spcs AB Visma Compact API

Datastrukturer. föreläsning 3. Stacks 1

Tentamen: Programutveckling ht 2015

Föreläsning 2. Länkad lista och iterator

Anmälningskod: Lägg uppgifterna i ordning. Skriv uppgiftsnummer (gäller B-delen) och din kod överst i högra hörnet på alla papper

Tentamen Programmeringsteknik II Inledning. Anmälningskod:

OOP Objekt-orienterad programmering

Datalogi, grundkurs 1. Lösningsförslag till tentamen

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

Bygga hus med LECA-stenar

Twincat: PLC Control

SCB :-0. Uno Holmer, Chalmers, höger 2 Ex. Induktiv definition av lista. // Basfall

VHDL och laborationer i digitalteknik

Föreläsning 4. Kö Implementerad med array Implementerad med länkad lista Djup kontra bredd Bredden först mha kö

TENTAMEN PROGRAMMERINGSMETODIK MOMENT 2 - JAVA, 4P

public static void mystery(int n) { if (n > 0){ mystery(n-1); System.out.print(n * 4); mystery(n-1); } }

Översikt. Installation av EasyPHP 1. Ladda ner från Jag använder Release Installera EasyPHP.

KOMPLETTERANDE HEMTENTAMEN TDDB53

Programmering A C# VT Ett kompendie över Programmering A (50p) i c# Stefan Fredriksson

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 8 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Datorlära 3 Octave Workspace ovh mijlö Skriva text på skärmen Värdesiffror Variabler och typer Strängar Makro Vektorer

Problem: BOW Bowling. Regler för Bowling. swedish. BOI 2015, dag 1. Tillgängligt minne: 256 MB

Rekursion. Koffman & Wolfgang kapitel 5

Transkript:

Föreläsning 6: Introduktion av listor Med hjälp av pekare kan man bygga upp datastrukturer på olika sätt. Bland annat kan man bygga upp listor bestående av någon typ av data. Begreppet lista bör förklaras. En lista är en sekvens av data. Man skulle kunna implemetera en lista med hjälp av t.ex. antingen typen fält "array (...) of..." eller type fil "File_Type". Jämförr mellan lista gjord med pekare och lista av fält: + Pekarlistan kan vara godtyckligt lång och man behöver inte dimensionera den från början. Endast primärminnets storlek begränsar längden. + Pekarlistan kan bli längre och kortare beroende på hur många data som skall lagras. Tar alltså lagom mycket minne hela tiden. - Element i pekarlistan kan inte nås direkt (i varianten med fält går detta m.h.a. index) utan måste letas upp. - Man måste skriva underprogram för att kunna kopiera en pekarlista till en annan eller jämföra två pekarlistor. Jämförr mellan lista gjord med pekare och lista av fil: + Pekarlistan ligger i primärminnet vilket ger snabbare åtkomst än filvarianten. + I pekarlistan kan man stoppa in data mellan två andra data vilket inte går enkelt i filvarianten. - Pekarlistan försvinner när programmet avslutas då det finns krav att man lämnar tillbaka dynamiskt bundet minne. Vi väljer i detta fall att arbeta med pekarlistor och det är vanligast att man menar dessa om man bara säger lista. Här följer en figur som visar hur en enkellänkad lista med två data kan se ut. List: Data:???? Data:???? Next: Next:

Man lagrar varje data i en post (man brukar kalla listans poster för element eller noder) och dessutom en pekare till nästa element i listan. Man kan rita detta på ett förenklat sätt. Exempel med tre element i listan.???????????? När man hanterar dessa listor i ett huvudprogram vill man dock inte hålla på med en massa "bökig" pekarhantering. Det man gör är att abstrahera bort detta genom att skapa ett paket som hanterar listor. Hur deklarerar man det som behövs för att det skall motsvara ovanstående bild? Här kommer typdeklarationsdelen. type Element_Type; type List_Type is access Element_Type; type Element_Type is record Data : Integer; Next : List_Type; end record; Deklaration av Element_Type. Definition av List_Type. Definition av Element_Type. Om datat är ett heltal. Om man skall stoppa in detta i ett paket skall man självklart gömma undan hela definitionen av typerna i den privata delen och endast visa List_Type i den publika delen. Hur visar man List_Type utan att visa vad den egentligen "består av". Alternativ 1 (räcker för att gömma undan typen): type List_Type is private; Alternativ 2 (med lite fler restriktioner på vad man kan göra med data av den typen): type List_Type is limited private; Vilka operationer vill man kunna utföra på en lista? Här är några förslag, men beroende på vad man skall använda listan till kan det vara andra saker som är mer väsentliga. 1. Insert - Stoppa in ett data i listan. Man kan tänka sig olika varianter på "Insert". Argument: Listan och datat. 2. Remove - Ta bort ett data ur listan. Man kan tänka sig olika varianter på "Remove". Argument: Listan och någon form av söknyckel (i enklaste fallet själva datat). 3. Delete - Ta bort hela listan. Argument: listan.

4. Find - Leta reda på ett data i listan. Argument: Listan och någon form av söknyckel (i enklaste fallet själva datat). 5. Length - Längden av listan (antalet element i listan). Argument: Listan. Detta skall göras på laborationen så vi tar inte och gör detta nu, men vi kan titta på en av funktionerna för att prova det här med pekarhantering. Vi tar funktionen Length som exempel och löser den på två olika sätt. Iterativt och rekursivt. Det är viktigt att man tar hand om fallet med "tom lista". Om detta inte görs kan man råka ut för att programmet kraschar (exception) och det skall man ju undvika om det är möjligt. Iterativ lösning (rita figurer). function Length(List : in List_Type) return Natural is L : List_Type := List; Len : Natural := 0; while (L /= null) then L := L.Next; Len := Len + 1; end loop; return Len; end Length; Rekursiv lösning (rita figurer). function Length(List : in List_Type) return Natural is if (List = null) then return 0; return 1 + Length(List.Next); end Length; Ett exempel där vi använder oss av iterativ problemlösning för att visa att detta ger en hel del extra att hålla reda på. Vi utför en "Insert" där datat skall hamna sorterat i en redan existerande lista. Listan består av heltal för att göra det så litet som möjligt.

Vi antar att vi har ett huvudprogram som ser ut så här: procedure Main is Deklaration av listtyp... type List_Node; type List_Type is access List_Node; type List_Node is record Data : Integer; Next : List_Type; end record; Definition av proceduren "Insert". Här skall proceduren in senare... Deklaration av variabler... List : List_Type; X : Integer; Kod som utförs innan... for I in 1..10 loop Get(X); Insert(List, X); end loop; Mer kod som gör något... end Main; Hur ser nu "Insert" ut? Vi kan iallafall få fram hur ett procedurskelett ser ut. Sen får vi fundera på hur vi skall lösa problemet. procedure Insert(List : in out List_Type; end Insert; Nu behöver vi rita en hel del figurer. Dels vilka varianter av listor som kan komma in till denna rutin och dels vilka olika data som skall lagras som kommer in. Detta ger en grund till vad som skall göras. Vi ser alltså proceduren som en svart låda som skall utföra något och bryr oss inte om exakt hur den gör detta.

Fall 1: Tom lista och godtyckligt data. Fall 2: Lista med ett element och a) data som skall in först i listan. b) data som skall in efter listans element. Fall 3: Lista med två element och a) data som skall in först i listan. b) data som skall in efter listans första element. c) data som skall in efter listans andra element. Fall 4: Lista med tre element (vilket i många fall är det samma som godtyckligt antal element och a) data som skall in först i listan. b) data som skall in efter listans första element. c) data som skall in efter listans andra element. d) data som skall in efter listans sista element. Hur gör vi detta? Vi tar ett fall i taget och löser det. Fall 1: Fall 2 a: Fall 2 b: New_Node : List_Type; New_Node := new List_Node; New_Node.Data := Data; New_Node.Next := null; end...; New_Node := new List_Node'(Data => Data, Next => List); List := New_Node; OBS! Ej Free på New_Node!!! end...; New_Node := new List_Node'(Data => Data, Next => List.Next); List.Next := New_Node; end...; Fall 3 a: Samma som 2 a. Fall 3 b: Samma som 2 b.

Fall 3 c: New_Node := new List_Node'(Data => Data, Next => List.Next.Next); List.Next.Next := New_Node; end...; Vi ser att det inte kommer att bli roligt att fortsätta så här. Vi försöker hitta ett lite bättre sätt (som löser vårt probem lite mer generellt). Vi inför ett par extra pekare som håller reda på var i listan man befinner sig. Fall 3 b: Current, Next : List_Type; Current := List; Next := List.Next; while (Next /= null) loop if (Data < Next.Data) then Insert_First(Current.Next, Data); Fall 2a Lämnar en rad... Current := Next; Next := Current.Next; end loop; end...; Nu fungerar det ganska bra, men det fallerar när man kommer till slutet av listan (om vi skall stoppa in datat efter alla element i listan). Skulle man kunna lösa detta på en gång? Det går om vi på något sätt håller reda på att vi inte har stoppat in datat i listan. Vi inför en extra variabel till som bara håller reda på detta. Current, Next : List_Type; Data_Inserted : Boolean := False; Current := List; Next := List.Next; while (Next /= null) and (not Data_Inserted) loop if (Data < Next.Data) then Insert_First(Current.Next, Data); Fall 2 a Data_Inserted := True; Current := Next; Next := Current.Next; end loop; if not Data_Inserted then Insert_First(Current.Next, Data); end...;

Om man tittar lite extra på detta ser man att detta inkluderar alla fall då det nya datat inte skall in först i listan. Hur kommer hela proceduren att se ut då? procedure Insert(List : in out List_Type; New_Node : List_Type; Current, Next : List_Type; Data_Inserted : Boolean := False; if List = null then List := new List_Node'(Data => Data, Next => null); elsif Data < List.Data then New_Node := new List_Node'(Data => Data, Next => List); List := New_Node; Current := List; Next := List.Next; while (Next /= null) and (not Data_Inserted) loop if (Data < Next.Data) then New_Node := new List_Node'(Data => Data, Next => Next); Current.Next := New_Node; Data_Inserted := True; Current := Next; Next := Current.Next; end loop; if not Data_Inserted then New_Node := new List_Node'(Data => Data, Next => Null); Current.Next := New_Node; end Insert;

Man kan också upptäcka att det går att modifiera lite till för att slippa lite kod, men det kanske är lättare att förstå koden om man inte gör detta. Vi gör modifieringen iallafall för att se vad som kunde gjorts. procedure Insert(List : in out List_Type; Current, Next : List_Type; if (List = null) or (Data < List.Data) then Datat skall in först i listan. List := new List_Node'(Data => Data, Next => List); Leta rätt på den plats datat skall in. Current := List; Next := List.Next; while (Next /= null) and then (Data >= Next.Data) loop Current := Next; Next := Current.Next; end loop; Stoppa in datat i efterhand. Current.Next := new List_Node'(Data => Data, Next => Next); end Insert; Om vi skapar alla våra underprogram som hanterar listor på detta vis (iterativt) kommer vi att ha samma problem för varje gång vi skall lösa ett nytt problem. Om vi istället nyttjar att vi kan skriva samma sak rekursivt kommer vi att få en mall som vi snabbt kan nyttja till att lösa många olika varianter av problem. Det finns nackdelar med att använda rekursion, men i denna kurs behöver vi inte ta hänsyn till dessa. Under labserien gör vi uppgifterna rekursivt så slipper vi krångla med iterativa lösningar. Övning på lektion Uppgift 1: Antag att vi har en lista med data som är heltal. Hur gör vi för att stoppa in ett nytt heltal först i listan. Vi kan kalla det underprogram vi skapar för Insert_First. Vi löser detta rekursivt. Är det någon som vill lösa detta iterativt så får det bli en hemuppgift. Först måste man komma fram till om det skall vara en procedur eller en funktion man skall göra. I detta fall är det väl lämpligast med en procedur. Vi skall ju påverka den lista vi skickar in. Vi skriver ett procedurskelett:

procedure Insert_First(List : in out List_Type; Här skall det in satser... end Insert_First; Rita upp olika fall av indata och vad som skall komma ut som resultat. Bra fall är "tom lista", "lista med ett element", "lista med två element" och "lista med tre element". Diskutera hur vi skall lösa problemen ett efter ett. Fallet "tom lista" (brukar alla komma på hur man gör): if (List = null) then List := new Element_Type; List.Data := Data; List.Next := null; Inte tom lista. Denna kod kan vi stoppa in direkt i procedurskelettet. Nästa steg är att fylla på med de övriga fallen. Här krävs nog en liten fundering. Efter kort diskussion brukar det lösa sig och de kommer fram till att alla tre fallen egentligen är samma fall. Fallet "ej tom lista": E := new Element_Type; E.Data := Data; E.Next := List; List := E; Här krävs en extra variabel, E (av typen List_Type), för att lösa problemet. Den måste deklareras i proceduren. Efter en liten diskussion kan man dessutom komma fram till att dessa två fall egentligen är samma. Vi modifierar fallet "tom lista" på så sätt att vi använder den lokala variabeln E och vi upptäcker att det blir samma kod som fallet "ej tom lista". Om man vill kan man också införa ett "snabbare" sätt att tilldela den nya posten sina initialvärden. Eller i kortform: E := new Element_Type'(Data => Data, Next => List); E := new Element_Type'(Data, List);

Alltså kan vi ta bort if-satsen och skriva vår funktion i en form som ser ut på följande sätt. procedure Insert_First(List : in out List_Type; E : List_Type; E := new Element_Type'(Data, List); List := E; end Insert_First; Vill man ändra sig lite till så kan man faktiskt till och med ta bort den extra variabeln E. Här kommer sista förslaget: procedure Insert_First(List : in out List_Type; List := new Element_Type'(Data, List); end Insert_First; Uppgift 2: Skriv proceduren som utför en insättning av ett nytt data i en lista, men där kravet är att datat skall sorteras in på rätt plats i listan. Listan skall både före insättning och efter det att nya datat stoppats in vara sorterad så att minsta datat ligger först och alla efterföljande ligger i stigande storleksordning. Förutsättning: Det nya datat får inte finnas i listan sen tidigare. (Hemuppgift?) Vi börjar direkt att skriva ett procedurskelett: procedure Insert(List : in out List_Type; Här skall det in satser... end Insert; Rita upp olika fall av indata och vad som skall komma ut som resultat. Bra fall är "tom lista", "lista med ett element där datat är större än det nya", "lista med ett element där datat är mindre än det nya", "lista med två element där första datat är större än det nya", "lista med två element där första datat är mindre än och andra datat är större än det nya", "lista med två element där båda data är mindre än det nya". Nu börjar diskussionerna igen. Hur gör man i de olika fallen. Vi börjar med fallet "tom lista" (här brukar de inse att det är samma kod som i Insert_First, men inte att de kan anropa den proceduren):

if (List = null) then Insert_First(List, Data); Om man frågar om det finns några fall av de uppräknade som egentligen är samma fall så kommer de antagligen fram till att alla fall som har första datat större än det nya datat kommer att resultera i samma fall. Vi tar alltså hand om alla fall där första datat är större än det nya: elsif (List.Data > Data) then Insert_First(List, Data); Även här kan det vara så att de inte inser att det går att använda samma rutin igen. Frågan är om man inte kunde slå ihop de två första delarna i if-satsen till en enda? Då skulle det väl bli: if (List = null) or (List.Data > Data) then Insert_First(List, Data); Eller fungerar inte det? Nej, det gör det inte! Ada är inte definierat på så sätt att första delvillkorets resultat avgör hela villkoret i if-satsen även om det råkar vara omöjligt att få annat svar efter detta. Hela villkoret kommer att beräknas och då kommer det andra villkoret att ge fel (pekaren pekar ju ingenstans och vi försöker referera ett minnesutrymme som "inte finns"). Den första varianten är alltså bättre. Det är dessutom så att man ökar läsbarheten om man alltid ser till att testa "tom lista" som ett eget fall. Nu går det att lösa problemet ovan i Ada. Man kan skriva if-satsen på följande sätt så att bara första villkoret testas om det är tillräckligt för att avgöra om man skall utföra något eller ej. if (List = null) or (List.Data > Data) then Insert_First(List, Data); De fall som nu finns kvar är de som inte har ett första data som är större än det nya datat. Av dem vi ritat upp är dessa kvar: "lista med ett element där datat är mindre än det nya", "lista med två element där första datat är mindre än och andra datat är större än det nya", "lista med två element där båda data är mindre än det nya". En diskussion med studenterna kan ge att man tar ett av fallen i taget och löser dem, men detta leder till att vi måste lägga till ytterligare ett antal fall som kan ställa till det (t.ex. med tre, fyra eller fem element i listan...). Ett förslag man då kan ge är att man stryker första elementet i listan och tittar på "resten av listan" som en enhet. Man vet ju att det första datat är mindre så det skall ju hamna först. Frågan är bara var vi skall stoppa in det nya datat i "resten av listan". Det kan vara någon som kommer på att man då kan göra ett rekurivt anrop, men om vi antar att det inte är någon kan vi ju säga att vi anropar en annan procedur som klarar av att stoppa in datat på rätt ställe så har vi iallafall skjutit problemet framåt. Vi kompletterar vår kod med: Insert2(List.Next, Data);

Nu har vi inga fler fall att ta hand om. Om det är så att ingen har kommit på att man kan anropa sig själv för att lösa problemet får vi väl börja om från början och fråga vilka olika fall som kan uppstå och när vi ritat upp några fall kan vi referera tillbaka till att det ju är samma fall som tidigare. Alltså: Vi anropar oss själva så är hela problemet löst. Ändra koden så att det blir: Insert(List.Next, Data); Rekursivt anrop. Hela proceduren så att vi ser vad vi gjort: procedure Insert(List : in out List_Type; if (List = null) then Insert_First(List, Data); elsif (List.Data > Data) then Insert_First(List, Data); Insert(List.Next, Data); end Insert; Från ett ganska komplext problem har vi nått fram till en lösning som är lättläst (när man väl vant sig med begreppet rekursions) och dessutom har vi bara två gånger i hela proceduren behövt använda oss av pekarhantering (List.Data respektive List.Next). Dessutom har vi i just detta exempel inget behov av lokala variabler i proceduren. Vi kan visa hur samma procedur skulle ha sett ut om vi inte hade haft rekursion att tillgå.