152 lines
3.5 KiB
Markdown
152 lines
3.5 KiB
Markdown
# 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$
|
|
|
|
|
|
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c4/Max-Heap-new.svg" width=600>(Source: Wikimedia Commons)</img>
|
|
|
|
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;
|
|
}
|
|
}
|
|
```
|