# from joelkit.algorithms import insertion_sort
def insertion_sort(arr): #in place
for i in range(1, len(arr)): #first item already sorted
to_move = arr[i]
curr_i = i
while curr_i > 0 and arr[curr_i - 1] > to_move:
arr[curr_i] = arr[curr_i - 1] #nudge item to the right
curr_i -= 1
arr[curr_i] = to_movetemplate <typename T>
void insertion_sort(std::vector<T>& v) {
for (int i = 1; i < v.size(); i++) {
T el = v[i]; //element to insert
int curr_i = i;
while (curr_i > 0 && v[curr_i - 1] > el) {
v[curr_i] = v[curr_i - 1];
curr_i--;
}
v[curr_i] = el;
}
}Best case: O(n)
Worst case: O(n2)
# from joelkit.algorithms import quick_sort
def quick_sorted(lst): #-> sorted list
# Base case: if list is empty or size 1, nothing to be done
if len(lst) <= 1:
return lst
# Recursive case: find a pivot (efficiently), then partition the list so the items to the left of the partition are less than the partition and the items to the right of the partition are greater than the partition
pivot_i = find_pivot_index(0, len(lst))
pivot = lst[pivot_i]
left = []
right = []
for current_index, current_value in enumerate(lst):
if current_index == pivot_i:
continue
if current_value <= pivot:
left.append(current_value)
else:
right.append(current_value)
sorted_left = quick_sorted(left)
sorted_right = quick_sorted(right)
return [*sorted_left, pivot, *sorted_right]
def find_pivot_index(start_i, end_i):
# Naive deterministic implementation would be
# return start_i
if start_i == end_i:
return start_i
return random.randint(start_i, end_i - 1) #exclude end_i as a possibility
template <typename T>
std::vector<T> quick_sorted(const std::vector<T>& lst) {
// Base case
if (lst.size() <= 1)
return lst;
// Recursive case
int pivot_i = find_pivot_index(0, lst.size());
T pivot = lst[pivot_i];
std::vector<T> left;
std::vector<T> right;
for (int curr_i = 0; curr_i < lst.size(); curr_i++) {
if (curr_i == pivot_i) {
continue;
}
T curr_val = lst[curr_i];
if (curr_val <= pivot) {
left.push_back(curr_val);
} else {
right.push_back(curr_val);
}
}
std::vector<T> sorted_left = quick_sorted(left);
std::vector<T> sorted_right = quick_sorted(right);
std::vector<T> out = sorted_left;
out.push_back(pivot);
out.insert(out.end(), sorted_right.begin(), sorted_right.end());
return out;
}
int find_pivot_index(int start_i, int end_i) {
if (start_i == end_i)
return start_i;
return start_i + rand() % (end_i - start_i);
}Expected: O(nlogn)
Worst case: O(n2)
# from joelkit.algorithms import merge_sort
def merge_sorted(l):
# Base case: already sorted
if len(l) == 0 or len(l) == 1:
return l
# Recursive step
middle = len(l) // 2
left = l[0:middle]
right = l[middle:]
left_sorted = merge_sorted(left)
right_sorted = merge_sorted(right)
return merge(left_sorted, right_sorted)
def merge(l1, l2):
merged = [] #out
i1 = 0 #index on l1
i2 = 0
while i1 < len(l1) or i2 < len(l2): #while there are still items to process
if i1 == len(l1): #must process l2 because l1 already all processed
merged.append(l2[i2])
i2 += 1
elif i2 == len(l2):
merged.append(l1[i1])
i1 += 1
else: #add that which is smaller
if l1[i1] <= l2[i2]:
merged.append(l1[i1])
i1 += 1
else:
merged.append(l2[i2])
i2 += 1
return mergedtemplate <typename T>
std::vector<T> merge_sorted(const std::vector<T>& l) {
if (l.size() == 0 || l.size() == 1)
return l;
int middle = l.size() / 2;
std::vector<T> left(l.begin(), l.begin() + middle);
std::vector<T> right(l.begin() + middle, l.end());
std::vector<T> left_sorted = merge_sorted(left);
std::vector<T> right_sorted = merge_sorted(right);
return merge(left_sorted, right_sorted);
}
template <typename T>
std::vector<T> merge(const std::vector<T>& l1, const std::vector<T>& l2) {
std::vector<T> merged(l1.size() + l2.size());
auto merged_it = merged.begin();
auto i1 = l1.begin();
auto i2 = l2.begin();
while (i1 != l1.end() || i2 != l2.end()) { //while there is still something to process
if (i1 == l1.end()) { //must do l2
*merged_it = *i2;
merged_it++;
i2++;
} else if (i2 == l2.end()) { //must do l1
*merged_it = *i1;
merged_it++;
i1++;
} else {
if (*i1 < *i2) {
*merged_it = *i1;
i1++;
merged_it++;
} else {
*merged_it = *i2;
i2++;
merged_it++;
}
}
}
return merged;
}O(nlogn)
# from joelkit.algorithms import bubble_sort
d# Elements bubble their way to the right
# Topmost element bubbles to the last element
# Second-largest element bubbles to the second to last element
# ...
def bubble_sort(arr):
for i in range(len(arr) - 1): #need -1 because j+1
for j in range(0, len(arr) - 1 - i): #don't need to consider last i items because they are sorted (i largest items bubbled up)
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]template <typename T>
void bubble_sort(std::vector<T>& l) {
for (int i = 0; i < l.size() - 1; i++) {
for (int j = 0; j < l.size() - 1 - i; j++) {
if (l[j] > l[j + 1]) {
T temp = l[j];
l[j] = l[j + 1];
l[j + 1] = temp;
}
}
}
}O(n2)
k-select(list, k) - find the kth-smallest element of the list in O(n) time
# from joelkit.algorithms import k_select
def find_pivot(lst):
medians = []
for i in range(0, len(lst), 5):
five = []
for j in range(i, i + 5):
if j >= len(lst):
break
five.append((j, lst[j]))
five.sort(key=lambda x : x[1])
median = five[len(five) // 2]
medians.append(median)
return sorted(medians, key=lambda x : x[1])[len(medians) // 2]
# return k_select(medians, len(medians) // 2)
def k_select(lst, k):
if k > len(lst) or len(lst) == 0: #not possible (check)
return None
if len(lst) < 10:
return sorted(lst)[k]
pivot_index, pivot_value = find_pivot(lst)
# Partition
left = []
right = []
for i, item in enumerate(lst):
if i == pivot_index:
continue
if item <= pivot_value:
left.append(item)
else:
right.append(item)
# Recursion
if k == len(left): #is the pivot
return pivot_value
if k < len(left):
return k_select(left, k)
else:
return k_select(right, k - len(left) - 1)template <typename T>
using IndexAndValue = std::pair<int, T>;
/** Finds median of medians. @returns its index in the passed-in list as well as its value */
template <typename T, typename KeyFunc>
IndexAndValue<T> find_pivot(std::vector<T> list, KeyFunc key) {
std::vector<IndexAndValue<T>> medians;
for (int i = 0; i < list.size(); i += 5) {
std::vector<IndexAndValue<T>> five;
for (int j = i; j < list.size() && j < i + 5; j++) {
five.push_back(
std::make_pair(j, list[j])
);
}
insertion_sort(five, [&key](IndexAndValue<T> item) { return key(item.second); }); //insertion sort is nice on small lists
IndexAndValue<T> median = five[five.size() / 2]; //do this instead of [2] in the case that |five|<5
medians.push_back(median);
}
insertion_sort(medians, [](IndexAndValue<T> item) { return item.second; });
IndexAndValue<T> median_of_medians = medians[medians.size() / 2];
// IndexAndValue<T> median_of_medians = k_select(medians, medians.size() / 2, [&key](IndexAndValue<T> el) {
// return key(el.second);
// });
return median_of_medians;
}
template <typename T, typename KeyFunc>
T k_select(std::vector<T> list, int k, KeyFunc key) {
if (list.size() < 50) //base case
return insertion_sorted(list, key)[k];
IndexAndValue<T> pivot = find_pivot(list, key);
// Partition
std::vector<T> left;
std::vector<T> right;
for (int i = 0; i < list.size(); i++) {
T item = list[i];
if (i == pivot.first) //skip the pivot. It is neither in left nor right
continue;
if (key(item) <= key(pivot.second))
left.push_back(item);
else
right.push_back(item);
}
// Recursive step
if (k == left.size()) //is pivot
return pivot.second;
else if (k < left.size())
return k_select(left, k, key);
else
return k_select(right, k - left.size() - 1, key); //takes out the left and the pivot
}
template <typename T>
T k_select(std::vector<T> list, int k) {
return k_select(list, k, [](T el) { return el; }); //use the self selector
}partition(list, pivot) - partitions the list such that all the elements to the left of the pivot are less than the pivot and all the elements to the right of the pivot are greater than the pivot. In our implementation, pivot has the start index, end index, and pivot index passed in of the list so that it can do in-place partitioning just for the sublist from the start to end index. It also returns the new index of the pivot (since the pivot moves around during the partition function's work).
# from joelkit.algorithms import partition
def partition(lst, start_i, end_i, pivot_i): # returns new position of the pivot
p = lst[pivot_i] #pivot value
# 1. Put the pivot at the beginning
lst[start_i], lst[pivot_i] = lst[pivot_i], lst[start_i]
# 2. Incorporate every vertex v into the left or right
# Everything before l_i (left index) belongs to the left (<p)
# Everything after l_i but before r_i belongs to the right (>p)
# l_i points to the first element in the right, called r^
# r_i points to v, the current value under consideration
l_i = start_i + 1
for r_i in range(start_i + 1, end_i):
v = lst[r_i] #decide if v should be in left or right
if v > p: #v should be in the right
continue
if v <= p: #v should be in the left
lst[l_i], lst[r_i] = lst[r_i], lst[l_i] #l_i points to r^
l_i += 1
# 3. Move pivot to end of the left
lst[start_i], lst[l_i - 1] = lst[l_i - 1], lst[start_i]
return l_i - 1 #l_i - 1 is where the pivot is now#include "partition.h"
template <typename T>
int partition(std::vector<T>& lst, int start_i, int end_i, int pivot_i) {
T p = lst[pivot_i];
// 1. Put pivot at beginning
lst[pivot_i] = lst[start_i];
lst[start_i] = p;
// 2.
int l_i = start_i + 1;
for (int r_i = start_i + 1; r_i < end_i; r_i++) {
T v = lst[r_i];
if (v > p) {
continue;
}
if (v <= p) {
T rhat = lst[l_i];
lst[l_i] = lst[r_i];
lst[r_i] = rhat;
l_i++;
}
}
// 3. Move pivot to end of the left
lst[start_i] = lst[l_i - 1];
lst[l_i - 1] = p;
return l_i - 1;
}Stack supports push, pop, and peek. It's a last in first out (LIFO) data structure.
#include <cstddef>
#include <iostream>
template <typename T>
class Stack {
private:
T* elems_;
size_t size_ = 0;
size_t capacity_ = 100;
/**
* when capacity is exceeded, double capacity
*/
void grow();
public:
Stack();
Stack(size_t capacity);
~Stack();
void push(T el);
T pop();
T peek() const;
size_t size() const;
bool is_empty() const;
template <typename U>
friend std::ostream& operator<<(std::ostream& os, const Stack<U>& s);
};template <typename T>
Stack<T>::Stack() {
elems_ = new T[capacity_];
}
template <typename T>
Stack<T>::Stack(size_t capacity) {
capacity_ = capacity;
elems_ = new T[capacity];
}
template <typename T>
Stack<T>::~Stack() {
delete[] elems_;
}
template <typename T>
void Stack<T>::push(T elem) {
if (capacity_ == size_) {
grow();
}
elems_[size_] = elem;
size_++;
}
template <typename T>
T Stack<T>::pop() {
T elem = elems_[size_ - 1];
size_--;
return elem;
}
template <typename T>
T Stack<T>::peek() const {
return elems_[size_ - 1];
}
template <typename T>
size_t Stack<T>::size() const {
return size_;
}
template <typename T>
bool Stack<T>::is_empty() const {
return size_ == 0;
}
template <typename T>
void Stack<T>::grow() {
size_t new_capacity_ = capacity_ * 2;
T* new_elems_ = new T[new_capacity_];
// Insert the old elements
for (int i = 0; i < capacity_; i++) {
new_elems_[i] = elems_[i];
}
delete[] elems_;
elems_ = new_elems_;
capacity_ = new_capacity_;
}
template <typename T>
std::ostream& operator<<(std::ostream& os, const Stack<T>& s) {
for (int i = 0; i < s.size_; i++) {
std::cout << s.elems_[i] << " ";
}
return os;
}Queue supports enqueue, dequeue, and peek. It's a first in first out (FIFO) data structure.
template <typename T>
class Queue {
private:
T* elems_;
int h_ = 0; //index of head
int t_ = 0; //index of tail
size_t capacity_ = 100;
void grow();
public:
Queue();
~Queue();
std::size_t size() const;
bool is_empty() const;
void enqueue(T new_element);
T dequeue();
T peek() const;
template <typename U>
friend std::ostream& operator<<(std::ostream& os, const Queue<U>& q);
};#include "queue.h"
template <typename T>
Queue<T>::Queue() {
elems_ = new T[capacity_];
}
template <typename T>
Queue<T>::~Queue() {
delete[] elems_;
}
template <typename T>
std::size_t Queue<T>::size() const {
int out = t_ - h_;
if (out < 0)
out += capacity_;
return out;
}
template <typename T>
bool Queue<T>::is_empty() const {
return size() == 0;
}
template <typename T>
void Queue<T>::enqueue(T elem) {
elems_[t_] = elem;
t_++;
if (t_ == capacity_)
t_ = 0;
if (t_ == h_)
grow(); //grow size by 2-fold
}
template <typename T>
T Queue<T>::dequeue() {
T out = elems_[h_];
h_++;
if (h_ == capacity_)
h_ = 0;
return out;
}
template <typename T>
T Queue<T>::peek() const {
T out = elems_[h_];
return out;
}
template <typename T>
std::ostream& operator<<(std::ostream& os, const Queue<T>& q) {
int p = q.h_;
while (p != q.t_) {
std::cout << q.elems_[p] << " ";
p++;
if (p == q.capacity_) //loop around
p = 0;
}
return os;
}
template <typename T>
void Queue<T>::grow() { //called when h_ is the same as t_
// std::cout << "Grow called" << std::endl;
size_t new_capacity = capacity_ * 2;
T* new_elems = new T[new_capacity];
int new_elem_i = 0;
int p = h_;
bool first_round = true;
while (!(p == t_ && first_round == false)) { //DeMorganed: p != t_ || first_round
new_elems[new_elem_i] = elems_[p];
new_elem_i++;
p++;
if (p == capacity_)
p = 0;
first_round = false;
}
delete[] elems_;
elems_ = new_elems;
capacity_ = new_capacity;
h_ = 0;
t_ = new_elem_i;
}This linked list implementation allows for O(1) insertion to the front and back.
#pragma once
#include <iostream>
template <typename T>
class Node {
public:
Node(T value);
Node(T value, Node* next);
T value;
Node* next;
};
template <typename T>
class LinkedList {
public:
Node<T>* head = nullptr;
Node<T>* tail = nullptr;
LinkedList();
/** insert at front of list */
void insert(T value);
void insert_back(T value);
void insert_at(size_t index, T value);
Node<T>* operator[](size_t index);
size_t size() const;
template <typename U>
friend std::ostream& operator<<(std::ostream& os, const LinkedList<U>& ll);
};template <typename T>
Node<T>::Node(T value, Node<T>* next) {
this->value = value;
this->next = next;
}
template <typename T>
Node<T>::Node(T value) {
this->value = value;
this->next = nullptr;
}
template <typename T>
LinkedList<T>::LinkedList() {}
template <typename T>
void LinkedList<T>::insert(T value) {
Node<T>* new_head = new Node(value);
new_head->next = head;
head = new_head;
if (tail == nullptr)
tail = new_head;
}
template <typename T>
void LinkedList<T>::insert_back(T value) {
Node<T>* new_tail = new Node(value);
tail->next = new_tail; //old tail now points to new tail
tail = new_tail;
}
template <typename T>
void LinkedList<T>::insert_at(size_t index, T value) {
Node<T>* n = new Node(value);
Node<T>* before = head; //node right before where the new node will be
for (int i = 0; i < index - 1; i++) {
before = before->next;
}
n->next = before->next;
before->next = n;
}
template <typename T>
Node<T>* LinkedList<T>::operator[](size_t index) {
Node<T>* n = head;
for (int i = 0; i < index; i++) {
n = n->next;
}
return n;
}
template <typename T>
size_t LinkedList<T>::size() const {
Node<T>* n = this->head;
size_t out = 0;
while (n != nullptr) {
out++;
n = n->next;
}
return out;
}
template <typename T>
std::ostream& operator<<(std::ostream& os, const LinkedList<T>& l) {
Node<T>* n = l.head;
while (n != nullptr) {
os << n->value << " ";
n = n->next;
}
return os;
}Binary search tree that supports insert, contains, and remove, each in O(logn) time when the tree is balanced.
#include <iostream>
#include <utility> //std::tuple
#include <optional>
template <typename T>
struct BSTNode {
T value;
BSTNode<T>* left = nullptr;
BSTNode<T>* right = nullptr;
BSTNode(T value): value(value) {}
};
/** templates are helpful so you can make it a BST of any comparable object (int, float, double, Decimal, string) */
template <typename T>
class BST {
public:
BSTNode<T>* root = nullptr;
/** returns if an element was inserted */
bool insert(T el);
bool contains(T el) const;
bool contains_rec(T el) const;
/** @returns if an element was removed */
bool remove(T el);
template <typename U>
friend std::ostream& operator<<(std::ostream& os, const BST<U>& bst);
private:
void print_node(BSTNode<T>* n, int level = 0) const;
bool contains_rec_helper(T el, BSTNode<T>* current_node) const;
/**
* This method is useful for remove
* @returns optional<(parent, child, is_left)>; pointer to parent node, pointer to child node, boolean for if it is the left child (true is left, false is right). if the item is not found, returns None
* */
std::optional<std::tuple<BSTNode<T>*, BSTNode<T>*, bool>> find_node_and_its_parent(T el);
};template <typename T>
bool BST<T>::insert(T el) {
BSTNode<T>* new_node = new BSTNode<T>(el);
if (root == nullptr) {
root = new_node;
return true;
}
BSTNode<T>* parent = root; //parent of the new node
while (true) {
if (el > parent->value) {
if (parent->right == nullptr) {
parent->right = new_node;
return true;
}
parent = parent->right;
} else if (el == parent->value) {
return false;
} else { // el < parent->value
if (parent->left == nullptr) {
parent->left = new_node;
return true;
}
parent = parent->left;
}
}
}
template <typename T>
bool BST<T>::contains(T el) const { //iterative approach
BSTNode<T>* current_node = root;
while (current_node != nullptr) {
if (el == current_node->value)
return true;
else if (el > current_node->value)
current_node = current_node->right;
else
current_node = current_node->left;
}
return false;
}
template <typename T>
bool BST<T>::contains_rec(T el) const { //recursive implementation
return contains_rec_helper(el, root);
}
template <typename T>
bool BST<T>::contains_rec_helper(T el, BSTNode<T>* current_node) const {
if (current_node == nullptr)
return false;
if (current_node->value == el)
return true;
if (el > current_node->value) //it must be on the right if it is there
return contains_rec_helper(el, current_node->right);
else //el < current_node->value
return contains_rec_helper(el, current_node->left);
}
template <typename T>
using ParentChildRel = std::tuple<BSTNode<T>*, BSTNode<T>*, bool>;
template <typename T>
std::optional<ParentChildRel<T>> BST<T>::find_node_and_its_parent(T el) {
BSTNode<T>* parent = nullptr;
BSTNode<T>* child = root;
bool is_left = false;
while (child != nullptr) {
if (el == child->value) {
return std::make_tuple(parent, child, is_left);
} else if (el > child->value) { //go right
parent = child;
child = child->right;
is_left = false;
} else { //el < child->value, go left
parent = child;
child = child->left;
is_left = true;
}
}
return {};
}
template <typename T>
bool BST<T>::remove(T el) {
std::optional<ParentChildRel<T>> res = find_node_and_its_parent(el);
if (!res) //cannot remove it because it is not in the tree
return false;
ParentChildRel<T> unwrapped_res = res.value();
BSTNode<T>* parent = std::get<0>(unwrapped_res); //parent of node to delete
BSTNode<T>* child = std::get<1>(unwrapped_res); //node to delete
bool is_left = std::get<2>(unwrapped_res);
// Case 1: deleting a leaf
if (child->left == nullptr && child->right == nullptr) {
if (parent == nullptr) { //child is root node
// In this case, deleting root node with nothing else in tree (root is leaf)
root = nullptr;
return true;
}
if (is_left) {
parent->left = nullptr;
return true;
} else {
parent->right = nullptr;
return true;
}
}
// Case 2: deleting root (that has children by not being a leaf)
// Since it is the root, we have to update the root pointer (parent is nullptr)
if (parent == nullptr) { //child is root
if (child->right != nullptr) {
BSTNode<T>* replacement = child->right;
BSTNode<T>* parent_of_replacement = child;
while (replacement->left != nullptr) {
parent_of_replacement = replacement;
replacement = replacement->left;
}
BSTNode<T>* old_replacement_right = replacement->right;
replacement->left = child->left;
replacement->right = child->right;
root = replacement;
delete child;
parent_of_replacement->left = old_replacement_right;
return true;
}
// In this case, since child cannot be a leaf, it only has a ->left
BSTNode<T>* replacement = child->left;
BSTNode<T>* parent_of_replacement = child;
while (replacement->right != nullptr) {
parent_of_replacement = replacement;
replacement = replacement->right;
}
BSTNode<T>* old_replacement_left = replacement->left;
replacement->left = child->left;
root = replacement;
// we already know replacement->right is null so it is fine
delete child;
parent_of_replacement->right = old_replacement_left;
return true;
}
// Case 3: deleting a non-root interior node
// Since it is an interior node, it has to have a child
// Case 3a: deleted node has a right child
if (child->right != nullptr) {
BSTNode<T>* replacement = child->right;
BSTNode<T>* parent_of_replacement = child;
while (replacement->left != nullptr) {
parent_of_replacement = replacement;
replacement = replacement->left;
}
BSTNode<T>* old_replacement_right = replacement->right;
replacement->left = child->left;
if (child->right != replacement) {
replacement->right = child->right;
}
if (is_left)
parent->left = replacement;
else
parent->right = replacement;
delete child;
return true;
}
// Case 3b: deleted node only has a left child
// implicitly, we know that child->left != nullptr since it is not a leaf and does not have a right node
BSTNode<T>* replacement = child->left;
BSTNode<T>* parent_of_replacement = child;
while (replacement->right != nullptr) {
parent_of_replacement = replacement;
replacement = replacement->right;
}
BSTNode<T>* old_replacement_left = replacement->left;
if (child->left != replacement) {
replacement->left = child->left;
}
if (is_left)
parent->left = replacement;
else
parent->right = replacement;
parent_of_replacement->right = old_replacement_left;
delete child;
return true;
}
/** this printing method is nice because leave nodes are surrounded like so (3) */
template <typename T>
std::ostream& operator<<(std::ostream& os, const BST<T>& bst) {
bst.print_node(bst.root, 0);
return os;
}
template <typename T>
void BST<T>::print_node(BSTNode<T>* n, int level) const {
if (n == nullptr) return;
int paren_type = level % 4;
char open_paren = '(';
char close_paren = ')';
if (paren_type == 1) {
open_paren = '<';
close_paren = '>';
} else if (paren_type == 2) {
open_paren = '{';
close_paren = '}';
} else if (paren_type == 3) {
open_paren = '[';
close_paren = ']';
}
if (n->left != nullptr) {
std::cout << open_paren;
print_node(n->left, level + 1);
std::cout << close_paren << " ← ";
}
std::cout << n->value;
if (n->right != nullptr) {
std::cout << " → " << open_paren;
print_node(n->right, level + 1);
std::cout << close_paren;
}
}