# Week 11 - Heaps
## Team
Team name: Gappies van andere wappies
Date: 16/6/2022
Members
| Role | Name |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------|
| **Facilitator** keeps track of time, assigns tasks and makes sure all the group members are heard and that decisions are agreed upon. |Steohan|
| **Spokesperson** communicates group’s questions and problems to the teacher and talks to other teams; presents the group’s findings. |Ingmar|
| **Reflector** observes and assesses the interactions and performance among team members. Provides positive feedback and intervenes with suggestions to improve groups’ processes. |Ingmar|
| **Recorder** guides consensus building in the group by recording answers to questions. Collects important information and data. |Stijn|
## Activities
Make sure to have the activities signed off regularly to ensure progress is tracked.
Download the provided project and open it in CLion. **NOTE**: in this week you will have to reuse the code of last week. Follow the instructions given in the `main.cpp` file.
### Activity 1: Comparing different data structures ✅
| Data structure | Find-max | Delete-Max | Insert |
|--------------------|:--------:|:----------:|:------:|
| Sorted array | O(1) | O(1) | O(n) |
| Sorted linked list | O(1) | O(1) | O(n) |
| Balanced BST | O(log n) | O(log n) | O(log n)|
Best: Sorted array/ sorted linked list
### Activity 2: Identify valid heaps ✅
1st tree: Min-heap
2nd tree: Not a heap. Because if you look at the path from the root to the bottom left node, it is not sorted. So the heap invariant does not hold true.
3rd tree: Min-heap
### Activity 3: Do it yourself ✅
1st swap: 12 - 3
2nd swap: 12 - 11
After these two swaps, the parent value is higher than 12 so the path is not sorted again. This took 2 swaps.
### Activity 4: Worst-case time complexities ✅
| Data structure | Find-max | Delete-Max | Insert | Find |
|-----------------|:--------:|:----------:|:------:|:----:|
| Balanced BST | O(log n) | O(log n) |O(log n)| O(log n) |
| Binary max-heap | O(1) | O(log n) |O(log n)| O(n) |
### Activity 5: Index computations ✅
```c++
size_t maxheap::parent_index(size_t index) {
// Activity 5
return floor(((double)index - 1) / 2);
}
size_t maxheap::left_child_index(size_t index) {
// Activity 5
size_t left_child = index * 2 + 1;
if(left_child > size() - 1) {
return npos;
}
return left_child;
}
size_t maxheap::right_child_index(size_t index) {
// Activity 5
size_t right_child = index * 2 + 2;
if(right_child > size() - 1) {
return npos;
}
return right_child;
}
```
Executing this code outputs:
```
test_left_child_index PASS
test_right_child_index PASS
test_parent_index PASS
```
### Activity 6: Implement the find-max operation ✅
```c++
const task &maxheap::maximum() const {
// Activity 6
return m_values[0];
}
```
The max value is simply the first index.
### Activity 7: Bubbling up ✅
```c++
size_t maxheap::bubble_up(size_t index) {
// Activity 7
// while the value at the given index is greater than its parent value,
// bubble it up
// using the functions swap and parent_index
size_t swaps = 0;
size_t p_index;
int priority;
int parent_priority;
do {
p_index = parent_index(index);
// Make sure there is a parent node.
if(p_index != npos) {
priority = m_values[index].priority;
parent_priority = m_values[p_index].priority;
if(priority > parent_priority) {
swap(index, p_index);
swaps += 1;
// Swap indices
index = p_index;
}
}
} while(priority > parent_priority && p_index != npos);
return swaps;
}
```
### Activity 8: Bubbling down ✅
```c++
size_t maxheap::bubble_down(size_t index) {
size_t swaps = 0;
auto currentIndex = index;
auto leftChildIndex = left_child_index(currentIndex);
auto rightChildIndex = right_child_index(currentIndex);
while (m_values[currentIndex] < m_values[leftChildIndex] || m_values[currentIndex] < m_values[rightChildIndex]) {
if (std::max(m_values[leftChildIndex].priority, m_values[rightChildIndex].priority) == m_values[leftChildIndex].priority) {
swap(currentIndex, left_child_index(currentIndex));
swaps++;
currentIndex = left_child_index(currentIndex);
leftChildIndex = left_child_index(currentIndex);
rightChildIndex = right_child_index(currentIndex);
continue;
} else {
swap(currentIndex, right_child_index(currentIndex));
swaps++;
currentIndex = right_child_index(currentIndex);
leftChildIndex = left_child_index(currentIndex);
rightChildIndex = right_child_index(currentIndex);
}
}
return swaps;
}
```
### Activity 9: Implement heapify ✅
```c++
size_t maxheap::heapify() {
size_t swaps = 0;
if (!this->m_values.empty()) {
swaps = heapifyTree(0);
}
return swaps;
}
size_t maxheap::heapifyTree(size_t index) {
size_t swaps{0};
auto biggestIndex = index;
auto leftChildIndex = left_child_index(index);
auto rightChildIndex = right_child_index(index);
if (!invariantHolds(leftChildIndex)) {
swaps += heapifyTree(leftChildIndex);
}
if (!invariantHolds(rightChildIndex)) {
swaps +=heapifyTree(rightChildIndex);
}
if (leftChildIndex != npos && this->m_values[leftChildIndex] > this->m_values[biggestIndex]) {
biggestIndex = leftChildIndex;
} else if (rightChildIndex != npos && this->m_values[rightChildIndex] > this->m_values[biggestIndex]) {
biggestIndex = rightChildIndex;
}
if (biggestIndex != index) {
swaps += bubble_down(index);
}
return swaps;
}
bool maxheap::invariantHolds (size_t i) {
auto leftChildIndex = left_child_index(i);
auto rightChildIndex = right_child_index(i);
if (i == npos) {
return true;
}
if (leftChildIndex != npos && m_values[leftChildIndex] > m_values[i]) {
return false;
} else if (rightChildIndex != npos && m_values[rightChildIndex] > m_values[i]) {
return false;
}
return true;
}
```
### Activity 10: Heapify - time complexity ✅
| Number of values | Number of swaps |
|------------------|:----------------:|
| 5 | 3 |
| 10 | 8 |
| 20 | 17 |
| 50 | 47 |
| 100 | 96 |
| 200 | 195 |
| 300 | 295 |
| 400 | 394 |
| 500 | 493 |
| 1000 | 992 |
As you can see the number of swaps is almost exactly the number of values, so the worst case time complexity is O(n)
### Activity 11: Bubbling down vector elements ✅
```c++
size_t left_child_index(std::vector<int> &numbers, size_t index) {
size_t left_child = index * 2 + 1;
if(left_child > numbers.size() - 1) {
return std::string::npos;
}
return left_child;
}
size_t right_child_index(std::vector<int> &numbers, size_t index) {
size_t right_child = index * 2 + 2;
if(right_child > numbers.size() - 1) {
return std::string::npos;
}
return right_child;
}
void swap(std::vector<int> &numbers, size_t first_index, size_t second_index) {
std::swap(numbers[first_index], numbers[second_index]);
}
void bubble_down(std::vector<int> &values, size_t index, size_t count) {
auto currentIndex = index;
auto leftChildIndex = left_child_index(values, currentIndex);
auto rightChildIndex = right_child_index(values, currentIndex);
while ((values[currentIndex] < values[leftChildIndex] || values[currentIndex] < values[rightChildIndex])) {
if (leftChildIndex >= count) break;
if (std::max(values[leftChildIndex], values[rightChildIndex]) == values[leftChildIndex]
|| rightChildIndex == count
|| rightChildIndex == std::string::npos) {
if (leftChildIndex == std::string::npos) break;
swap(values, currentIndex, left_child_index(values, currentIndex));
currentIndex = left_child_index(values, currentIndex);
leftChildIndex = left_child_index(values, currentIndex);
rightChildIndex = right_child_index(values, currentIndex);
continue;
} else {
if (rightChildIndex == std::string::npos) break;
swap(values, currentIndex, right_child_index(values, currentIndex));
currentIndex = right_child_index(values, currentIndex);
leftChildIndex = left_child_index(values, currentIndex);
rightChildIndex = right_child_index(values, currentIndex);
}
}
}
bool invariantHolds (std::vector<int> &numbers, size_t i) {
auto leftChildIndex = left_child_index(numbers,i);
auto rightChildIndex = right_child_index(numbers, i);
if (i == std::string::npos) {
return true;
}
if (leftChildIndex != std::string::npos && numbers[leftChildIndex] > numbers[i]) {
return false;
} else if (rightChildIndex != std::string::npos && numbers[rightChildIndex] > numbers[i]) {
return false;
}
return true;
}
void heapifyTree(std::vector<int> &numbers, size_t index) {
auto biggestIndex = index;
auto leftChildIndex = left_child_index(numbers, index);
auto rightChildIndex = right_child_index(numbers, index);
if (!invariantHolds(numbers, leftChildIndex)) {
heapifyTree(numbers, leftChildIndex);
}
if (!invariantHolds(numbers, rightChildIndex)) {
heapifyTree(numbers, rightChildIndex);
}
if (leftChildIndex != std::string::npos && numbers[leftChildIndex] > numbers[biggestIndex]) {
biggestIndex = leftChildIndex;
} else if (rightChildIndex != std::string::npos && numbers[rightChildIndex] > numbers[biggestIndex]) {
biggestIndex = rightChildIndex;
}
if (biggestIndex != index && index < numbers.size()) {
bubble_down(numbers, index, numbers.size());
}
}
// Activity 11
void heapify(std::vector<int> &numbers) {
if (!numbers.empty()) {
heapifyTree(numbers, 0);
}
}
```
### Activity 12: In-place heap sort ✅
```c++
void heap_sort(std::vector<int> &numbers) {
heapify(numbers);
auto end = numbers.size() - 1;
while (end > 0) {
if (numbers[0] > numbers[end]) std::swap(numbers[0], numbers[end]);
end--;
bubble_down(numbers, 0, end);
}
}
```
### Activity 13: General heap sort ✅
```c++
template <typename T>
size_t left_child_index(std::vector<T> &numbers, size_t index) {
size_t left_child = index * 2 + 1;
if(left_child > numbers.size() - 1) {
return std::string::npos;
}
return left_child;
}
template <typename T>
size_t right_child_index(std::vector<T> &numbers, size_t index) {
size_t right_child = index * 2 + 2;
if(right_child > numbers.size() - 1) {
return std::string::npos;
}
return right_child;
}
template <typename T>
void swap(std::vector<T> &numbers, size_t first_index, size_t second_index) {
std::swap(numbers[first_index], numbers[second_index]);
}
template <typename T>
void bubble_down(std::vector<T> &values, size_t index, size_t count) {
auto currentIndex = index;
auto leftChildIndex = left_child_index(values, currentIndex);
auto rightChildIndex = right_child_index(values, currentIndex);
while ((values[currentIndex] < values[leftChildIndex] || values[currentIndex] < values[rightChildIndex])) {
if (leftChildIndex >= count) break;
if (std::max(values[leftChildIndex], values[rightChildIndex]) == values[leftChildIndex]
|| rightChildIndex == count
|| rightChildIndex == std::string::npos) {
if (leftChildIndex == std::string::npos) break;
swap(values, currentIndex, left_child_index(values, currentIndex));
currentIndex = left_child_index(values, currentIndex);
leftChildIndex = left_child_index(values, currentIndex);
rightChildIndex = right_child_index(values, currentIndex);
continue;
} else {
if (rightChildIndex == std::string::npos) break;
swap(values, currentIndex, right_child_index(values, currentIndex));
currentIndex = right_child_index(values, currentIndex);
leftChildIndex = left_child_index(values, currentIndex);
rightChildIndex = right_child_index(values, currentIndex);
}
}
}
template <typename T>
bool invariantHolds (std::vector<T> &numbers, size_t i) {
auto leftChildIndex = left_child_index(numbers,i);
auto rightChildIndex = right_child_index(numbers, i);
if (i == std::string::npos) {
return true;
}
if (leftChildIndex != std::string::npos && numbers[leftChildIndex] > numbers[i]) {
return false;
} else if (rightChildIndex != std::string::npos && numbers[rightChildIndex] > numbers[i]) {
return false;
}
return true;
}
template <typename T>
void heapifyTree(std::vector<T> &numbers, size_t index) {
auto biggestIndex = index;
auto leftChildIndex = left_child_index(numbers, index);
auto rightChildIndex = right_child_index(numbers, index);
if (!invariantHolds(numbers, leftChildIndex)) {
heapifyTree(numbers, leftChildIndex);
}
if (!invariantHolds(numbers, rightChildIndex)) {
heapifyTree(numbers, rightChildIndex);
}
if (leftChildIndex != std::string::npos && numbers[leftChildIndex] > numbers[biggestIndex]) {
biggestIndex = leftChildIndex;
} else if (rightChildIndex != std::string::npos && numbers[rightChildIndex] > numbers[biggestIndex]) {
biggestIndex = rightChildIndex;
}
if (biggestIndex != index && index < numbers.size()) {
bubble_down(numbers, index, numbers.size());
}
}
template <typename T>
void heapify(std::vector<T> &numbers) {
if (!numbers.empty()) {
heapifyTree(numbers, 0);
}
}
template <typename T>
void heap_sort(std::vector<T> &numbers) {
heapify(numbers);
auto end = numbers.size() - 1;
while (end > 0) {
if (numbers[0] > numbers[end]) std::swap(numbers[0], numbers[end]);
end--;
bubble_down(numbers, 0, end);
}
}
```
## Looking back
### What we've learnt
Which heaps there were, and the advantages of each of them.
### What were the surprises
That heaps could be used in different ways.
### What problems we've encountered
There were no big problems that we encountered.
### What was or still is unclear
Nothing, the things we didn't know we asked in class.
### How did the group perform?
Very well as always. :)