b) Ungefär tio gånger längre tid, dvs ungefär 10 sekunder.
c) Datorn från b-uppgiften är mycket långsammare än en vanlig, modern skrivbordsdator. (I alla fall om det är själva loopen som tar en sekund att köra. Att starta programmet som loopen finns i kan ta ännu längre tid.)
b) Ungefär tusen gånger längre tid, dvs ungefär 1000 sekunder (17 minuter).
Kommentar 1: Det är inget fel, och kan till och med vara en snyggare lösning, att låta alla funktionerna ta en pekare till en MapPoint-post som argument, i stället för att skicka kopior (som i save_point) eller returnera en post (som i fetch_point).#ifndef MAPPOINT_H #define MAPPOINT_H #include <stdio.h> struct MapPoint { int x; int y; }; extern void init_point(struct MapPoint* mpp, int x, int y); extern void save_point(FILE* f, struct MapPoint mp); extern struct MapPoint fetch_point(FILE* f); extern int same_point(struct MapPoint mp1, struct MapPoint mp2); #endif
Kommentar 2: Det kan också vara bra att använda typedef för att definiera en datatyp som bara heter MapPoint.
Kommentar 3: En alternativ lösning på init_point är att låta den returnera en post, som man sen med en vanlig tilldelning kopierar till den variabel som ska initieras.
b) (6p) Skriv (hela) implementationsfilen MapPoint.c.
c) (2p) Skriv (hela) filen test_points.c.#include <stdio.h> #include "MapPoint.h" void init_point(struct MapPoint* mpp, int x, int y) { mpp->x = x; mpp->y = y; } void save_point(FILE* f, struct MapPoint mp) { fprintf(f, "%d %d\n", mp.x, mp.y); } struct MapPoint fetch_point(FILE* f) { struct MapPoint mp; fscanf(f, "%d", &mp.x); fscanf(f, "%d", &mp.y); return mp; } int same_point(struct MapPoint mp1, struct MapPoint mp2) { return mp1.x == mp2.x && mp1.y == mp2.y; }
#include <stdio.h> #include "MapPoint.h" int main(void) { struct MapPoint p1; init_point(&p1, 3, 1); struct MapPoint p2; init_point(&p2, 4, 7); if (same_point(p1, p2) != 0) printf("Fel! same_point fungerade inte.\n"); return 0; }
Kommentar 1: I den här lösningen finns en pekare även till den sista länken i listan. Det går att klara sig med bara en pekare till den första, men då får inläggning i listan tidskomplexiteten O(n) i stället för O(1), eftersom man måste gå igenom hela listan för att hitta den sista länken. Insättning av n stycken punkter får då tidskomplexiteten O(n2) i stället för O(n). (Om man inte vänder listan med punkter baklänges!)#ifndef ROUTE_H #define ROUTE_H #include <stdio.h> #include "MapPoint.h" struct LinkPoint { struct MapPoint point; struct LinkPoint* next; }; struct Route { struct LinkPoint* first; struct LinkPoint* last; }; extern void init_route(struct Route* rp); extern void append_point(struct Route* rp, struct MapPoint p); extern void save_route(FILE* f, struct Route r); extern struct Route fetch_route(FILE* f); extern int has_visited(struct Route r, struct MapPoint p); extern void cleanup_route(struct Route* rp); #endif
Kommentar 2: Det är inget fel, och kan till och med vara en snyggare lösning, att låta alla funktionerna ta pekare till Route som argument, i stället för att skicka kopior (som i save_route) eller returnera en post (som i fetch_route).
b) (10p) Skriv (hela) implementationsfilen Route.c.
#include <stdlib.h> #include <stdio.h> #include "Route.h" void init_route(struct Route* rp) { rp->first = rp->last = NULL; } void append_point(struct Route* rp, struct MapPoint p) { struct LinkPoint* newnode = malloc(sizeof(struct LinkPoint)); if (newnode == NULL) { fprintf(stderr, "Minnet är fullt. Programmet avslutas.\n"); exit(EXIT_FAILURE); } newnode->point = p; newnode->next = NULL; if (rp->first == NULL) { rp->first = rp->last = newnode; } else { rp->last->next = newnode; rp->last = newnode; } } void save_route(FILE* f, struct Route r) { struct LinkPoint* node = r.first; while (node != NULL) { save_point(f, node->point); node = node->next; } } struct Route fetch_route(FILE* f) { struct Route r; init_route(&r); struct MapPoint p = fetch_point(f); while (!feof(f)) { append_point(&r, p); p = fetch_point(f); } return r; } int has_visited(struct Route r, struct MapPoint p) { struct LinkPoint* node = r.first; while (node != NULL) { if (same_point(node->point, p)) return 1; node = node->next; } return 0; } void cleanup_route(struct Route* rp) { struct LinkPoint* node = rp->first; while (node != NULL) { struct LinkPoint* doomed = node; node = node->next; free(doomed); } }
a) (3p) Skriv (hela) include-filen PointSet.h.
Kommentar 1: Alla funktionerna tar en pekare till PointSet som argument. Till is_member kunde man i stället ha skickat en kopia på posten, men för att göra koden likformig har valt att skicka en pekare även till den.#ifndef POINTSET_H #define POINTSET_H #include "MapPoint.h" struct PointTreeNode { struct MapPoint point; struct PointTreeNode* left; struct PointTreeNode* right; }; struct PointSet { struct PointTreeNode* root; }; extern void init_pointset(struct PointSet* sp); extern void add_point(struct PointSet* sp, struct MapPoint p); extern int is_member(struct PointSet* sp, struct MapPoint p); #endif
Kommentar 2: En punktmängd representeras av en post ("struct") som innehåller en pekare till rotnoden. Man kunde i stället använt bara en lös pekare till rotnoden, men för att det ska bli likadant som i de andra uppgifterna använder vi en post här också. Dessutom bygger hanteringen av abstrakta datatyper i C++ på att allt är structar (även om de där kallas för "klasser"), så det är lika bra att vänja sig redan nu.
b) (5p) Skriv (hela) implementationsfilen PointSet.c.
Sedan en lösning på a och b, med en hashtabell:#include <stdlib.h> #include <stdio.h> #include "PointSet.h" void init_pointset(struct PointSet* sp) { sp->root = NULL; } static int compare_points(struct MapPoint p1, struct MapPoint p2) { int xdiff = p1.x - p2.x; if (xdiff != 0) return xdiff; else return p1.y - p2.y; } static void tree_insert(struct PointTreeNode** rootp, struct MapPoint newpoint) { if (*rootp == NULL) { struct PointTreeNode* newnode = malloc(sizeof(struct PointTreeNode)); if (newnode == NULL) { fprintf(stderr, "Minnet är fullt. Programmet avslutas.\n"); exit(EXIT_FAILURE); } newnode->point = newpoint; newnode->left = newnode->right = NULL; *rootp = newnode; } else if (same_point(newpoint, (*rootp)->point)) ; else if (compare_points(newpoint, (*rootp)->point) < 0) tree_insert(&(*rootp)->left, newpoint); else/* if (compare_points(newpoint, (*rootp)->point) > 0) */ tree_insert(&(*rootp)->right, newpoint); } static struct PointTreeNode* tree_search(struct PointTreeNode* root, struct MapPoint wanted) { if (root == NULL) return NULL; else if (same_point(wanted, root->point)) return root; else if (compare_points(wanted, root->point) < 0) return tree_search(root->left, wanted); else /* if (compare_points(wanted, root->point) > 0) */ return tree_search(root->right, wanted); } void add_point(struct PointSet* sp, struct MapPoint p) { tree_insert(&sp->root, p); } int is_member(struct PointSet* sp, struct MapPoint p) { return tree_search(sp->root, p) != NULL; }
a) (3p) Skriv (hela) include-filen PointSet.h.
Kommentar 1: Alla funktionerna tar en pekare till PointSet som argument. Till is_member kunde man i stället ha skickat en kopia på posten, men för att göra koden likformig har valt att skicka en pekare även till den. Dessutom (varning!) innehåller posten hela hashtabell-arrayen, som är 4 megabyte stor eller ännu mer, och det kan hända att programmet kraschar om man försöker skicka ett så stort dataobjekt som argument till en funktion!#ifndef POINTSET_H #define POINTSET_H #include "MapPoint.h" #define POINTSET_HASH_TABLE_SIZE 1000003 struct PointHashNode { struct MapPoint point; struct PointHashNode* next_in_bucket; }; struct PointSet { struct PointHashNode* hash_table[POINTSET_HASH_TABLE_SIZE]; }; extern void init_pointset(struct PointSet* sp); extern void add_point(struct PointSet* sp, struct MapPoint p); extern int is_member(struct PointSet* sp, struct MapPoint p); #endif
Kommentar 2: En punktmängd representeras av en post ("struct") som innehåller hashtabell-arrayen. Man kunde i stället använt bara en lös array, men för att det ska bli likadant som i de andra uppgifterna använder vi en post här också. Dessutom bygger hanteringen av abstrakta datatyper i C++ på att allt är structar (även om de där kallas för "klasser"), så det är lika bra att vänja sig redan nu. Ytterligare ett skäl till att baka in arrayen i en struct är att om man vill använda typedef för att definiera en datatyp som bara heter PointSet, så är det inte så bra att typedeffa arrayer. Arrayer fungerar lite speciellt i C, till exempel det där med att man inte kan skicka dem som kopior till och från funktioner, och därför kan det vara dumt att dölja det faktum att något är en array.
b) (5p) Skriv (hela) implementationsfilen PointSet.c.
#include <stdlib.h> #include <stdio.h> #include "PointSet.h" void init_pointset(struct PointSet* sp) { for (int i = 0; i < POINTSET_HASH_TABLE_SIZE; ++i) sp->hash_table[i] = NULL; } static int hash(struct MapPoint p) { int sum = p.x + p.y; if (sum < 0) sum = -sum; return sum % POINTSET_HASH_TABLE_SIZE; } void add_point(struct PointSet* sp, struct MapPoint p) { if (is_member(sp, p) == 0) { int position = hash(p); struct PointHashNode* newnode = malloc(sizeof(struct PointHashNode)); if (newnode == NULL) { fprintf(stderr, "Minnet är fullt. Programmet avslutas.\n"); exit(EXIT_FAILURE); } newnode->point = p; newnode->next_in_bucket = sp->hash_table[position]; sp->hash_table[position] = newnode; } } int is_member(struct PointSet* sp, struct MapPoint p) { int position = hash(p); struct PointHashNode* np = sp->hash_table[position]; while (np != NULL) { if (same_point(np->point, p)) return 1; np = np->next_in_bucket; } return 0; }