Compare commits
No commits in common. "55795ec8051c677fbf34786042e80cdbb6729f15" and "1ebe6a7b6819e2ab9bf1bef4b3d9a985c867a8be" have entirely different histories.
55795ec805
...
1ebe6a7b68
71
DataStructures.org
Normal file
71
DataStructures.org
Normal file
@ -0,0 +1,71 @@
|
||||
#+TITLE: Random Data Structures
|
||||
#+AUTHOR: Joseph Ferano
|
||||
#+OPTIONS: ^:{}
|
||||
|
||||
** Stack
|
||||
*** C
|
||||
|
||||
#+begin_src C :includes stdlib.h stdio.h stdbool.h
|
||||
typedef struct Node {
|
||||
struct Node* next;
|
||||
/* void *data; */
|
||||
int data;
|
||||
} Node;
|
||||
|
||||
typedef struct Stack {
|
||||
Node* top;
|
||||
int length;
|
||||
} Stack;
|
||||
|
||||
/* void stack_create(void* data) { */
|
||||
Stack *stack_create(int data) {
|
||||
Stack *stack = malloc(sizeof(Stack));
|
||||
Node *node = malloc(sizeof(Node));
|
||||
node->data = data;
|
||||
node->next = NULL;
|
||||
stack->top = node;
|
||||
stack->length = 1;
|
||||
return stack;
|
||||
}
|
||||
|
||||
int stack_pop(Stack *stack) {
|
||||
Node *top = stack->top;
|
||||
Node *next = top->next;
|
||||
if (stack->length > 0 && next != NULL) {
|
||||
stack->top = next;
|
||||
stack->length--;
|
||||
int value = top->data;
|
||||
free(top);
|
||||
return value;
|
||||
} else {
|
||||
// A better API design would be to return a bool and the int as a pointer
|
||||
// Although once we switch away from int and use void pointers, might not be needed
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* void stack_push(Stack *stack, void *data) { */
|
||||
void stack_push(Stack *stack, int data) {
|
||||
Node *node = malloc(sizeof(Node));
|
||||
Node *top = stack->top;
|
||||
node->data = data;
|
||||
node->next = top;
|
||||
stack->top = node;
|
||||
stack->length++;
|
||||
}
|
||||
|
||||
/* void* stack_peak(Stack *stack) { */
|
||||
int stack_peak(Stack *stack) {
|
||||
return stack->top->data;
|
||||
}
|
||||
|
||||
void stack_print(Stack *stack) {
|
||||
Node *current = stack->top;
|
||||
int i = 0;
|
||||
while (current != NULL) {
|
||||
printf("Stack at %d: %d\n", ++i, current->data);
|
||||
current = current->next;
|
||||
}
|
||||
printf("------------------\n");
|
||||
}
|
||||
#+end_src
|
138
GrokkingAlgorithms.org
Normal file
138
GrokkingAlgorithms.org
Normal file
@ -0,0 +1,138 @@
|
||||
#+TITLE: Notes & Exercises: Grokking Algorithms
|
||||
#+AUTHOR: Joseph Ferano
|
||||
#+OPTIONS: ^:{}
|
||||
|
||||
* Algorithms from the book
|
||||
|
||||
** Recursive sum
|
||||
|
||||
*** OCaml
|
||||
#+begin_src ocaml
|
||||
let rec sum_rec = function
|
||||
| [] -> 0
|
||||
| n::ns -> n + sum_rec ns;;
|
||||
|
||||
sum_rec [2;3;4;2;1];;
|
||||
#+end_src
|
||||
|
||||
#+RESULTS:
|
||||
: 12
|
||||
|
||||
#+begin_src ocaml
|
||||
let sum_rec_tail list =
|
||||
let rec f acc = function
|
||||
| [] -> 0
|
||||
| n::ns -> sum_rec (acc + n) ns
|
||||
in f 0 list;;
|
||||
|
||||
sum_rec [2;3;4;2;1];;
|
||||
#+end_src
|
||||
|
||||
#+RESULTS:
|
||||
: 12
|
||||
|
||||
*** Python
|
||||
|
||||
#+begin_src python :results output
|
||||
def sum_rec(arr):
|
||||
if not arr:
|
||||
return 0
|
||||
else:
|
||||
return arr[0] + sum_rec(arr[1:])
|
||||
|
||||
print(sum_rec([1,2,3]))
|
||||
#+end_src
|
||||
|
||||
#+RESULTS:
|
||||
: 6
|
||||
|
||||
** Binary Search
|
||||
*** OCaml
|
||||
|
||||
#+begin_src ocaml
|
||||
let binary_search items target =
|
||||
let rec f low high =
|
||||
match (high - low) / 2 + low with
|
||||
| mid when target = items.(mid) -> Some items.(mid)
|
||||
| mid when target < items.(mid) -> f low mid
|
||||
| mid when target > items.(mid) -> f mid high
|
||||
| _ -> None
|
||||
in f 0 (Array.length items);;
|
||||
|
||||
binary_search [|1;2;3;4;5|] 3;;
|
||||
#+end_src
|
||||
|
||||
|
||||
** Selection Sort
|
||||
|
||||
Runtime O(n^{2})
|
||||
|
||||
*** Python
|
||||
|
||||
#+begin_src python
|
||||
def selection_sort(arr):
|
||||
sorted_list = []
|
||||
for i in range(len(arr)):
|
||||
max = arr[0]
|
||||
for count, value in enumerate(arr):
|
||||
if value > max:
|
||||
max = value
|
||||
sorted_list.append(max)
|
||||
arr.remove(max)
|
||||
return sorted_list
|
||||
|
||||
selection_sort([2,1,5,3,4])
|
||||
#+end_src
|
||||
|
||||
*** OCaml
|
||||
|
||||
Really reinventing the wheel on this one...
|
||||
|
||||
#+begin_src ocaml
|
||||
let max_element = function
|
||||
| [] -> invalid_arg "empty list"
|
||||
| x::xs ->
|
||||
let rec f acc = function
|
||||
| [] -> acc
|
||||
| x::xs -> f (if x > acc then x else acc) xs
|
||||
in f x xs
|
||||
|
||||
let remove item list =
|
||||
let rec f acc item = function
|
||||
| [] -> List.rev acc
|
||||
| x::xs -> if item = x then (List.rev acc) @ xs else f (x::acc) item xs
|
||||
in f [] item list
|
||||
|
||||
let selection_sort list =
|
||||
let rec f acc = function
|
||||
| [] -> acc
|
||||
| xs ->
|
||||
let m = max xs
|
||||
in f (m::acc) (remove m xs)
|
||||
in f [] list
|
||||
#+end_src
|
||||
|
||||
** Quicksort
|
||||
|
||||
*** Python
|
||||
#+begin_src python
|
||||
import random
|
||||
|
||||
def quicksort(arr):
|
||||
if len(arr) < 2:
|
||||
return arr
|
||||
elif len(arr) == 2:
|
||||
if arr[0] > arr[1]:
|
||||
temp = arr[1]
|
||||
arr[1] = arr[0]
|
||||
arr[0] = temp
|
||||
return arr
|
||||
else:
|
||||
# Pick a random pivot
|
||||
index = random.randrange(0, len(arr))
|
||||
pivot = arr.pop(index)
|
||||
left = [x for x in arr if x <= pivot]
|
||||
right = [x for x in arr if x > pivot]
|
||||
return quicksort(left) + [pivot] + quicksort(right)
|
||||
#+end_src
|
||||
|
20
LICENSE
20
LICENSE
@ -1,20 +0,0 @@
|
||||
Copyright (c) 2023 Joseph Ferano - joseph@ferano.io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
README.org
23
README.org
@ -1,23 +0,0 @@
|
||||
* About
|
||||
|
||||
[[./Notes.org]] contains notes and solutions to exercises, as well as
|
||||
implementations of [[https://www.algorist.com/][The Algorithm Design Manual]]. All notes and source code can be
|
||||
found in this single file and the reason for that is that it uses a style of
|
||||
programming known as [[https://en.wikipedia.org/wiki/Literate_programming][Literate Programming]] with the help of Emacs, org-mode, and
|
||||
[[https://orgmode.org/worg/org-contrib/babel/intro.html][org-babel]]. Most code implementations are done in C, however, some have been also
|
||||
implemented in Python or OCaml in order to compare and contrast styles. All code
|
||||
is executable from within Emacs by just running ~C-c C-c~ while the cursor is in
|
||||
the source code block.
|
||||
|
||||
* Progress
|
||||
|
||||
So far, the first 6 chapters have a decent amount of coverage. The focus is more
|
||||
on implementing the actual data structures and algorithms discussed in the book,
|
||||
rather than working through the exercises, although some have solutions. I plan
|
||||
on revisiting the book from time to time, to keep developing my knowledge of
|
||||
Data Structures and Algorithms.
|
||||
|
||||
|
||||
* Screenshot of org-mode
|
||||
|
||||
[[./screenshot.png]]
|
@ -1,8 +1,9 @@
|
||||
#+TITLE: Notes & Exercises: The Algorith Design Manual
|
||||
#+AUTHOR: Joseph Ferano
|
||||
#+STARTUP: overview
|
||||
#+OPTIONS: ^:{}
|
||||
|
||||
* Chapter 1 - Introduction
|
||||
* Chapter 1
|
||||
|
||||
** 1.1 Robots
|
||||
|
||||
@ -69,7 +70,7 @@ structures. These fundamental structures include;
|
||||
** 1.5-1.6 War Story about Psychics
|
||||
|
||||
|
||||
* Chapter 2 - Algorithm Analyses
|
||||
* Chapter 2
|
||||
|
||||
** 2.1 RAM Model of Computation
|
||||
|
||||
@ -125,52 +126,6 @@ Apparently you can do arithmetic on the Big Oh functions
|
||||
** 2.5 Efficiency
|
||||
|
||||
*** Selection Sort
|
||||
|
||||
**** OCaml
|
||||
|
||||
Really reinventing the wheel on this one...
|
||||
|
||||
#+begin_src ocaml
|
||||
let max_element = function
|
||||
| [] -> invalid_arg "empty list"
|
||||
| x::xs ->
|
||||
let rec f acc = function
|
||||
| [] -> acc
|
||||
| x::xs -> f (if x > acc then x else acc) xs
|
||||
in f x xs
|
||||
|
||||
let remove item list =
|
||||
let rec f acc item = function
|
||||
| [] -> List.rev acc
|
||||
| x::xs -> if item = x then (List.rev acc) @ xs else f (x::acc) item xs
|
||||
in f [] item list
|
||||
|
||||
let selection_sort list =
|
||||
let rec f acc = function
|
||||
| [] -> acc
|
||||
| xs ->
|
||||
let m = max xs
|
||||
in f (m::acc) (remove m xs)
|
||||
in f [] list
|
||||
#+end_src
|
||||
|
||||
**** Python
|
||||
|
||||
#+begin_src python
|
||||
def selection_sort(arr):
|
||||
sorted_list = []
|
||||
for i in range(len(arr)):
|
||||
max = arr[0]
|
||||
for count, value in enumerate(arr):
|
||||
if value > max:
|
||||
max = value
|
||||
sorted_list.append(max)
|
||||
arr.remove(max)
|
||||
return sorted_list
|
||||
|
||||
selection_sort([2,1,5,3,4])
|
||||
#+end_src
|
||||
|
||||
**** C
|
||||
|
||||
#+begin_src C :includes stdio.h
|
||||
@ -202,6 +157,17 @@ int nums[9] = { 2, 4, 9, 1, 3, 8, 5, 7, 6 };
|
||||
selection_sort(nums, 9);
|
||||
#+end_src
|
||||
|
||||
#+RESULTS:
|
||||
| 2 | 4 | 9 | 1 | 3 | 8 | 5 | 7 | 6 | |
|
||||
| 1 | 4 | 9 | 2 | 3 | 8 | 5 | 7 | 6 | |
|
||||
| 1 | 2 | 9 | 4 | 3 | 8 | 5 | 7 | 6 | |
|
||||
| 1 | 2 | 3 | 4 | 9 | 8 | 5 | 7 | 6 | |
|
||||
| 1 | 2 | 3 | 4 | 9 | 8 | 5 | 7 | 6 | |
|
||||
| 1 | 2 | 3 | 4 | 5 | 8 | 9 | 7 | 6 | |
|
||||
| 1 | 2 | 3 | 4 | 5 | 6 | 9 | 7 | 8 | |
|
||||
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 8 | |
|
||||
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
|
||||
|
||||
|
||||
*** Insertion Sort
|
||||
**** C
|
||||
@ -249,7 +215,6 @@ no real impact on the growth rate; log_{2} and log_{3} are roughly equivalent.
|
||||
|
||||
Cool story bro
|
||||
|
||||
|
||||
** 2.9 Advanced Analysis
|
||||
|
||||
Some advanced stuff
|
||||
@ -259,11 +224,11 @@ Some advanced stuff
|
||||
Binary search on a sorted array of only log n items
|
||||
- *log n / log log n*
|
||||
- log^{2} n
|
||||
- sqrt(n)
|
||||
- \sqrt{,}n
|
||||
|
||||
There are also limits and dominance relations
|
||||
|
||||
* Chapter 3 - Data Structures
|
||||
* Chapter 3
|
||||
|
||||
** 3.1 Contiguous vs Linked Data Structures
|
||||
|
||||
@ -287,145 +252,9 @@ However, pointers require extra space for storing pointer fields
|
||||
*** Stacks
|
||||
/(PUSH, /POP/) LIFO, useful in executing recursive algorithms.
|
||||
|
||||
#+begin_src C :includes stdlib.h stdio.h stdbool.h
|
||||
typedef struct Node {
|
||||
struct Node* next;
|
||||
/* void *data; */
|
||||
int data;
|
||||
} Node;
|
||||
|
||||
typedef struct Stack {
|
||||
Node* top;
|
||||
int length;
|
||||
} Stack;
|
||||
|
||||
/* void stack_create(void* data) { */
|
||||
Stack *stack_create(int data) {
|
||||
Stack *stack = malloc(sizeof(Stack));
|
||||
Node *node = malloc(sizeof(Node));
|
||||
node->data = data;
|
||||
node->next = NULL;
|
||||
stack->top = node;
|
||||
stack->length = 1;
|
||||
return stack;
|
||||
}
|
||||
|
||||
int stack_pop(Stack *stack) {
|
||||
Node *top = stack->top;
|
||||
Node *next = top->next;
|
||||
if (stack->length > 0 && next != NULL) {
|
||||
stack->top = next;
|
||||
stack->length--;
|
||||
int value = top->data;
|
||||
free(top);
|
||||
return value;
|
||||
} else {
|
||||
// A better API design would be to return a bool and the int as a pointer
|
||||
// Although once we switch away from int and use void pointers, might not be needed
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* void stack_push(Stack *stack, void *data) { */
|
||||
void stack_push(Stack *stack, int data) {
|
||||
Node *node = malloc(sizeof(Node));
|
||||
Node *top = stack->top;
|
||||
node->data = data;
|
||||
node->next = top;
|
||||
stack->top = node;
|
||||
stack->length++;
|
||||
}
|
||||
|
||||
/* void* stack_peak(Stack *stack) { */
|
||||
int stack_peak(Stack *stack) {
|
||||
return stack->top->data;
|
||||
}
|
||||
|
||||
void stack_print(Stack *stack) {
|
||||
Node *current = stack->top;
|
||||
int i = 0;
|
||||
while (current != NULL) {
|
||||
printf("Stack at %d: %d\n", ++i, current->data);
|
||||
current = current->next;
|
||||
}
|
||||
printf("------------------\n");
|
||||
}
|
||||
#+end_src
|
||||
|
||||
*** Queues
|
||||
(/ENQUEUE/, /DEQUEUE/) FIFO, useful for breadth-first searches in graphs.
|
||||
|
||||
#+name: queue
|
||||
#+begin_src C :includes stdio.h stdlib.h
|
||||
#define QBUFSIZE 64
|
||||
#define T int
|
||||
|
||||
struct queue {
|
||||
T buf[QBUFSIZE];
|
||||
int start;
|
||||
int end;
|
||||
int size;
|
||||
};
|
||||
|
||||
struct queue *q_create() {
|
||||
struct queue *q = calloc(1, sizeof(struct queue));
|
||||
q->start = 0;
|
||||
q->end = 0;
|
||||
}
|
||||
|
||||
void q_enqueue(struct queue *q, T item) {
|
||||
if (q->size == QBUFSIZE) {
|
||||
printf("Queue Overflow");
|
||||
exit(1);
|
||||
}
|
||||
q->buf[q->end] = item;
|
||||
q->end = ++q->end % QBUFSIZE;
|
||||
q->size++;
|
||||
}
|
||||
|
||||
T q_dequeue(struct queue *q) {
|
||||
if (q->size == 0) {
|
||||
printf("Queue empty");
|
||||
exit(1);
|
||||
}
|
||||
T item = q->buf[q->start++];
|
||||
q->size--;
|
||||
return item;
|
||||
}
|
||||
|
||||
T q_peek(struct queue *q) {
|
||||
if (q->size == 0) {
|
||||
printf("Queue empty");
|
||||
exit(1);
|
||||
}
|
||||
return q->buf[q->start];
|
||||
}
|
||||
|
||||
bool q_empty(struct queue *q) {
|
||||
return q->size <= 0;
|
||||
}
|
||||
|
||||
void q_print(struct queue *q) {
|
||||
printf("Qeueu_Elements: ");
|
||||
for (int i = 0; i < q->size; i++) {
|
||||
printf("%i-", q->buf[(i + q->start) % QBUFSIZE]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// struct queue *q = q_create();
|
||||
// q_enqueue(q, 1);
|
||||
// q_enqueue(q, 2);
|
||||
// q_enqueue(q, 3);
|
||||
// q_enqueue(q, 4);
|
||||
// q_dequeue(q);
|
||||
// q_dequeue(q);
|
||||
// q_enqueue(q, 5);
|
||||
// q_enqueue(q, 6);
|
||||
// q_print(q);
|
||||
#+end_src
|
||||
|
||||
|
||||
** 3.3 Dictionaries
|
||||
|
||||
Not just hashtables but anything that can provide access to data by
|
||||
@ -530,7 +359,7 @@ printf("Final: %s\n", str);
|
||||
| After: | sirhC | si | eman | yM |
|
||||
| Final: | Chris | is | name | My |
|
||||
|
||||
* Chapter 4 - Sorting and Searching
|
||||
* Chapter 4
|
||||
|
||||
** 4.1 Applications of Sorting
|
||||
|
||||
@ -590,21 +419,19 @@ because the nodes aren't guaranteed to be ordered, only the relationship between
|
||||
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 {
|
||||
// TODO: Why did I make this a pointer?
|
||||
item_type *q;
|
||||
int len;
|
||||
int capacity;
|
||||
};
|
||||
|
||||
struct priority_queue *pq_create(int capacity) {
|
||||
item_type *q = calloc(capacity, sizeof(item_type));
|
||||
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;
|
||||
pq->capacity = capacity;
|
||||
}
|
||||
|
||||
int pq_parent(int n) {
|
||||
@ -661,7 +488,7 @@ void pq_bubble_down(struct priority_queue *pq, int n) {
|
||||
}
|
||||
|
||||
void pq_insert(struct priority_queue *pq, item_type x) {
|
||||
if (pq->len >= pq->capacity) {
|
||||
if (pq->len >= PQ_SIZE) {
|
||||
printf("Error: Priority Queue Overflow");
|
||||
return;
|
||||
}
|
||||
@ -740,7 +567,7 @@ print_pq(pq);
|
||||
|
||||
** 4.4 War Story
|
||||
|
||||
Apparently calculating the price of airline tickets is hard.
|
||||
Apparently calculating airline tickets is hard
|
||||
|
||||
** 4.5 Mergesort
|
||||
|
||||
@ -885,29 +712,6 @@ new arrays to hold the new sorted elements. However, it becomes more challenging
|
||||
when doing it in place. This algorithm requires 3 pointers to keep track of the
|
||||
mid point, the iterator, and the high, then finish once mid passes h.
|
||||
|
||||
*** Python
|
||||
|
||||
#+begin_src python
|
||||
import random
|
||||
|
||||
def quicksort(arr):
|
||||
if len(arr) < 2:
|
||||
return arr
|
||||
elif len(arr) == 2:
|
||||
if arr[0] > arr[1]:
|
||||
temp = arr[1]
|
||||
arr[1] = arr[0]
|
||||
arr[0] = temp
|
||||
return arr
|
||||
else:
|
||||
# Pick a random pivot
|
||||
index = random.randrange(0, len(arr))
|
||||
pivot = arr.pop(index)
|
||||
left = [x for x in arr if x <= pivot]
|
||||
right = [x for x in arr if x > pivot]
|
||||
return quicksort(left) + [pivot] + quicksort(right)
|
||||
#+end_src
|
||||
|
||||
** 4.7 Distribution Sort: Bucketing
|
||||
|
||||
Two other sorting algorithms function similarly by subdividing the sorting
|
||||
@ -931,21 +735,6 @@ dealing with lazy sequences, where you search first by A[1], then A[2], A[4],
|
||||
A[8], A[16], and so forth. You can also use these sorts of bisections on square
|
||||
root problems, whatever those are.
|
||||
|
||||
Here's a simple binary search guessing game;
|
||||
|
||||
#+begin_src ocaml
|
||||
let binary_search items target =
|
||||
let rec f low high =
|
||||
match (high - low) / 2 + low with
|
||||
| mid when target = items.(mid) -> Some items.(mid)
|
||||
| mid when target < items.(mid) -> f low mid
|
||||
| mid when target > items.(mid) -> f mid high
|
||||
| _ -> None
|
||||
in f 0 (Array.length items);;
|
||||
|
||||
binary_search [|1;2;3;4;5|] 3;;
|
||||
#+end_src
|
||||
|
||||
** 4.10 Divide-and-Conquer
|
||||
|
||||
Algorithms that use this technique, Mergesort being the classic example, have
|
||||
@ -958,317 +747,6 @@ relations.
|
||||
It is an equation that is defined in terms of itself, so I guess recursive.
|
||||
Fibonacci is a good example F_{n} = F_{n-1} + F_{n-2}...
|
||||
|
||||
* Chapter 5 - Graph Traversal
|
||||
|
||||
** 5.1 Graphs
|
||||
|
||||
Graphs are G = (E,V), so a set of edges and a set of vertices. Many real world
|
||||
systems can be modeled as graphs, such as road/city networks. Many algorithmic
|
||||
problems become much simpler when modeled with graphs. There are several flavors
|
||||
of graphs;
|
||||
|
||||
- Directed vs Undirected
|
||||
- Weighted vs Unweighted
|
||||
- Simple vs Non-simple
|
||||
- Spares vs Dense
|
||||
- Cyclic vs Acyclic
|
||||
- Embedded vs Topological
|
||||
- Implicted vs Explicit
|
||||
- Labeled vs Unlabeled
|
||||
|
||||
Social networks provide an interesting way to analyze each one of these
|
||||
considerations.
|
||||
|
||||
** 5.2 Data Structures for Graphs
|
||||
|
||||
Which data structure we use will impact the time and space complexity of certain
|
||||
operations.
|
||||
|
||||
- Adjecency Matrix
|
||||
You can use an /n/ x /m/ matrix, where each /(i,j)/ index answers whether an edge
|
||||
exists. However, with sparse graphs, there might be a lot of wasted space.
|
||||
|
||||
- Adjencency Lists
|
||||
Use linked lists instead, however it is harder to verify if a certain edge
|
||||
exists. This can be mitigated by collecting them in a BFS of DFS.
|
||||
|
||||
#+name: graph
|
||||
#+begin_src C :includes stdio.h stdlib.h stdbool.h
|
||||
#define MAXV 1000
|
||||
|
||||
struct edgenode {
|
||||
int y;
|
||||
int weight;
|
||||
struct edgenode *next;
|
||||
};
|
||||
|
||||
struct graph {
|
||||
struct edgenode *edges[MAXV];
|
||||
int degree[MAXV];
|
||||
int nvertices;
|
||||
int nedges;
|
||||
bool directed;
|
||||
};
|
||||
|
||||
void initialize_graph(struct graph *g, bool directed) {
|
||||
g->nvertices = 0;
|
||||
g->nedges = 0;
|
||||
g->directed = directed;
|
||||
for (int i = 0; i < MAXV; i++) g->degree[i] = 0;
|
||||
for (int i = 0; i < MAXV; i++) g->edges[i] = NULL;
|
||||
}
|
||||
|
||||
// TODO: We have to read whether the graph is directed or not and
|
||||
// insert edges twice
|
||||
void insert_edge(struct graph *g, int x, int y) {
|
||||
struct edgenode *p;
|
||||
p = malloc(sizeof(struct edgenode));
|
||||
p->weight = 0;
|
||||
p->y = y;
|
||||
p->next = g->edges[x];
|
||||
|
||||
g->edges[x] = p;
|
||||
g->degree[x]++;
|
||||
|
||||
g->nedges++;
|
||||
}
|
||||
|
||||
void print_graph(struct graph *g) {
|
||||
int i;
|
||||
struct edgenode *p;
|
||||
|
||||
for (i = 0; i < g->nvertices; i++) {
|
||||
printf("V%d", i+1);
|
||||
p = g->edges[i];
|
||||
while (p != NULL) {
|
||||
printf("->%d", p->y);
|
||||
p = p->next;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
struct graph *g = malloc(sizeof(struct graph));
|
||||
initialize_graph(g, true);
|
||||
g->nvertices = 3;
|
||||
insert_edge(g, 0, 1);
|
||||
insert_edge(g, 0, 2);
|
||||
insert_edge(g, 0, 3);
|
||||
insert_edge(g, 1, 1);
|
||||
insert_edge(g, 1, 3);
|
||||
insert_edge(g, 2, 1);
|
||||
|
||||
print_graph(g);
|
||||
#+end_src
|
||||
|
||||
|
||||
** 5.3 War Story
|
||||
|
||||
Apparently, computers were really slow before. That and it's better to keep
|
||||
asymptotics in mind even if it's about the same.
|
||||
|
||||
** 5.4 War Story
|
||||
|
||||
Apparently loading data itself can be really slow.
|
||||
|
||||
** 5.5 Traversing a Graph
|
||||
|
||||
When traversing a graph, it's useful to keep track of the state of each vertex,
|
||||
whether the vertex has been /undiscovered/, /discovered/, or /processed/.
|
||||
|
||||
** 5.6 Breadth-First Search (BFS)
|
||||
|
||||
This is a C implementation of BFS, however at the moment it doesn't really do
|
||||
much. The important thing here is that a BFS uses a queue to visit every node in
|
||||
the graph.
|
||||
|
||||
#+begin_src C :includes stdio.h stdlib.h stdbool.h :noweb yes
|
||||
<<queue>>
|
||||
<<graph>>
|
||||
|
||||
bool processed[QBUFSIZE];
|
||||
bool discovered[QBUFSIZE];
|
||||
int parent[QBUFSIZE];
|
||||
|
||||
void initialize_search(struct graph *g) {
|
||||
for (int i = 0; i < g->nvertices; i++) {
|
||||
processed[i] = discovered[i] = false;
|
||||
parent[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void process_edge(T vertex, T edge) {}
|
||||
|
||||
void bfs(struct graph *g, int start) {
|
||||
struct queue *q = q_create();
|
||||
initialize_search(g);
|
||||
q_enqueue(q, start);
|
||||
discovered[start] = true;
|
||||
|
||||
while (!q_empty(q)) {
|
||||
T v = q_dequeue(q);
|
||||
processed[v] = true;
|
||||
struct edgenode *p = g->edges[v];
|
||||
while (p != NULL) {
|
||||
T y = p->y;
|
||||
if ((processed[y] == false) || g->directed) {
|
||||
process_edge(v, y);
|
||||
}
|
||||
if (discovered[y] == false) {
|
||||
q_enqueue(q, y);
|
||||
discovered[y] = true;
|
||||
parent[y] = v;
|
||||
}
|
||||
p = p->next;
|
||||
}
|
||||
}
|
||||
|
||||
free(q);
|
||||
}
|
||||
#+end_src
|
||||
|
||||
** 5.7 Applications of BFS
|
||||
|
||||
The one project we worked on, the "Six Degrees of Bacon", which is just a six
|
||||
degrees of separation problem. However, one interesting point to the "six
|
||||
degrees of separation" between all humans is that it assumes that all human
|
||||
social networks are "connected", meaning that there may be vertices on the graph
|
||||
that are not connected to the others and form their own little cluster. BFS
|
||||
works well for this, since it's basically a shortest path problem.
|
||||
|
||||
Other applications include solving a Rubiks cube so in theory you should now
|
||||
know enough to be able to create a solver, because each legal move up until the
|
||||
solved move should be connected on the graph.
|
||||
|
||||
The vertex-coloring problem assigns each vertex a label or color such that no
|
||||
edge links any two vertices of the same label/color, ideally using as few colors
|
||||
as possible. A graph is /bipartite/ if it can be colored without conflicts with
|
||||
just two colors.
|
||||
|
||||
** 5.8 Depth-first Search (DFS)
|
||||
|
||||
While BFS uses a queue, DFS uses a stack, but since recursion models a stack
|
||||
already, we don't need an extra data structure. Here is the C implementation
|
||||
mostly just copied from the book.
|
||||
|
||||
#+begin_src C :includes stdio.h stdlib.h stdbool.h :noweb yes
|
||||
<<graph>>
|
||||
void dfs(struct graph *g, int v) {
|
||||
edgenode *p;
|
||||
int y;
|
||||
|
||||
// No idea where this symbol comes from
|
||||
// They seem to be global
|
||||
if (finished) return;
|
||||
|
||||
discovered[v] = true;
|
||||
// And these two as well
|
||||
entry_time[v] = ++time;
|
||||
p = g->edges[v];
|
||||
while (p != NULL) {
|
||||
y = p->y;
|
||||
if (discovered[y] == false) {
|
||||
parent[y] == v;
|
||||
process_edge(v, y);
|
||||
dfs(g, y);
|
||||
} else if (!processed[y] || g->directed) {
|
||||
process_edge(v, y);
|
||||
if (finished) return;
|
||||
p = p->next;
|
||||
}
|
||||
|
||||
time = time + 1;
|
||||
exit_time[v] = time;
|
||||
processed[v] = true;
|
||||
}
|
||||
}
|
||||
#+end_src
|
||||
|
||||
This uses a technique called Backtracking which still hasn't been explored, but
|
||||
the idea is that it goes further down until it cannot process anything further,
|
||||
then goes all the way back up to where it can start branching out again.
|
||||
|
||||
** 5.9 Applications of DFS
|
||||
|
||||
DFS is useful for finding articulation vertices, which are basically weak points
|
||||
where removal will cause other vertices to become disconnected. It's also useful
|
||||
for finding cycles.
|
||||
|
||||
** 5.10 DFS on Directed Graphs
|
||||
|
||||
The important operation here is topological sorting which is useful with
|
||||
Directed Acyclic Graphs (DAGs). We can also check if a DAG is strongly
|
||||
connected, meaning that we won't run into any dead ends since we cannot
|
||||
backtrack. However, these graphs can be partitioned.
|
||||
|
||||
* Chapter 6 - Weighted Graph Algorithms
|
||||
|
||||
** 6.1 Minimum Spanning Trees
|
||||
|
||||
Weighted graphs open up a whole new universe of algorithms which tend to be a
|
||||
bit more helpful in modeling real world stuff like roads. A minimum spanning
|
||||
tree would have all vertices connected but uses the smallest weights for all its
|
||||
edges.
|
||||
|
||||
Prim's Algorithm can be used to construct a spanning tree but because it is a
|
||||
greedy algorithm. Kruskal's algorithm is a more efficient way to find MSTs with
|
||||
the use of a /union-find/. This data structure is a /set partition/, which finds
|
||||
disjointed subsets. These can also be used to solve other interesting problems;
|
||||
|
||||
- Maximum Spanning Trees
|
||||
- Minimum Product Spanning Trees
|
||||
- Minimum Bottleneck Spanning Tree
|
||||
|
||||
However, Steiner Tree and Low-degree Spanning Tree apparently cannot be solved
|
||||
with the previous two algorithms.
|
||||
|
||||
** 6.2 War Story
|
||||
|
||||
Minimum Spanning Trees and its corresponding algorithms can help solve Traveling
|
||||
Salesman Problems
|
||||
|
||||
** 6.3 Shortest Paths
|
||||
|
||||
In an unweighted graph, BFS will find the shortest path between two nodes. For
|
||||
weighted graphs the shortest path might have many more edges. Dijstra's
|
||||
algorithm can help us find the shortest path in a weighted path. It runs in
|
||||
O(n^{2}) time. One caveat is that it does not work with negative weighted edges.
|
||||
|
||||
Another problem in this space is the all-pairs shortest path, and for this we
|
||||
would use the O(n^{3}) Floyd-Warshall algorithm which uses an adjacency matrix
|
||||
instead since we needed to construct one anyway to track all the possible pairs.
|
||||
It's a useful algorithm for figuring out /transitive closure/ which is a fancy way
|
||||
of asking if some vertices are reachable from a node.
|
||||
|
||||
*** TODO Dijstra's
|
||||
#+begin_src C :includes stdio.h stdlib.h
|
||||
|
||||
#+end_src
|
||||
|
||||
|
||||
** 6.4 War Story
|
||||
|
||||
Kids are probably too young to know what the author is even talking about with these
|
||||
phone codes.
|
||||
|
||||
** 6.5 Network Flows and Bipartite Matching
|
||||
|
||||
Bipartite matching is where no two edges share a vertex. There are also residual
|
||||
flow graphs which are useful for network bandwidth optimization. Flow algorithms
|
||||
generally solve problems related to edge and vertex connectivity. Edmonds and
|
||||
Karp runs at O(n^{3}).
|
||||
|
||||
*** TODO Network Flow
|
||||
|
||||
** 6.6 Design Graphs, Not Algorithms
|
||||
|
||||
Modeling problems as graphs is useful for a variety of applications; pathfinding
|
||||
in games, DNA sequencing, smallest set of non-overlapping rectangles in a plain,
|
||||
filename shortening, line segmentation for optical character-recognition, and
|
||||
the list goes on.
|
||||
|
||||
* Chapter 7 - Combinatorial Search and Heuristic Methods
|
||||
|
||||
** 7.1 Backtracking
|
||||
* Chapter 5
|
||||
|
||||
|
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 92 KiB |
Loading…
x
Reference in New Issue
Block a user