diff --git a/TheAlgorithmDesignManual.org b/TheAlgorithmDesignManual.org index 38d9ac9..eb0e574 100644 --- a/TheAlgorithmDesignManual.org +++ b/TheAlgorithmDesignManual.org @@ -1,5 +1,6 @@ #+TITLE: Notes & Exercises: The Algorith Design Manual #+AUTHOR: Joseph Ferano +#+STARTUP: overview #+OPTIONS: ^:{} * Chapter 1 @@ -357,3 +358,224 @@ printf("Final: %s\n", str); | Before: | My | name | is | Chris | | After: | sirhC | si | eman | yM | | Final: | Chris | is | name | My | + +* Chapter 4 + +** 4.1 Applications of Sorting + +Apparently sorting is a big deal. There are a lot of problems that can be solved by using a sorted +list, for example; closest pair, searching, frequency distribution, convex hulls, etc; + +The fastest sorting algorithms are ~n log n~, here is how it scales compared to an algorithm with +quadratic growth. Even crazier is that this table divides it by 4 so it's not that ridiculous. + +| n | n^{2}/4 | n lg n | +|---------+---------------+-----------| +| 10 | 25 | 33 | +| 100 | 2,500 | 664 | +| 1,000 | 250,000 | 9,965 | +| 10,000 | 25,000,000 | 132,877 | +| 100,000 | 2,500,000,000 | 1,660,960 | + +Whenever you have an algorithmic problem, don't be afraid to use sorting, because if it results in +the ability to then do a linear scan, at worst is becomes ~2n log n~, which in the end is just ~n log n~ + +** 4.2 Pragmatics of Sorting + +There are some important things to consider when sorting; the order, keys and their data, equality, +and non-numerical data. For resolving these, we would use /comparison functions/. Here is the +signature of the C function for quicksort + +#+begin_src C :include stdlib.h +void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*)) +#+end_src + +It takes a comparison function that might look like this; + +#+begin_src C :include stdlib.h +int compare(int *i, int *j) { + if (*i > *j) return 1; + if (*i < *j) return -1; + return 0; +} +#+end_src + + +** 4.3 Heapsort + +An O(n^{2}) sorting algorithm like Selection Sort can be made faster by using the right data structure, +in this case either a heap or a balanced binary tree. The first initial construction will take +one O(n), but subsequent operations within the loop will now take O(log n) rather than O(n), giving +a final complexity of O(n log n). + +*** Heaps + +They can either be min or max heaps and the root node will dominate its children in min-ness or +max-ness. It's also different in that it can be implemented in an array and still be reasonably +conservative with it's space complexity. However it should be noted that searching isn't efficient +because the nodes aren't guaranteed to be ordered, only the relationship between the parent/child. + +Here's the full heap implementation; + +#+begin_src C :includes stdio.h stdlib.h +#define PQ_SIZE 256 +#define item_type int + +struct priority_queue { + item_type *q; + int len; +}; + +struct priority_queue *pq_create() { + item_type *q = calloc(PQ_SIZE, sizeof(item_type)); + struct priority_queue *pq = malloc(sizeof(struct priority_queue)); + pq->q = q; + pq->len = 0; +} + +int pq_parent(int n) { + if (n == 1) return -1; + else return ((int) n/2); +} + +int pq_young_child(int n) { + return 2 * n; +} + +// I mean technically we shouldn't need to provide the parent, since we can +// just call that ourselves +void pq_swap(struct priority_queue *pq, int n, int parent) { + item_type item = pq->q[n]; + pq->q[n] = pq->q[parent]; + pq->q[parent] = item; +} + +void pq_bubble_up(struct priority_queue *pq, int n) { + int pidx = pq_parent(n); + if (pidx == -1) { + return; + } + item_type parent = pq->q[pidx]; + item_type node = pq->q[n]; + + if (parent > node) { + pq_swap(pq, n, pidx); + pq_bubble_up(pq, pidx); + } +} + +void pq_bubble_down(struct priority_queue *pq, int n) { + int cidx = pq_young_child(n); + if (cidx > pq->len) { + return; + } + item_type child = pq->q[cidx]; + item_type node = pq->q[n]; + int min_idx = n; + + if (cidx <= pq->len && node > child) { + min_idx = cidx; + } + if (cidx + 1 <= pq->len && pq->q[min_idx] > pq->q[cidx + 1]) { + min_idx = cidx + 1; + } + + if (node > child) { + pq_swap(pq, n, min_idx); + pq_bubble_down(pq, min_idx); + } +} + +void pq_insert(struct priority_queue *pq, item_type x) { + if (pq->len >= PQ_SIZE) { + printf("Error: Priority Queue Overflow"); + return; + } + pq->q[++pq->len] = x; + pq_bubble_up(pq, pq->len); +} + +item_type pq_pop_top(struct priority_queue *pq) { + if (pq->len == 0) { + printf("Error: No elements in Priority Qeueu"); + return -1; + } + item_type top = pq->q[1]; + pq->q[1] = pq->q[pq->len--]; + pq_bubble_down(pq, 1); + return top; +} + +struct priority_queue* pq = pq_create(); + +#define ELEMENTS 8 +item_type arr[ELEMENTS]; + +for (int i = 0; i < ELEMENTS; i++) { + arr[i] = i + 1; +} + +void reverse(item_type *arr) { + for (int i = 0; i < ELEMENTS / 2; i++) { + item_type temp = arr[i]; + arr[i] = arr[ELEMENTS - i - 1]; + arr[ELEMENTS - i - 1] = temp; + } +} + +void shuffle(item_type *arr) { + for (int i = 0; i < ELEMENTS - 1; i++) { + float r = (float)rand() / RAND_MAX; + int idx = (int)(ELEMENTS * r); + item_type temp = arr[idx]; + arr[idx] = arr[i]; + arr[i] = temp; + } +} + +void print_elems(item_type *arr) { + for (int i = 0; i < ELEMENTS; i++) { + printf("%d,", arr[i]); + if (i == ELEMENTS - 1) { + printf("\n"); + } + } +} + +void print_pq(struct priority_queue *pq) { + for (int i = 1; i <= pq->len; i++) { + printf("%d,", pq->q[i]); + if (i == pq->len) { + printf("\n"); + } + } +} + +reverse(arr); +for (int i = 0; i < ELEMENTS; i++) { + pq_insert(pq, arr[i]); +} + +print_pq(pq); + +pq_pop_top(pq); + +print_pq(pq); +#+end_src + + +** 4.4 War Story + +Apparently calculating airline tickets is hard + +** 4.5 Mergesort + +** 4.6 Quicksort + +** 4.7 Distribution Sort: Bucketing + +** 4.8 War Story + +** 4.9 Binary Search and Related Algorithms + +** 4.10 Divide-and-Conquer