1(5) Institutionen för datavetenskap LUNDS TEKNISKA HÖGSKOLA Tentamen, EDA011/EDA017 Programmeringsteknik BME C E F I N Pi 2017–05–31, 8.00–13.00 Anvisningar: Preliminärt ger uppgifterna 9 + 13 + 10 + 13 = 45 poäng. För godkänt betyg krävs 22, 5 poäng. Kommentarer från specifikationerna (/** ... */) behöver inte skrivas i lösningen. Tillåtet hjälpmedel: Java-snabbreferens. Lösningsförslag kommer att finnas på kursens hemsida senast dagen efter tentamen. Resultatet läggs in i Ladok när rättningen är klar och anslås inte på anslagstavlan. Tentamensvillkor: Om du tenterar utan att vara tentamensberättigad annulleras din skrivning. För att undvika att någon skrivning annulleras av misstag kommer alla som, enligt institutionens noteringar, tenterat utan att vara tentamensberättigade att kontaktas via epost. Felaktigheter i institutionens noteringar kan därefter påtalas fram till nästa tentamenstillfälle då resterande skrivningar annulleras. 2(5) Bakgrund till uppgift 1–3: om grafer, noder och länkar I figur 1 till höger visas ett exempel på en datastruktur som kallas graf. Grafen består av två slags objekt: noder (ellipser i bilden) och länkar (pilar mellan noder i bilden). Varje nod har ett namn, som ”Dalby”, och varje länk är märkt med en längd (ett heltal, exempelvis 12). I denna bild är alltså pilarna inte vanliga referenser, utan motsvarar ett slags objekt (nämligen länkar). Vi ska strax se hur det fungerar. Bilden visar en vanlig tillämpning för grafer, nämligen geografisk information. Här visas några orter i Lunds omnejd med de inbördes avstånden (i kilometer) angivna: det är 12km från Lund till Södra Sandby, och 4km därifrån till Flyinge. Notera att länkarna är enkelriktade: i vårt exempel finns en Fig. 1: En graf med sex noder och åtta länkar. länk från Lund till Dalby, men inte tvärtom. Om vi vill ange att en väg är dubbelriktad, som mellan Lund och Södra Sandby i bilden, så behövs länkar åt båda hållen. Grafen är ingen komplett karta. I bilden har vi valt att rita noden Flyinge nära Torna Hällestad, men i verkligheten ligger Flyinge mer än en mil därifrån (åt Eslöv till). Vi ska nu införa Java-klasser för noder och länkar. För noder behöver vi hålla reda på ett namn och ett antal länkar, samt ett värde. Nodens värde är ett heltal som vi kommer att få användning för i uppgift 2 och 3. För länkar håller vi reda på länkens destination (en nod) och längd. Klasserna Node och Link beskriver noder respektive länkar, och har följande specifikationer. Klassen Link är färdigimplementerad. (Du kan säkert föreställa dig hur implementationen ser ut.) Node /** Skapar en ny nod med givet namn, utan länkar. Nodens värde är inledningsvis Integer.MAX_VALUE. */ Node(String name); /** Hämtar nodens namn. */ String getName(); /** Sätter nodens värde till v. */ void setValue(int v); /** Hämtar nodens värde. */ int getValue(); /** Lägger till en ny länk från denna nod till en given destination. Länken ska ha längden length. */ void addLink(Node destination, int length); /** Hämtar de länkar som utgår från denna nod. */ ArrayList<Link> getLinks(); /** Hämtar en sträng på formatet "Grönköping: 123", om nodens namn är Grönköping och värdet 123. */ String toString(); Link /** Skapa en ny länk till noden destination, med längden length. */ Link(Node destination, int length); /** Hämtar länkens destination. */ Node getDestination(); /** Hämtar länkens längd. */ int getLength(); 3(5) Exempel: Följande satser bygger upp grafen i figur 1 och skriver ut något om noderna som kan nås från noden med namn ”Dalby”. Node Node Node Node Node Node lund = new Node("Lund"); dalby = new Node("Dalby"); sandby = new Node("Södra Sandby"); hällestad = new Node("Torna Hällestad"); flyinge = new Node("Flyinge"); veberöd = new Node("Veberöd"); lund.addLink(dalby, 12); lund.addLink(sandby, 12); dalby.addLink(veberöd, 11); dalby.addLink(hällestad, 5); sandby.addLink(lund, 12); sandby.addLink(hällestad, 8); sandby.addLink(flyinge, 4); hällestad.addLink(veberöd, 8); System.out.println("anslutningar från " + dalby.getName() + ":"); for (Link link : dalby.getLinks()) { Node m = link.getDestination(); System.out.println(m.toString()); } När dessa satser körs fås utskriften anslutningar från Dalby: Veberöd: 2147483647 Torna Hällestad: 2147483647 Här ser vi att noderna har värdet Integer.MAX_VALUE = 2147483647. Uppgifter 1. Uppgift: Implementera klassen Node. Ledning: Använd en ArrayList för att hålla reda på länkarna. 2. Inför nästa uppgift behöver vi kunna hålla reda på ett antal noder och välja den som har lägst värde. Med värde avses här det heltal som förknippas med en nod, så som beskrivits i bakgrundsavsnittet. NodeSet /** Skapar en tom mängd av noder. */ NodeSet(); /** Lägger till noden node till mängden, om mängden inte redan innehåller en nod med samma namn. */ void add(Node node); /** Returnerar true om det finns fler noder i mängden, annars false. */ boolean more(); /** Väljer ut den nod som har lägst värde och returnerar den. Den returnerade noden tas bort ur mängden. Om mängden är tom returneras null. */ Node pickLeast(); Uppgift: Implementera klassen NodeSet. 4(5) 3. Det är ofta intressant att ta reda på hur lång den kortaste vägen mellan två noder i en graf är. Givet grafen i figur 1 kan vi exempelvis fråga oss hur lång den kortaste vägen från Lund till Veberöd är. För att lösa detta problem ska vi använda Dijkstras algoritm,1 som vi strax ska titta närmare på. Denna utgår från en given nod start, och går ut på att ge varje nod N i grafen ett värde som motsvarar det kortaste avståndet från start till N (antingen direkt, eller via en eller flera andra noder). Om vi använder Dijkstras algoritm med start i Lund får noderna värden enligt figur 2. Här har Veberöd fått värdet 23, eftersom kortaste vägen från Lund till Veberöd är 12 + 11 = 23 (via Dalby). Fig. 2: Grafen i figur 1, med avstånd från Lund utmärkta. Dijkstras algoritm Algoritmen utgår från att alla noder inledningsvis har värdet Integer.MAX_VALUE (enligt uppgift 1). 1. Låt start vara noden vi vill räkna avstånd från. Sätt start:s värde till 0. 2. Låt S vara en mängd av noder. Inledningsvis ska S innehålla en enda nod, nämligen start. 3. Så länge mängden S inte är tom ska följande upprepas: 3.1. Ta ut den nod ur S som har lägst värde. Kalla noden n. 3.2. Gå igenom de länkar som utgår från n. För var och en av dessa länkar, gör följande: • Kalla länkens längd för l och dess destination för d. • Låt a vara summan av n:s värde och l. • Om a är mindre än d:s värde: ändra d:s värde till a, och lägg in d i mängden S. Algoritmen bygger på följande idé. Mängden S innehåller de noder som vi funnit en ny väg till. När vi hittat en väg till en nod n, innebär det att vi kanske även hittat ett kortare avstånd a till dess granne d. I så fall stoppas d in i mängden, så att även dess grannar undersöks på samma sätt. Vårt program Vi utgår från följande program. I klassen Travel finns, förutom main-metoden, en metod dijkstra där algoritmen ovan ska implementeras. I main-metoden antas satserna från kodexemplet på sid. 3 stå, följda av de två satserna nedan. Satsen märkt (1) nedan skriver ut Veberöd: 23, enligt figur 2. public class Travel { /** Dijkstras algoritm. Varje nod som kan nås från start ges ett värde, där värdet anger det kortaste avståndet från noden start. */ public static void dijkstra(Node start) { // återstår att implementera } public static void main(String[] args) { // satserna från sid. 3, där grafen byggs upp, antas stå här Travel.dijkstra(lund); System.out.println(veberöd.toString()); // (1) } } Uppgift: Implementera metoden dijkstra ovan. Anvisningar: • • • • • 1 Din lösning ska använda Dijkstras algoritm enligt ovan. Din lösning ska använda klassen NodeSet. Resten av klassen (förutom metoden dijkstra) behöver inte återges i lösningen. Förutsätt att alla noder har värdet Integer.MAX_VALUE när metoden anropas. Din lösning ska vara generell, och fungera även för andra grafer än de i figur 1–2. Ursprungligen beskriven i E.W. Dijkstra, ”A note on two problems in connexion with graphs”, Numerische Mathematik 1, 1959. 5(5) 4. (Denna uppgift är fristående från uppgift 1–3.) En magisk kvadrat är en N × N-matris med följande egenskaper: • Heltalen 1, 2, ..., N 2 förekommer alla exakt en gång. • Alla radsummor och kolonnsummor, liksom de båda diagonalernas summor, har samma värde. Detta värde beror på kvadratens storlek. Värdet är N ( N 2 + 1)/2, där N är kvadratens storlek. Fig. 3: En magisk kvadrat av storlek 3. I figur 3 visas en magisk kvadrat av storleken 3 × 3. Talen 1..9 förekommer alla exakt en gång. Alla rader, kolonner och diagonaler har samma summa, nämligen 3(32 + 1)/2 = 15. Just denna magiska kvadrat är känd från en kinesisk legend från 600-talet f.Kr., där en sköldpadda sägs ha haft dessa värden på sitt skal. Klassen SquareMatrix beskriver kvadratiska matriser, där alltså antalet rader och kolonner är lika. En ofullständig implementation följer nedan. public class SquareMatrix { private int[][] a; private int n; /** Skapar en kvadratisk matris av storleken n x n. Storleken n kan antas vara positiv. */ public SquareMatrix(int n) { a = new int[n][n]; this.n = n; } /** Sätter elementet (r,c) till värdet value. Index r och c antas vara giltiga index i matrisen. */ public void set(int r, int c, int value) { a[r][c] = value; } /** Returnerar true om denna matris är en magisk kvadrat, annars false. */ public boolean isMagic() { // återstår att implementera } /** Hjälpmetod. Vad gör denna? */ private boolean checkUnique() { boolean[] numberChecked = new boolean[n * n + 1]; for (int i = 0; i < n; i++) { for (int k = 0; k < n; k++) { int v = a[i][k]; if (v < 1 || v > n * n || numberChecked[v]) { return false; } numberChecked[v] = true; // bocka av v } } return true; } } Uppgift: Implementera metoden isMagic i klassen SquareMatrix. Anvisningar: • Resten av klassen behöver inte återges i lösningen. • Försök förstå vad hjälpmetoden checkUnique gör. Du har nytta av den i din lösning. Vad ger metoden för resultat för den magiska kvadraten i figur 3? • Metoden isMagic ska vara generell, och fungera för kvadratiska matriser av godtycklig positiv storlek ≥ 1.