Tentamen Data- och programstrukturer Datum: 15-06-04 Tid: 09:00-13:00 Sal: Ansvarig: Resultat: Hjälpmedel: Betygsgränser: Ulf Johansson Anslås inom 3 veckor. Inga G 22p, VG 33p, Max 45p Iakttag följande: Numrera sidorna samt börja varje ny uppgift på nytt blad. Skriv personnummer på försättsblad och alla inlämnade sidor. Var tydliga, kortfattade och konkreta i era svar. Skriv tydligt (oläsligt = fel)! På programmeringsuppgifterna är principen viktigare än syntaxen. Poängavdrag kan ges för onödigt komplicerade eller ostrukturerade lösningar. Kommandon från bibliotek (även Roberts) får normalt användas fritt. Inkluderingar behöver aldrig anges om det inte framgår av frågan. Lycka till!
1) a) Vilken komplexitet har operationerna lookup och insert i ett binärt sökträd? b) Studenten Börje, som inte läser på SA, har på en laboration försökt konstruera ett program som avgör om ett givet binärt träd är ett binärt sökträd eller inte. Hans lösning nedan är dock inte korrekt. Ge ett exempelträd som programmet felaktigt kommer att identifiera som ett binärt sökträd, samt förklara med egna ord vad problemet med Börjes lösning är. (Observera att ni inte behöver förslå en korrigering.) int isbst(struct node* node){ if (node == NULL) return 1; false if left is > than node if (node->left!= NULL && node->left->data > node->data) return 0; false if right is < than node if (node->right!= NULL && node->right->data < node->data) return 0; false if, recursively, the left or right is not a BST if (!isbst(node->left)!isbst(node->right)) return 0; passing all that, it's a BST return 1; c) Vi har i kursen sett exempel på representationer som är hybridlösningar mellan vektorer och länkade listor, dels i form av chunklistor för prioritetsköer i laboration 2 och dels Roberts implementation av hashtabeller. Beskriv hur en chunklista för prioritetskö respektive bokens implementation av hashtabell ser ut, samt redogör för vilka fördelar och nackdelar som respektive lösning har jämfört med de rena representationerna. Diskutera slutligen varför just dessa varianter är lämpliga för sin respektive uppgift, dvs. prioritetskö och hashtabell. Din diskussion skall inkludera resonemang om komplexitet. (2+3+4p) 2) a) För att hitta ett numeriskt värde för roten, x, ur ett visst tal, y, kan följande metod användas: Gissa ett värde på x. Om x är tillräckligt bra avbryt och returnera x. Annars förbättra approximation av x till (x+y/x)/2. Antag att följande hjälpfunktioner är givna: bool goodenough (double y, double x){ return(fabs(x*x y) < 0.00001) double improveguess(double y, double x){ return((x+y/x) / 2);
Skriv en rekursiv funktion rot med prototyp enligt nedan som använder sig av de båda hjälpfunktionerna. double rot (double y, double initialguess); b) Skriv en rekursiv funktion issorted som tar en heltalsvektor och dess storlek som argument och avgör om vektorn är sorterad i stigande ordning. Prototypen är alltså: bool issorted (int *a, int size); För full poäng skall lösningen vara riktigt kompakt den går att göra som en oneliner. Det är dock även helt ok att följa standardmönstret if( ) return else return men mer trassligt än så bör det inte vara. (4+4p) 3) Bifogat i bilaga 1 finner ni delar av gränssnittet till vanliga köer, queueadt. Antag ni har en implementering av köadt:n med en enkellänkad lista och där den konkreta datatypen definieras enligt: struct queuecdt { cellt *head; cellt *tail; ; a) Ange typdefinitionen för celler i listan. b) Givet ovanstående queuecdt, implementera funktionerna Enqueue och Dequeue med en så god tidskomplexitet som möjligt. KöADT:n kan även implementeras med hjälp av en vektor. Vanligtvis använder man då en s.k. ringbuffer, men i denna uppgift skall ni använda en rak vektor istället, vilket innebär att köns element ligger i sekvens och att huvudet på kön alltid har ett lägre index är svansen. När köns svans når slutet av vektorn, görs en ommöblering så att huvudet hamnar först, se exempel nedan. Exempel Enqueue av elementen 2, 3, 5, 7, 11 (i den ordningen) ger följande vektor: 2 3 5 7 11 Efter Dequeue x 3, samt Enqueue av 13, 17, 19, 23, 29 och 31 ser vektorn ut så här: 7 11 13 17 19 23 29
När nästa Enqueue sker, exempelvis inläggning av 31, sker förflyttning av alla element till början: 7 11 13 17 19 23 29 31 c) Ge den konkreta datatypen för en vektorimplementation enligt ovan. Vektorn skall vara statiskt allokerad med en storlek som ges av konstanten MAX_QUEUE_SIZE. d) Givet din queuecdt för vektorimplementationen, samt ovanstående beskrivning av hur den skall fungera, implementera funktionerna Enqueue och Dequeue med en så god tidskomplexitet som möjligt. Ange även vilken tidskomplexitet operationerna får i worst-case. (1+5+2+4p) 4) Utgå från den implementering av (opåverkbara) listor som vi tittat på under kursen; gränssnittet framgår av list.h (bilaga 2). Notera att elementtypen här är satt att vara heltal. a) Skriv en funktion som summerar alla tal i en lista. Prototypen blir: int sum(listadt xs); b) Vi vill skapa en funktion som returnerar en kopia av en lista där dock ordningen skall vara omvänd (det första elementet i originallistan skall ligga sist i kopian etc.) Prototypen blir: listadt reverse(listadt xs); Denna funktion skrivs enklast genom att använda en s.k. wrapper och därmed förflytta själva arbetet till ytterligare en ny funktion. I detta fall är det enda som görs i wrappern innan anropet att en tom lista skapas. Denna tomma lista är tänkt att fungera som en ackumulator; dvs. elementen skall plockas ur den ursprungliga listan ett i taget (från huvudet) och läggas först i ackumulatorn. När hela originallistan är genomgången kommer därför ackumulatorn att innehålla alla elementen men i omvänd ordning. Wrapperfunktionen blir: listadt reverse(listadt xs){ listadt newlist=null; return(rev(xs,newlist)); Skriv nu funktionen rev som arbetar (rekursivt) enligt beskrivningen ovan! (3+5p)
5) Nedanstående (förhoppningsvis välbekanta) grammatik definierar aritmetiska uttryck med iversonian prioritet, dvs. strikt höger till vänster: E ::= T E ::= T op E T ::= integer T ::= identifier T ::= ( E ) För att parsa uttryck i detta språk använder vi rekursivt nedstigande parsning och ömsesidigt rekursiva funktioner. a) Ge pseudokod för funktionen reade och förklara i ord hur funktionen arbetar. b) Ge pseudokod för funktionen readt och förklara i ord hur funktionen arbetar. (4+4p)
Bilaga1: queue.h * Type: queueelementt typedef int queueelementt; * Function: NewQueue * Usage: queue = NewQueue(); * -------------------------- * This function allocates and returns an empty queue queueadt NewQueue(void); * Function: FreeQueue * Usage: FreeQueue(queue); * -------------------------- * This function frees the storage associated with queue. void FreeQueue(queueADT queue); * Function: EnQueue * Usage: EnQueue(queue, element); * -------------------------- * This function adds element to the end of the queue. void Enqueue(queueADT queue, queueelementt element); * Function: DeQueue * Usage: element = DeQueue(queue); * -------------------------------- * This function removes the data value at the head of the queue * and returns it to the client. If the queue is empty, Dequeue * calls Error with an appropriate message. queueelementt Dequeue(queueADT queue); * Functions: QueueIsEmpty, QueueIsFull * Usage: if (QueueIsEmpty(queue))... * if (QueueIsFull(queue))... * ------------------------------------ * These functions test whether the queue is empty or full. bool QueueIsEmpty(queueADT queue); bool QueueIsFull(queueADT queue);
Bilaga2: list.h * File: list.h * ------------ * This interface defines an abstraction for an immutable list. * Type: listelementt * ------------------ * The type listelementt is used in this interface to indicate * the type of values that can be stored in the list. Here the * list is used to store values of type int, but that can * be changed by editing this definition line. typedef int listelementt; * Type: listadt * ------------- * The type listadt represents the abstract type used to represent * the list itself. The empty list is always represented by NULL. * The client can rely on the following aspects of the internal * structure: * * 1. Each list value returned is dynamically allocated. * * 2. The list element is stored at the beginning of the * structure used internally to hold the record so that * the address of the list cell is also the address of the * stored value. This constraint makes it possible to apply * comparison functions to the stored data. typedef struct listcdt *listadt; * Function: ListCons * Usage: list = ListCons(head, tail); * ----------------------------------- * This function allocates and returns a new list, which consists * of the element head followed by the list tail. listadt ListCons(listElementT head, listadt tail); * Functions: ListHead, ListTail * Usage: head = ListHead(list); * tail = ListTail(list); * ----------------------------- * These functions return the head and tail of a list, where the * head is defined to be the first element and the tail is the * list that remains after removing the head of the list. listelementt ListHead(listADT list); listadt ListTail(listADT list); * Function: isemptylist * Usage: isemptylist(list); * ----------------------------------- * This function returns true if the list is empty and false otherwize bool isemptylist(listadt list);