Algorithms trees algorithms data-structures binary-trees bst binary-search-trees avl-trees b-trees red-black-trees
- Elementary Data Structures
- Stacks and Queues
- [Rooted Trees](#rooted\ trees)
- Binary Trees
- [Full Binary Tree](#full\ binary\ tree)
- [Complete Binary Tree](#complete\ binary\ tree)
- [Perfect Binary Tree](#perfect\ binary\ tree)
- Traversal
- Pre-order
- in-order
- post-order
- level-order (BFS)
- Binary Search Trees
- Practice
- Balanced Binary Search Trees
- AVL Trees
- Rebalancing
- Red-Black Trees
- Recoloring
- Splay Trees
- AVL Trees
- B trees
Data Structures
Stacks
Last In First Out (LIFO)
- PUSH()
- POP()
- TOP()
- using arrays
each operation is
#define STACK_CAPACITY 100
typedef struct Stack {
int stack[STACK_CAPACITY];
int top; // index of top element
} stack;
int isEmpty (stack* s) {
if (s.top == 0)
return true;
else
return false;
}
void push (stack* s, int n) {
s.top++;
s.stack[s.top] = n;
}
void pop (stack* s) {
if (isEmpty(s))
exit 1;
else {
s.top--;
return s.stack[s.top + 1]; // return the popped element
}
}
- using linked lists
each operation is
#include<stdlib.h>
#include<stdio.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* createNode(int data) {
Node* new_node = (Node*) malloc(sizeof(Node));
new_node->data = data;
new_node->next = NULL;
return new_node;
}
typedef struct Stack {
Node* head;
} Stack;
void initStack(Stack* stack) {
stack->head = NULL;
}
int isEmpty(Stack* stack) {
return stack->head == NULL;
}
void push(Stack* stack, int data) {
Node* new_node = createNode(data);
if (!new_node) {
exit 1; // stack overflow
}
new_node->next = stack->head;
stack->head = new_node;
}
void pop(Stack* stack) {
if (isEmpty(stack)) {
exit 2; // stack underflow
}
else {
Node* temp = stack->head;
stack->head = stack->head->next;
free(temp);
}
}
Queues
First In First Out (FIFO)
- ENQUEUE()
- DEQUEUE()
- HEAD()
- TAIL()
- array implementation
#define QUEUE_CAPACITY 100
typedef struct Queue {
int head; // init head = 0
int tail; // init tail = 0
int queue[QUEUE_CAPACTIY];
} Queue;
void enqueue(Queue* q, int data) {
q.queue[q.tail] = data;
q.tail = (q.tail + 1) % QUEUE_CAPACITY;
}
int dequeue(Queue* q) {
int x = q.queue[q.head];
q.head = (q.head + 1) % QUEUE_CAPACITY;
return x;
}
- linked list
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* createNode (int data) {
Node* new_node = (Node*) malloc(sizeof(Node));
new_node->data = data;
new_node->next = NULL;
return new_node;
}
typedef struct Queue {
Node* front;
Node* rear;
} Queue;
Queue* initQueue() {
Queue* q = (Queue*) malloc(sizeof(Queue));
q->front = q->rear = NULL;
return q;
}
int isEmpty(Queue* q) {
return q->rear == q->front && q->front == NULL;
}
void enqueue(Queue* q, int data) {
Node* new_node = createNode(data);
if (isEmpty(q)) {
q->front = q->rear = new_node;
return;
}
q->rear->next = new_node;
q->rear = new_node;
}
void deqeue(Queue* q) {
if (isEmpty(q)){
exit 1; // Queue underflow
}
Node* temp = q->front;
q->front = q->front->next;
free(temp);
}
Rooted Trees
Unbounded Branching Implementation
- each node is a linked list with data and two pointers
- first pointer links to the first child, and the second pointer to the next sibling
typedef struct Node {
int data;
struct Node* left_child;
struct Node* right_sibling;
} Node;
Binary Trees
each node has at most two children → left_child → right_child
Full Binary Tree
every node has either 0 or two children
The number of leaves in a non-empty full binary tree is one more than the number of internal nodes. You can notice this in the image.
Complete Binary Tree
Every level except possible the last one is completely filled, and all nodes in the last level are as far left as possible.
Memory Aid “Complete” is a wider word than “Full” and you can remember that Complete Trees are typically wider than Full Trees
Perfect Binary Tree
All internal nodes have two children and all leaf nodes have the same depth
Implementation:
typedef struct node {
int data;
struct node* left;
struct node* right;
} node;
node* newNode (int data) {
node* node = (node*) malloc(sizeof((node));
node->data = data;
node->left = NULL;
node->right = NULL;
return node;
}
Binary Tree Traversal
From a node N:
- (N): process N itself
- (L): recurse on N’s left subtree
- (R): recurse on N’s right subtree
Pre-Order Traversal (NLR)
void printPreorder (node* node) {
if (node == NULL)
return;
// print (N)
printf("%d ", node->data);
// recurse on (L)
printPreorder(node->left);
//recurse on (R)
printPreorder(node->right);
}
In-Order Traversal (LNR)
void printInorder (node* node) {
if (node == NULL)
return ;
printInorder(node->left);
printf("%d ", node->data);
printInorder(node->right);
}
Post-Order Traversal (LRN)
void printPostorder (node* node) {
if (node == NULL)
return;
printPostorder(node->left);
printPostorder(node->right);
printf("%d ", node->data);
}
Level Order Traversal (BFS)
>> 1
>> 2 3
>> 4 5 0 0
void BFS(Node* root) {
if (root == NULL)
return;
enqueue(q, root);
while(q.front != NULL) {
Node* curr = dequeue(q);
printf("%d ", curr->data);
if (root->left != NULL)
enqueue(q, root->left);
if (root->right != NULL)
enqueue(q, root->right);
}
}
Binary Search Trees
binary tree in which every node contains a value greater than left_child and lesser than right_child
Search()
Node* search(Node* root, int query) {
if (root == NULL || root->data == query)
return root;
if (query < root->data)
return search(root->left, query);
else
return search(root->right, query);
return root;
}
Insert()
Node* insert(Node* root, int data) {
if (root == NULL)
return createNode(data);
if (key < root->data)
root->left = insert(root->left, data);
else if (data > root->data)
root->right = insert(root->right, data);
else
printf("%d already present\n", data);
return root;
}
- running time h = height
- worst case with n different nodes =
Max()/Min()
go as far left or right as possible
int getMax(Node* root) {
while (node->right != NULL) {
root = root->right;
}
return root->data;
}
int getMin(Node* root) {
while (root->left != NULL)
root = root->left;
return root->data;
}
Delete()
Lazy Node Deletion
Don’t actually delete the node. Keep the data and mark the node as ‘disabled’. Upon a later insertion of the same data, the node can be easily re-enabled.
Actual Node Deletion
3 cases
- leaf node (no child)
- internal node (one child)
- internal node (two children)
Case 1: just remove it
Case 2: replace it with it’s child
Case 3: let be the node to be deleted
- find min() in right subtree and copy its value to
- recursively delete that min() value
Node* delete(Node* root, int data) {
// key not found
if (root == NULL)
return NULL;
//recur down the tree
if (data < root->data)
root->left = delete(root->left, data);
else if (data > root->data)
root->right = delete(root->right, data);
// if data == root->key then root is to be deleted
else {
// if leaf node
if (root->left == NULL && root->right == NULL){
free(root);
return NULL;
}
// if node with left child only
else if (root->right == NULL) {
Node* temp = root->left;
free(root);
return temp;
}
// if node with right child only
else if (root->left == NULL) {
Node* temp = root->right;
free(root);
return temp;
}
// if both children with min node in the right subtree
else {
Node* temp = getMin(root->right);
root->data = temp->data;
root->right = delete(root->right, temp->data);
}
}
return root;
}
Complexity Analysis
running time of all operations is (d = depth), apart from traversal which is necessarily (n = nodes)
HOWEVER: tree shape depends on the order of insertion, at best if the tree is complete its height is and randomly also it’s around that, but it’s very easy for the trees to become unbalanced to one side
Worst Case running time is in a completely unbalanced tree
Practice
Practice from here
Balanced Trees
Trees
- 1962: first self balancing BST
For each node, the heights of left and right subtrees can differ by at most 1
- This difference is called the node’s balance factor