Data Structures
- Doubly-linked list
- Queue (something British)
- Stack (something having to do with pancakes)
- Binary heap
- Hash table
- Graph (todo: implement traversal and weighted edges)
- Binary search tree1 (todo: implement self-balancing)
Algorithms
Disclaimer
Don't trust anything I say.
1My feelings toward BSTs:
Doubly-linked list
This repo implements the doubly-linked list data structure. A doubly-linked list is a sequence of nodes, wherein each node links to the previous and following node. This implementation includes the following methods:
insert(val)
inserts val at the beginning of the listappend(val)
appends val to the end of the listshift()
removes and returns the first value in the listpop()
removes and returns the last value in the listremove(val)
removes the first instance of val in the listsize()
returns the number of the nodes in the list (i.e., the list's length)contains(val)
returns True if the list contains val; otherwise, False
These methods perform accordingly, where n is the number of nodes in a list:
insert | append | shift | pop | remove | size | contains | |
---|---|---|---|---|---|---|---|
Average/Worst | O(1) | O(1) | O(1) | O(1) | O(n) | O(1) | O(n) |
Queue
A queue is a first-in-first-out or FIFO data structure, from which items are removed (dequeued) in the order that they were inserted (enqueued). This repo's implementation includes the following methods:
enqueue(val)
appends val to the end of the queuedequeue()
removes and returns the first value in the queuepeek()
returns the first value in the queue without removing it from the queuesize()
returns the number of items in the queue
enqueue | dequeue | peek | size | |
---|---|---|---|---|
Average/Worst | O(1) | O(1) | O(1) | O(1) |
Stack
A stack is a last-in-first-out or LIFO data structure, from which the most recently inserted items are removed first. This repo's implementation includes the following methods:
push(val)
adds val to the top of the stackpop()
removes and returns the topmost (i.e., most recently added) value in the stackpeek()
returns the first value in the stack without removing it from the stacksize()
returns the number of items in the stack
push | pop | peek | size | |
---|---|---|---|---|
Average/Worst | O(1) | O(1) | O(1) | O(1) |
Binary heap
Binary heap description coming soon!
This repo's implementation of a binary heap includes the following methods:
push(val)
adds val to the heap, filling in the heap at the lowest level from left to rightpop()
removes and returns the topmost value in the heappeek()
returns the topmost value in the heap without removing it from the heapsize()
returns the number of items in the heap
push | pop | peek | size | |
---|---|---|---|---|
Average | O(1) | O(log n) | O(1) | O(1) |
Worst | O(log n) | O(log n) | O(1) | O(1) |
Hash table
Hash table description coming soon!
This repo's implementation of a hash table includes the following methods:
set(key, val)
stores the key-val pair in the hash table (assuming that key is not already in the table)get(key)
retrieves the value stored at key
set | get | |
---|---|---|
Average | O(1) | O(1) |
Worst | O(n) | O(n) |
Graph
Graph description coming soon!
This repo uses adjacency lists to represent undirected graphs. The following methods are available in this implementation:
add_node(val)
adds the node val to the graphadd_edge(val1, val2)
adds an edge to the graph connecting nodes val1 and val2 (adding both nodes if need be)del_node(val)
deletes the node val from the graph, along with all of its edgesdel_edge(val1, val2)
deletes the edge connecting nodes val1 and val2 in the graphhas_node(val)
returns True if the node val is in the graph; otherwise, Falseneighbors(val)
returns a list of the nodes with edges connecting to the node valis_adjacent(val1, val2)
returns True if there is an edge connecting nodes val1 and val2; otherwise, False
These methods perform accordingly, where n is the number of nodes in a graph and e is the number of edges:
add_node | add_edge | del_node | del_edge | has_node | neighbors | is_adjacent |
---|---|---|---|---|---|---|
O(1) | O(1) | O(e) | O(n) | O(n) | O(1) | O(n) |
Binary search tree
Once again:
Available methods:
insert(val)
inserts val into the treecontains(val)
returns True if the tree contains val; otherwise, Falsedelete(val)
deletes val from the treesize()
returns the number of nodes in the treedepth()
returns the depth of the treeget_balance()
returns an integer i indicating the tree's balance- if i == 0, the tree is perfectly balanced
- if i < 0, the tree is left-heavy
- if i > 0, the tree is right-heavy
in_order()
traverses and returns the tree's nodes in orderpre_order()
traverses and returns the tree's nodes, yielding parents firstpost_order()
traverses and returns the tree's nodes, yielding children firstbreadth_first()
traverses and returns the tree's nodes in level order
insert | contains | delete | |
---|---|---|---|
Average | O(log n) | O(log n) | O(log n) |
Worst | O(n) | O(n) | O(n) |
Insertion sort
Insertion sort constructs a sorted list one item at a time by iterating up the provided list and accruing a sorted sub-list behind it. At each position in the list, it compares the current value with the previous value. If the current value is greater, the algorithm moves on to the next iteration. Otherwise, it moves the current value to its correct position in the sub-list and shifts all rightward values in the sub-list up by one position.
The best case for insertion sort is an input that is already sorted. With a sorted list, the algorithm merely iterates across the list without shifting any items, thus taking O(n) time, where n is the number of items in the list.
The worst case is an input sorted in descending order. This forces the algorithm to shift the current value to the front of the sorted sub-list at each iteration, taking O(n2) time.
The average case also takes O(n2) time, rendering it impractical for larger lists.
Best | Average | Worst |
---|---|---|
O(n) | O(n2) | O(n2) |
Merge sort
Merge sort is a divide-and-conquer sorting algorithm. The implementation in this repo takes a top-down approach that recursively divides the input list into halves, sorts each halve, then merges the sorted halves back together.
Merge sort always takes O(n log n) time, since the order of the input does not impact how often the algorithm invokes itself. See here for a more in-depth analysis of merge sort's performance.
Best | Average | Worst |
---|---|---|
O(n log n) | O(n log n) | O(n log n) |
Quicksort
Quicksort is another divide-and-conquer sorting algorithm. It picks a value, called a pivot, then partitions the input list into two sub-lists: a list containing values less than (or equal) to the pivot and a list containing values greater than the pivot. It then recursively sorts each sub-list accordingly. This repo implements both the Lomuto and Hoare partitioning schemes.
The best case for quicksort is a balanced input, where each partitioning results in two approximately equally-sized sub-lists. With such an input, quicksort takes O(n log n) time.
Likewise, the average case for quicksort also takes O(n log n) time.
The worst case for quicksort is when the partitioning results in an empty sub-list and a sub-list of size n - 1, taking O(n2) time to sort. This can happen if the pivot is equal to the lowest or greatest value in the input or, in the Lomuto scheme, when all of the items are equal in value.
Best | Average | Worst |
---|---|---|
O(n log n) | O(n log n) | O(n2) |
Radix sort
Coming (not so) soon! Until then, check out this brief YouTube video on radix sort.