LUNDS TEKNISKA HÖGSKOLA 1(5) Institutionen för datavetenskap Tentamen, EDA690 Algoritmer och Datastrukturer, Helsingborg 2013 12 19, 8.00 13.00 Anvisningar: Denna tentamen består av 4 uppgifter. Preliminärt ger uppgifterna 1. 1 + 1 + 1 + 1 = 4 p 2. 2 + 1 +1 + 1 = 5 p 3. 8 + 1 = 9 p 4. 7 + 1 + 1 = 9 p 5. 7 + 2 = 9 p För full poäng ska dina lösningar inte vara onödigt ineffektiva. Bifogat tentamen finns Java snabbreferens samt dokumentation av någon/några klasser och interface i Java Collections Framework. När rättningen är klar meddelas detta på kursens hemsida (cs.lth.se/eda690). 1. Förklara följande begrepp: a) Stack b) Kollision i en hashtabell c) Balanserat binärt träd d) Partitionering i samband med Quicksort 2. Studera följande klass public class ListHandler { Tar bort alla dubbletter i listan list. public static <T> void deleteduplicates(list<t> list) { LinkedList<T> temp = new LinkedList<T>(); for (T element : list) { if (! temp.contains(element)) { temp.add(element); list = temp; a) Metoden deleteduplicates fungerar inte som avsett. Förklara varför och rätta till koden. Det är inte tillåtet att ändra metodens signatur. b) Vad får metoden för tidskomplexitet i värsta fall? Motivera ditt svar. c) Det finns effektivare sätt att ta bort dubbletter. Beskriv hur man kan göra istället. d) Vilken tidskomplexitet får din nya lösning? Motivera ditt svar.
2(5) 3. Antag att vi har en mängd element som tilldelas prioritet i form av ett heltal i ett intervall 1..k, där k är ett litet tal. 1 anger högsta prioritet och k lägsta. Vi kan då implementera en prioritetsköklass där alla operationer har en tidkomplexitet O(1) och där dessutom prioritetskön är stabil. Med stabil menas att bland element med lika prioritet tas det element som väntat längst ut först. Lös problemet genom att bilda k köer, en för varje prioritet. Nya element sätts in i den kö som deras prioritetstal anger. För att representera köerna ska cirkulära enkellänkade listor användas. I det sista elementet är inte referensen till efterföljaren (next) null utan i stället refererar den till det äldsta (första) elementet i listan. Se figur 1. referens till kön referens till kön Cirkulär lista att använda för att implementera en kö. next-referenser och referenser till insatta element är utritade. Noden längst till vänster innehåller det äldsta elementet i kön. Noden längst till höger innehåller det senast insatta elementet. Kö med ett enda element Figur 1: Kö som representeras av cirkulär enkellänkad lista. Eftersom elementen ska placeras in i rätt kö efter prioritet ställer vi kravet att de objekt som ska sättas in i kön är av en klass som implementerar ett interface Priority: public interface Priority { * Returns the element s priority * @return the element s priority int priority(); * Sets the element s priority to prio * @param prio the new priority void setpriority(int prio); a) Implementera klassen SimplePriorityQueue enligt ovanstående anvisningar. public class SimplePriorityQueue<E extends Priority> extends AbstractQueue<E> implements Queue<E> { private Node<E>[] queue; private int size; * Creates an empty priorityqueue. The allowed priorities are in the interval [1..k] * @param k the highest allowed priority public SimplePriorityQueue(int k) { queue = (Node<E>[]) new Node[k + 1]; // plats 0 används ej size = 0;
3(5) /* Nested class. Represents a node which contains an element of type E. private static class Node<E> { private E element; private Node<E> next; private Node(E x) { element = x; next = null; * Inserts the specified element into this priority queue. * @param e the element to add * @return true * @throws NullPointerException if the specified element is null * @throws IllegalArgumentException if the elements prio is outside * the allowed interval public boolean offer(e e) {... * Retrieves, but does not remove, the smallest element in this queue, or * returns null if this queue is empty. * @return the smallest element in this queue, or null if this queue is empty public E peek() {... * Retrieves and removes the smallest element in this queue, or returns null * if this queue is empty. * @return the smallest element in this queue, or null if this queue is empty public E poll() {... * Returns the number of elements in this queue. public int size() {... Anm. Egentligen måste man också implementera metoden iterator() i klassen SimplePriorityQueue men vi bortser från det i uppgiften. b) Felrapporteringssystem används inom många mjukvaruprojekt. När fel upptäcks av användare till ett program rapporteras felen in i systemet, som sedan hjälper programutvecklarna att hålla reda på och följa upp fel så att de blir åtgärdade. En felrapport (eng. ticket) beskrivs av klassen Ticket. Vi vill kunna ge en felrapport en prioritet i form av ett heltal samt kunna lagra felrapporter i en kö av typen SimplePriorityQueue. Gör de ändringar som behövs i klassen Ticket för att detta ska fungera. public class Ticket { private int id; private String title; // Felrapportens unika id-nr // Felrapportens titel public Ticket(int id, String title) { this.id = id; this.title = title;
4(5) public int getid() { return id; public String gettitle() { return title; 4. a) Följande klass beskriver ett binärt träd. public class BinaryTree<E extends Comparable<? super E>> { private Node<E> root;... operationer... private static class Node<E> { private E element; private Node<E> left; private Node<E> right;... operationer... Ett träd är heapordnat (eller partiellt ordnat) om det för varje nod gäller att dess element är mindre än eller lika med barnens element. Lägg till en metod i klassen BinaryTree<E> som returnerar true om trädet är heapordnat. Ledning och anvisningar: För full poäng får din lösning inte bygga på att du inför nya attribut i klasserna. Däremot är det tillåtet (och lämpligt) att lägga till en privat metod. Ett tomt träd ska betraktas som heapordnat. b) Att ett binärt träd är partiellt ordnat är ett krav för att trädet ska utgöra en heap. Det finns ytterligare ett villkor. Vilket? Bara namn på villkoret räcker inte för poäng. Beskriv också vad som menas med villkoret. c) En heap implementeras ofta på ett annat sätt än med ett binärt träd. Beskriv hur. I din beskrivning ska det ingå en figur med ett exempel. 5. a) I handelsresandeproblemet gäller det för en handelsresande att besöka ett antal städer. Varje stad ska besökas exakt en gång. Handelsresanden vill att den totala resvägen ska bli så kort som möjligt. Att lösa detta problem exakt är tidsödande, eftersom man måste beräkna alla möjliga resvägar för att finna den kortaste vägen. Vi nöjer oss därför i denna uppgift med att hitta en approximation till den bästa lösningen genom att i varje steg besöka den stad som ligger närmast den senast besökta staden. Kartan med alla städer beskrivs av en map av typen Map<String, List<TownDistance>>. Nyckeln är en teckensträng med stadens namn och värdet är en lista med TownDistanceobjekt. Klassen TownDistance beskriver avståndet till en annan stad (se nedan). Listorna med TownDistance-objekten är sorterade efter växande avstånd. Det finns uppgifter om avstånd för alla par av städer. public class TownDistance { private String destination; private int distance; // stadens namn // avstånd till staden (km) public TownDistance(String destination, int distance) { this.destination = destination; this.distance = distance; public String getdestination() {
5(5) return destination; public int getdistance() { return distance; Här är början på ett exempel som visar hur en karta med 6 städer skapas: Map<String, List<TownDistance>> map = new HashMap<String, List<TownDistance>>(); List<TownDistance> list = new LinkedList<TownDistance>(); list.add(new TownDistance("Lund", 21)); list.add(new TownDistance("Helsingborg",64 )); list.add(new TownDistance("Göteborg", 273 )); list.add(new TownDistance("Norrköping", 455)); list.add(new TownDistance("Stockholm", 613 )); map.put("malmö", list); list = new LinkedList<TownDistance>(); list.add(new TownDistance("Malmö", 21)); list.add(new TownDistance("Helsingborg", 55)); list.add(new TownDistance("Göteborg", 264)); list.add(new TownDistance("Norrköping", 445)); list.add(new TownDistance("Stockholm", 604 )); map.put("lund", list);... Din uppgift är att implementera metoden PrintTour i klassen RoadMap: public class RoadMap_list { private Map<String, List<TownDistance>> map; Skapar ett objekt som håller reda på en vägkarta. public RoadMap_list(Map<String, List<TownDistance>> map) { this.map = map; Skriver ut namnet på de städer som besöks, i tur och ordning, när man påbörjar en resa i staden med namnet starttown. Det förutsätts att det finns en stad med det namnet. Allra sist skrivs den totala resvägen (antal km) ut. public void printtour(string starttown) { // fyll i egen kod här Ledning och anvisningar: Använd följande algoritm: Använd en mängd, dvs. en klass som implementerar java.util.set för att hålla reda på de redan besökta städerna. Skriv ut startstadens namn, markera den som besökt (genom att lägga den i mängden för redan besökta städer). Leta upp närmaste stad som inte är besökt, skriv ut dess namn, markera den som besökt. Osv. b) Det finns algoritmer för att beräkna kortaste vägen mellan två städer. Då behöver man kunna lägga in objekt av typen TownDistance från förra uppgiften i en prioritetskö av typen java.util.priorityqueue. Man vill kunna hämta TownDistance-objektet med minsta avstånd. Gör de ändringar som krävs i klassen TownDistance för att den ska kunna läggas in i en sådan prioritetskö.
LUNDS TEKNISKA HÖGSKOLA 1(5) Institutionen för datavetenskap Lösningsförslag till tentamen i EDA690 Algoritmer och Datastrukturer, Helsingborg 2013 12 19 1. a) En samling element där insättning och borttagning sker överst på stacken. D.vs. det element som satts in senast är det element som först tas bort. b) Med kollision menas att hashfunktionen för ett element ger ett värde som motsvarar en redan upptagen plats i tabellen. c) I ett binärt träd har varje nod 0,1 eller 2 barn. I ett balanserat binärt sökträd gäller dessutom för varje nod att höjdskillnaden mellan dess båda subträd är högst 1. d) Den vektor som ska sorteras delas upp i två halvor genom att man först väljer ett av elementen (kallas pivot-element). Sedan flyttas elementen så att element pivot hamnar till vänster och element > pivot hamnar till höger om pivot-elementet. 2. a) När metoden deleteduplicates anropas kopieras en referens till den lista som ska ändras till parametern list. Men inuti metoden får list ett nytt värde och refererar nu till den nya temp-listan. Denna lista har fyllts på med unika värden, men den ursprungliga listan ändras ej. Sista raden i metoden ändras till: list.clear(); for (T element : temp) { list.add(element); b) O(n 2 ). Antag att listan innehåller n st. element. För vart och ett av elementen gör man en sökning bland de element som hittills lagts in i listan (som innehåller < n element). De n sökningarna kostar O(n n). Överflyttningen av de unika elementen till list kostar O(n). Totalt blir tidskomplexiteten alltså O(n 2 ). c) Man kan låta temp vara en mängd istället för en lista. Vid insättning sätts då ej dubbletter in. Mängden kan t.ex. implementeras med hjälp av en hashtabell eller ett binärt sökträd. d) Tidskomplexiteten beror på hur mängden implementeras. Om vi antar att vi använder en hashtabell kostar en insättning O(1) (i medelfall). Insättningen av de n elementen i mängden kostar då O(n 1). Överflyttningen av de unika elementen till list kostar O(n). Totalt blir tidskomplexiteten alltså O(n). Anm. Om vi istället använder ett binärt sökträd som mängd kostar en insättning O(logn) och den totala tidskomplexiteten blir O(nlogn). En annan lösning är att sortera elementen i listan (kostar O(nlogn). Dubbletter hamnar då intill varandra och vid genomgång av de sorterade elementen är det lätt att undvika att ta med dubbletterna. 3. a) public class SimplePriorityQueue<E extends Priority> extends AbstractQueue<E> implements Queue<E> { private Node<E>[] queue; private int size; * Creates an empty priorityqueue. The allowed priorities are in the interval [1..k] * @param k the highest allowed priority
2(5) public SimplePriorityQueue(int k) { queue = (Node<E>[]) new Node[k + 1]; // plats 0 används ej size = 0; /* Nested class. Represents a node which contains an element of type E. private static class Node<E> { private E element; private Node<E> next; private Node(E x) { element = x; next = null; * Inserts the specified element into this priority queue. * @param e the element to add * @return true * @throws NullPointerException if the specified element is null * @throws IllegalArgumentException if the elements prio is outside * the allowed interval public boolean offer(e e) { if (e == null) { throw new NullPointerException(); int prio = e.priority(); if (prio <= 0 prio >= queue.length) { throw new IllegalArgumentException(); Node<E> n = new Node<E>(e); if (queue[prio] == null) { queue[prio] = n; queue[prio].next = queue[prio]; else { Node<E> first = queue[prio].next; queue[prio].next = n; n.next = first; queue[prio] = queue[prio].next; size++; return true; * Retrieves, but does not remove, the smallest element in this queue, or * returns null if this queue is empty. * @return the smallest element in this queue, or null if this queue is empty public E peek() { int index = firstnonemptyindex(); if (index == -1) { return null; return queue[index].next.element;
3(5) * Retrieves and removes the smallest element in this queue, or returns null * if this queue is empty. * @return the smallest element in this queue, or null if this queue is empty public E poll() { int index = firstnonemptyindex(); if (index == -1) { return null; E e = queue[index].next.element; if (queue[index] == queue[index].next) { queue[index] = queue[index].next; queue[index] = null; else { queue[index].next = queue[index].next.next; size--; return e; /* * Returns the index for the first non empty list, or returns -1 if all lists * are empty. private int firstnonemptyindex() { for (int i = 1; i < list.length; i++) { if (list[i]!= null) { return i; return -1; * Returns the number of elements in this queue. public int size() { return size; Anm. Egentligen måste man också implementera metoden iterator() i klassen SimplePriorityQueue men vi bortser från det i uppgiften. b) public class Ticket implements Priority { private int id; // Felrapportens unika id-nr private String title; // Felrapportens titel private int prio = 5; public Ticket(int id, String title) { this.id = id; this.title = title; public int getid() { return id; public String gettitle() { return title;
4(5) public int priority() { return prio; public void setpriority(int prio) { this.prio = prio; 4. a) Returnerar true om trädet är partiellt ordnat, annars false. public boolean ispartiallyordered() { if (root!= null) { return ispartiallyordered(root); else { return true; private boolean ispartiallyordered(node<e> n) { if (n.left == null && n.right == null) { return true; if (n.left == null) { if (n.data.compareto(n.right.data) > 0) { return false; else { return ispartiallyordered(n.right); if (n.right == null) { if (n.data.compareto(n.left.data) > 0) { return false; else { return ispartiallyordered(n.left); return n.data.compareto(n.left.data) <= 0 && n.data.compareto(n.right.data) <= 0 && ispartiallyordered(n.left) && ispartiallyordered(n.right); b) Trädet är komplett, dvs.alla nivåer utom den högsta är fyllda med noder och om noderna på den högsta nivån är samlade längst till vänster : c) Man brukar implementera en heap med en vektor. Roten lagras på plats 0. Barnen till en nod vars element lagras på plats k i vektorn lagras på platserna 2k+1 och 2k+2. Exempel: 2 5 3 7 6 2 3 6 5 7 0 1 2 3 4
5(5) 5. a) public void printtour(string starttown) { Set<String> visitedtowns = new HashSet<String>(); visitedtowns.add(starttown); System.out.println(startTown); int totaldistance = 0; while (visitedtowns.size() < map.size()) { TownDistance nexttown = nearestunvisited(starttown, visitedtowns); System.out.println(nextTown.getDestination()); totaldistance += nexttown.getdistance(); visitedtowns.add(nexttown.getdestination()); starttown = nexttown.getdestination(); System.out.println("Total resväg " + totaldistance + (" km") ); TownDistance nearestunvisited(string starttown, Set<String> visitedtowns) { Iterator<TownDistance> itr = map.get(starttown).iterator(); while (itr.hasnext()) { TownDistance town = itr.next(); if (! visitedtowns.contains(town.getdestination())) { return town; return null; b) public class TownDistance implements Comparable<TownDistance> {... public int compareto(towndistance other) { return distance - other.distance; Anm. Här har vi valt att inte skugga equals vilket annars är brukligt när man implementerar Comparable. Metoden CompareTo ska användas specifikt i inuti prioritetskön och inte för att jämföra distans-objekt i allmänhet.