# ECE 250: DSA ## Heaps A heap is a binary tree **stored in an array** in which all levels but the lowest are filled. It is guaranteed that the parent of index $i$ is greater than or equal to the element at index $i$. - the parent of index $i$ is stored at $i/2$ - the left child of index $i$ is stored at $2i$ - the right child of index $i$ is stored at $2i+1$ (Source: Wikimedia Commons) The **heapify** command takes a node and makes it and its children a valid heap. ```rust fn heapify(&mut A: Vec, i: usize) { if A[2*i] >= A[i] { A.swap(2*i, i); heapify(A, 2*i) } else if A[2*i + 1] >= A[i] { A.swap(2*i + 1, i); heapify(A, 2*i + 1) } } ``` Repeatedly heapifying an array from middle to beginning converts it to a heap. ```rust fn build_heap(A: Vec) { let n = A.len() for i in (n/2).floor()..0 { // this is technically not valid but it's much clearer heapify(A, i); } } ``` ### Heapsort Heapsort constructs a heap annd then does magic things that I really cannot be bothered to figure out right now. ```rust fn heapsort(A: Vec) { build_heap(A); let n = A.len(); for i in n..0 { A.swap(1, i); heapify(A, 1); // NOTE: heapify takes into account the changed value of n } } ``` ### Priority queues A priority queue is a heap with the property that it can remove the highest value in $O(\log n)$ time. ```rust fn pop(A: Vec, &n: usize) { let biggest = A[0]; A[0] = n; *n -= 1; heapify(A, 1); return biggest; } ``` ```rust fn insert(A: Vec, &n: usize, key: i32) { *n += 1; let i = n; while i > 1 && A[parent(i)] < key { A[i] = A[parent(i)]; i = parent(i); } A[i] = k; } ``` ## Sorting algorithms ### Quicksort Quicksort operates by selecting a **pivot point** that ensures that everything to the left of the pivot is less than anything to the right of the pivot, which is what partitioning does. ```rust fn partition(A: Vec, left_bound: usize, right_bound: usize) { let i = left_bound; let j = right_bound; while true { while A[j] <= A[right_bound] { j -= 1; } while A[i] >= A[left_bound] { i += 1; } if i < j { A.swap(i, j); } else { return j } // new bound! } } ``` Sorting calls partitioning with smaller and smaller bounds until the collection is sorted. ```rust fn sort(a: Vec, left: usize, right: usize) { if left < right { let pivot = partition(A, left, right); sort(A, left, pivot); sort(A, pivot+1, right); } } ``` - In the best case, if partitioning is even, the time complexity is $T(n)=T(n/2)+\Theta(n)=\Theta(n\log n)$. - In the worst case, if one side only has one element, which occurs if the list is sorted, the time complexity is $\Theta(n^2)$. ### Counting sort If items are or are linked to a number from $1..n$ (duplicates are allowed), counting sort counts the number of each number, then moves things to the correct position. Where $k$ is the size of the counter array, the time complexity is $O(n+k)$. First, construct a count prefix sum array: ```rust fn count(A: Vec, K: usize) { let counter = vec![0; K]; for i in A { counter[i] += 1; } for (index, val) in counter.iter_mut().enumerate() { counter[index + 1] += val; // ignore bounds for cleanliness please :) } return counter } ``` Next, the prefix sum represents the correct position for each item. ```rust fn sort(A: Vec) { let counter = count(A, 100); let sorted = vec![0; A.len()]; for i in n..0 { sorted[counter[A[i]]] = A[i]; counter[A[i]] -= 1; } } ``` ## Graphs !!! definition - A **vertex** is a node. - The **degree** of a node is the number of edges connected to it. - A **connected graph** is such that there exists a path from any node in the graph to any other node. - A **connected component** is a subgraph such that there exists a path from any node in the subgraph to any other node in the subgraph. - A **tree** is a connected graph without cycles.