Advanced Data Structures
Advanced Data Structures
A. Time Complexity
Measures how the running time changes with the size of the input (nn).
Expressed using Big O notation:
o O(1)O(1): Constant time → Accessing an array element
o O(logn)O(\log n): Logarithmic → Binary search
o O(n)O(n): Linear → Traversing a list
o O(nlogn)O(n \log n): Log-linear → Merge sort
o O(n2)O(n^2): Quadratic → Bubble sort
Goal: Minimize time complexity for faster execution.
B. Space Complexity
1. Asymptotic Notations
Asymptotic notations describe the growth rate of an algorithm’s running time (or space) as the
input size nn becomes large.
They ignore machine-dependent constants and focus on order of magnitude.
Main Types
Definition: Maximum time an algorithm will take for any input of size nn.
Why important: Guarantees performance in the most difficult situation.
Example:
o Linear search: Worst case → element not present → O(n)O(n).
o Quick sort: Worst case (unbalanced partitions) → O(n2)O(n^2).
Definition: Expected running time over all possible inputs of size nn, assuming a
probability distribution of inputs.
Why important: Gives realistic expectation in practical use.
Example:
o Linear search: Average case → element is equally likely to be anywhere →
O(n)O(n), but roughly n/2n/2 comparisons.
Definition: Minimum time the algorithm will take for any input of size nn.
Example:
o Linear search best case → element at first position → O(1)O(1).
1. Arrays
Definition: A collection of elements stored in contiguous memory locations.
Types:
o Static array → fixed size
o Dynamic array (like vector in C++, ArrayList in Java) → resizable
#include <stdio.h>
int main() {
int arr[6] = {10, 20, 30, 40, 50};
int n = 5;
// Traversal
printf("Array elements: ");
for(int i = 0; i < n; i++) {
printf("%d ", arr[i]); // O(1) access
}
printf("\n");
return 0;
}
2. Linked Lists
Definition: A collection of nodes where each node contains data and a pointer (or
reference) to the next node (and possibly previous node in a doubly linked list).
Types:
o Singly linked list → one pointer (next)
o Doubly linked list → two pointers (next & prev)
o Circular linked list → last node points back to first
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
int main() {
// Create nodes
struct Node* head = malloc(sizeof(struct Node));
struct Node* second = malloc(sizeof(struct Node));
struct Node* third = malloc(sizeof(struct Node));
// Assign data
head->data = 10; head->next = second;
second->data = 20; second->next = third;
third->data = 30; third->next = NULL;
// Traversal O(n)
printf("Linked List elements: ");
struct Node* temp = head;
while (temp != NULL) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
return 0;
}
3. Key Differences
Feature Array Linked List
Memory allocation Contiguous Non-contiguous
Access by index O(1)O(1) O(n)O(n)
Insertion/deletion at
O(n)O(n) O(1)O(1)
front
Insertion/deletion at O(1)O(1) with tail pointer, else
O(1)O(1) amortized
end O(n)O(n)
O(n)O(n) or O(logn)O(\log n) if
Search O(n)O(n)
sorted
Space usage Only data Data + pointer(s) overhead
Cache performance Good (contiguous) Poor (pointer chasing)
1. Stack: Definition
A stack is a linear data structure that follows LIFO (Last In, First Out) principle.
Basic operations:
2. Implementation of Stack
A. Array-based Implementation (C)
#include <stdio.h>
#define MAX 5
int stack[MAX];
int top = -1;
void push(int x) {
if (top == MAX - 1) {
printf("Stack Overflow\n");
return;
}
stack[++top] = x;
}
int pop() {
if (top == -1) {
printf("Stack Underflow\n");
return -1;
}
return stack[top--];
}
int peek() {
if (top == -1) {
printf("Stack Empty\n");
return -1;
}
return stack[top];
}
int main() {
push(10);
push(20);
push(30);
printf("Top element: %d\n", peek());
printf("Popped: %d\n", pop());
printf("Popped: %d\n", pop());
return 0;
}
3. Applications of Stacks
(i) Quick Sort
#include <stdio.h>
void swap(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}
stack[++top] = l;
stack[++top] = h;
int main() {
int arr[] = {4, 2, 6, 9, 3};
int n = sizeof(arr) / sizeof(arr[0]);
quickSortIterative(arr, 0, n - 1);
printf("Sorted array: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
return 0;
}
Example (C):
#include <stdio.h>
#include <string.h>
char stack[100];
int top = -1;
int main() {
char expr[] = "{[()]}";
for (int i = 0; i < strlen(expr); i++) {
if (expr[i] == '(' || expr[i] == '[' || expr[i] == '{')
push(expr[i]);
else {
if (top == -1 || !isMatchingPair(pop(), expr[i])) {
printf("Not Balanced\n");
return 0;
}
}
}
printf(top == -1 ? "Balanced\n" : "Not Balanced\n");
return 0;
}
Infix → Postfix: Stack used to store operators; pop based on precedence rules.
Postfix Evaluation: Stack used to store operands; pop for operation when operator is
found.
Example:
Infix: A + B * C
Postfix: A B C * +
#include <stdio.h>
#include <ctype.h>
char stack[100];
int top = -1;
int main() {
char exp[] = "A+B*C";
char output[100];
int k = 0;
for (int i = 0; exp[i]; i++) {
if (isalnum(exp[i])) output[k++] = exp[i];
else {
while (top != -1 && prec(stack[top]) >= prec(exp[i]))
output[k++] = pop();
push(exp[i]);
}
}
while (top != -1) output[k++] = pop();
output[k] = '\0';
printf("Postfix: %s\n", output);
return 0;
}
#include <stdio.h>
int main() {
int n = 3;
tower(n, 'A', 'C', 'B');
return 0;
}
Each function call is pushed onto the call stack (activation record with parameters, local
variables, return address).
When a function returns, its record is popped from the stack.
#include <stdio.h>
int fact(int n) {
if (n == 0) return 1;
return n * fact(n - 1);
}
int main() {
printf("Factorial of 5: %d\n", fact(5));
return 0;
}
Alright — let’s go step-by-step so you have complete notes on Queues with definitions,
implementations (in C), and examples of applications including priority queues.
1. Queue: Definition
A queue is a linear data structure that follows the FIFO (First In, First Out) principle.
Basic Operations:
int queue[MAX];
int front = -1, rear = -1;
void enqueue(int x) {
if (rear == MAX - 1) {
printf("Queue Overflow\n");
return;
}
if (front == -1) front = 0; // first element
queue[++rear] = x;
}
int dequeue() {
if (front == -1 || front > rear) {
printf("Queue Underflow\n");
return -1;
}
return queue[front++];
}
int peek() {
if (front == -1 || front > rear) {
printf("Queue is empty\n");
return -1;
}
return queue[front];
}
int main() {
enqueue(10);
enqueue(20);
enqueue(30);
printf("Front element: %d\n", peek());
printf("Dequeued: %d\n", dequeue());
printf("Dequeued: %d\n", dequeue());
return 0;
}
3. Types of Queues
1. Simple Queue – Basic FIFO.
2. Circular Queue – Connects rear to front to avoid unused space.
3. Priority Queue – Elements are served based on priority, not arrival time.
4. Deque (Double Ended Queue) – Insertion & deletion at both ends.
4. Priority Queue
In a priority queue:
#include <stdio.h>
#define MAX 5
struct {
int data;
int priority;
} pq[MAX];
int size = 0;
int dequeue() {
if (size == 0) {
printf("Priority Queue Empty\n");
return -1;
}
int highest = 0;
for (int i = 1; i < size; i++) {
if (pq[i].priority > pq[highest].priority)
highest = i;
}
int val = pq[highest].data;
for (int i = highest; i < size - 1; i++) {
pq[i] = pq[i + 1];
}
size--;
return val;
}
int main() {
enqueue(10, 2);
enqueue(20, 1);
enqueue(30, 3);
printf("Dequeued: %d\n", dequeue()); // 30
printf("Dequeued: %d\n", dequeue()); // 10
return 0;
}
5. Applications of Queues
Application Explanation
Job Scheduling OS uses queues to schedule processes for execution.
Printer Queue Print jobs are queued; first job sent is printed first.
Customer Service Call centers manage customers in queue order.
Breadth First Search (BFS) BFS uses queues to store vertices to visit next.
Traffic Management Vehicles pass signals in arrival order.
CPU Task Scheduling (Priority Queue) High-priority tasks processed before others.
#define MAX 5
int queue[MAX], front = -1, rear = -1;
int graph[5][5] = {
{0,1,1,0,0},
{1,0,0,1,0},
{1,0,0,1,1},
{0,1,1,0,1},
{0,0,1,1,0}
};
int visited[5] = {0};
void enqueue(int v) {
if (rear == MAX - 1) return;
if (front == -1) front = 0;
queue[++rear] = v;
}
int dequeue() {
if (front == -1 || front > rear) return -1;
return queue[front++];
}
int main() {
printf("BFS Traversal: ");
BFS(0);
return 0;
}
1. Search Trees
A Search Tree is a tree data structure used for fast searching, insertion, and deletion of data.
2. BST Properties
Searching, Insertion, Deletion have O(h) time complexity,
where h = height of the tree.
Best case (balanced tree) → O(log n)
Worst case (skewed tree) → O(n)
3. Searching in BST
Algorithm:
Example (C):
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* left;
struct Node* right;
};
4. Insertion in BST
Algorithm:
Example (C):
5. Deletion in BST
Three cases when deleting a node:
Example (C):
// Insertion
root = insert(root, 50);
root = insert(root, 30);
root = insert(root, 70);
root = insert(root, 20);
root = insert(root, 40);
root = insert(root, 60);
root = insert(root, 80);
// Searching
struct Node* found = search(root, 40);
if (found) printf("Found: %d\n", found->data);
else printf("Not Found\n");
// Deletion
root = deleteNode(root, 20); // leaf node
root = deleteNode(root, 30); // one child
root = deleteNode(root, 50); // two children
return 0;
}
1. AVL Trees
Definition:
An AVL Tree is a self-balancing Binary Search Tree.
Occurs when:
o Insertion in left subtree of left child.
Fix:
o Right Rotation.
y x
/ / \
x → z y
/
z
Occurs when:
o Insertion in right subtree of right child.
Fix:
o Left Rotation.
x y
\ / \
y → x z
\
z
Occurs when:
o Insertion in right subtree of left child.
Fix:
o Left Rotation on left child + Right Rotation.
Occurs when:
o Insertion in left subtree of right child.
Fix:
o Right Rotation on right child + Left Rotation.
2. B-Trees
Definition:
A B-Tree is a self-balancing multi-way search tree (not binary), used in databases and file
systems to handle large blocks of data efficiently.
2.1 Properties of B-Tree (Order m)
Searching
Similar to binary search but done in each node with multiple keys.
Time complexity: O(log n).
Insertion
Deletion
1. AVL Trees
Definition:
An AVL Tree is a self-balancing Binary Search Tree.
Occurs when:
o Insertion in left subtree of left child.
Fix:
o Right Rotation.
y x
/ / \
x → z y
/
z
Case 2: Right-Right (RR)
Occurs when:
o Insertion in right subtree of right child.
Fix:
o Left Rotation.
x y
\ / \
y → x z
\
z
Occurs when:
o Insertion in right subtree of left child.
Fix:
o Left Rotation on left child + Right Rotation.
Occurs when:
o Insertion in left subtree of right child.
Fix:
o Right Rotation on right child + Left Rotation.
Searching
Similar to binary search but done in each node with multiple keys.
Time complexity: O(log n).
Insertion
Deletion
Step-by-Step Insertion
Insert 10
10
Insert 20
10
\
20
Insert 30
10
\
20
\
30
BF(10) = -2 → imbalance (Right-Right case)
Fix: Left Rotation at 10
After rotation:
20
/ \
10 30
Insert 40
20
/ \
10 30
\
40
Still balanced.
Insert 50
20
/ \
10 30
\
40
\
50
BF(30) = -2 → RR Case
Fix: Left Rotation at 30
After:
20
/ \
10 40
/ \
30 50
Insert 25
20
/ \
10 40
/ \
30 50
/
25
BF(40) = +2 → Left-Right Case
Fix: Left Rotation on 30, then Right Rotation on 40.
After:
20
/ \
10 30
/ \
25 40
\
50
Step-by-Step
Insert 10
[10]
Insert 20
[10 20]
Insert 5
[5 10 20] → Overflow (3 keys)
Split into [5] and [20] with 10 as middle key pushed up.
[10]
/ \
[5] [20]
Insert 6
[10]
/ \
[5 6] [20]
No split needed.
Insert 12
[10]
/ \
[5 6] [12 20]
Insert 30
[10]
/ \
[5 6] [12 20 30] → Overflow
[10 20]
/ | \
[5 6] [12] [30]
Insert 7
[10 20]
/ | \
[5 6 7] [12] [30]
Insert 17
[10 20]
/ | \
[5 6 7] [12 17] [30]
✅ Balanced, no split.
When to Use
AVL Tree → In-memory applications needing fast lookups (e.g., symbol tables in
compilers).
B-Tree → Disk-based storage like databases, file systems (minimizes disk reads).
1. Red-Black Trees
Properties
Operations
Insertion
1. Insert as in a normal BST, color new node red.
2. Fix violations by rotations & recoloring.
Deletion
1. Delete as in BST.
2. Fix double-black violations.
20B
/ \
10R 30R
(Satisfies properties.)
When to use: Red-Black Trees are used in Linux kernel and C++ STL map/set because
insert/delete performance is stable (O(log n)).
2. Splay Trees
A self-adjusting binary search tree where recently accessed elements are moved to the root by
splaying.
Properties
Operations
Example
Insert 10 → root
10
Insert 20 → splay 20
20
/
10
Insert 30 → splay 30
30
/
20
/
10
When to use: Good for caching and situations where recent accesses are likely to repeat.
3. 2–3 Trees
A balanced search tree where each internal node can have:
Properties
Operations
Insertion
1. Insert into a leaf.
2. If overflow (3 keys), split and promote middle key.
Deletion
1. Remove from leaf.
2. Merge or borrow from siblings if underflow.
1. Insert 10 → [10]
2. Insert 20 → [10 20]
3. Insert 30 → Overflow [10 20 30] → split into [10] and [30] with root [20]
[20]
/ \
[10] [30]
When to use: Database indexes, file systems (similar to B-trees, but simpler).
1. What is a Heap?
A Heap is a special complete binary tree where:
50
/ \
30 40
/ \ / \
10 20 35 25
10
/ \
20 30
/ \ / \
40 50 60 70
50
/ \
30 40
/ \ / \
10 20 35 25
Insert 55:
1. Add at the end → [50, 30, 40, 10, 20, 35, 25, 55]
50
/ \
30 40
/ \ / \
10 20 35 25
/
55
2. Heapify Up:
o Compare with parent (10) → Swap → [50, 30, 40, 55, 20, 35, 25, 10]
o Compare with parent (30) → Swap → [50, 55, 40, 30, 20, 35, 25, 10]
o Compare with parent (50) → Swap → [55, 50, 40, 30, 20, 35, 25, 10]
Final Heap:
55
/ \
50 40
/ \ / \
30 20 35 25
/
10
55
/ \
50 40
/ \ / \
30 20 35 25
/
10
Delete Root:
1. Replace root with last element (10) → [10, 50, 40, 30, 20, 35, 25]
2. Heapify Down:
o Compare with children → swap with 50 → [50, 10, 40, 30, 20, 35, 25]
o Compare 10 with children (30, 20) → swap with 30 → [50, 30, 40, 10, 20,
35, 25]
Final Heap:
50
/ \
30 40
/ \ / \
10 20 35 25
5. Applications
Priority Queue (jobs/tasks handled by priority).
Heap Sort (sorting in O(n log n)).
Graph Algorithms (Dijkstra’s shortest path, Prim’s MST).
Scheduling Systems (processes by urgency).
Median Finding (using two heaps).
6. C Example — Max Heap (Insertion & Deletion)
#include <stdio.h>
void heapifyUp(int i) {
while (i > 0 && heap[(i - 1) / 2] < heap[i]) {
swap(&heap[i], &heap[(i - 1) / 2]);
i = (i - 1) / 2;
}
}
void heapifyDown(int i) {
int largest = i, left = 2 * i + 1, right = 2 * i + 2;
if (left < size && heap[left] > heap[largest]) largest = left;
if (right < size && heap[right] > heap[largest]) largest = right;
if (largest != i) {
swap(&heap[i], &heap[largest]);
heapifyDown(largest);
}
}
int extractMax() {
if (size == 0) return -1;
int max = heap[0];
heap[0] = heap[size - 1];
size--;
heapifyDown(0);
return max;
}
int main() {
insert(50);
insert(30);
insert(40);
insert(10);
insert(20);
insert(35);
insert(25);
insert(55);
1. Heap Sort
Heap Sort is a comparison-based sorting algorithm using a binary heap (usually Max Heap for
ascending sort).
Time Complexity:
2. Binomial Heaps
A Binomial Heap is a collection of Binomial Trees that follow the heap property.
Properties
Each binomial tree B_k has exactly 2^k nodes and height k.
There is at most one binomial tree of each degree.
Trees are linked like a linked list by increasing degree.
Operations
Example:
Merging Binomial Heaps:
3. Fibonacci Heap
A Fibonacci Heap is a collection of trees (like a binomial heap) but with more relaxed structural
constraints, giving better amortized complexity.
Properties
Advantages
Example Usage
In Dijkstra’s Algorithm, Fibonacci Heaps reduce complexity from O(V^2) (array) or O(E log
V) (binary heap) to O(V log V + E).
Comparison Table
Heap Type Find Min Insert Delete Min Merge
Binary Heap O(1) O(log n) O(log n) O(n)
Binomial Heap O(log n) O(log n) O(log n) O(log n)
Fibonacci Heap O(1) O(1) O(log n) O(1)
1. Undirected Graph
2. A -- B
3. | |
4. D -- C
o Edges have no direction.
o Example: Road network between cities.
5. Directed Graph
6. A → B → C
7. ↑ ↓
8. D ← ← ←
o Edges have direction.
o Example: One-way streets.
9. Weighted Graph
10. A --(5)-- B
11. | |
(3) (2)
||
D --(4)-- C
---
| | A | B | C | D |
|-----|---|---|---|---|
| **A** | 0 | 1 | 0 | 1 |
| **B** | 1 | 0 | 1 | 0 |
| **C** | 0 | 1 | 0 | 1 |
| **D** | 1 | 0 | 1 | 0 |
**C Implementation:**
```c
#include <stdio.h>
#define V 4
int main() {
int graph[V][V] = {
{0, 1, 0, 1},
{1, 0, 1, 0},
{0, 1, 0, 1},
{1, 0, 1, 0}
};
printf("Adjacency Matrix:\n");
for(int i=0; i<V; i++) {
for(int j=0; j<V; j++) {
printf("%d ", graph[i][j]);
}
printf("\n");
}
return 0;
}
A → B → D
B → A → C
C → B → D
D → A → C
C Implementation:
#include <stdio.h>
#include <stdlib.h>
struct Node {
int vertex;
struct Node* next;
};
int main() {
int V = 4;
struct Node* adjList[4] = {NULL};
addEdge(adjList, 0, 1);
addEdge(adjList, 0, 3);
addEdge(adjList, 1, 2);
addEdge(adjList, 2, 3);
printGraph(adjList, V);
return 0;
}
Component 1: A-B-C
Component 2: D-E
#define V 5
int graph[V][V] = {
{0,1,1,0,0},
{1,0,1,0,0},
{1,1,0,0,0},
{0,0,0,0,1},
{0,0,0,1,0}
};
int visited[V] = {0};
void DFS(int v) {
visited[v] = 1;
printf("%d ", v);
for(int i=0; i<V; i++) {
if(graph[v][i] && !visited[i])
DFS(i);
}
}
int main() {
int components = 0;
for(int i=0; i<V; i++) {
if(!visited[i]) {
components++;
printf("\nComponent %d: ", components);
DFS(i);
}
}
return 0;
}
A -- B
| |
D -- C
Spanning Tree: Remove one edge (say D-A) → Tree with 3 edges.
Applications:
0 -- 1 -- 2
| |
3 -- 4 -- 5
Order: 0 1 3 2 4 5
C Code:
#include <stdio.h>
#define V 6
int graph[V][V] = {
{0,1,0,1,0,0},
{1,0,1,0,0,0},
{0,1,0,0,0,1},
{1,0,0,0,1,0},
{0,0,0,1,0,1},
{0,0,1,0,1,0}
};
int main() {
BFS(0);
return 0;
}
#include <stdio.h>
#define V 6
int graph[V][V] = {
{0,1,0,1,0,0},
{1,0,1,0,0,0},
{0,1,0,0,0,1},
{1,0,0,0,1,0},
{0,0,0,1,0,1},
{0,0,1,0,1,0}
};
int visited[V] = {0};
void DFS(int v) {
visited[v] = 1;
printf("%d ", v);
for(int i=0; i<V; i++) {
if(graph[v][i] && !visited[i])
DFS(i);
}
}
int main() {
DFS(0);
return 0;
}
Example Graph:
(0)---3-->(1)
| /
8 1
| /
(2)<--
0 3 ∞
∞ 0 1
8 ∞ 0
Steps:
5. Topological Sort
Idea: Ordering of vertices in a Directed Acyclic Graph (DAG) such that for every edge u→v, u
comes before v.
Example Graph:
5 → 2 → 3
↓ ↓
0 1
#include <stdio.h>
#define V 6
int graph[V][V] = {
{0,0,0,0,0,0},
{0,0,0,0,0,0},
{0,0,0,1,1,0},
{0,0,0,0,0,0},
{0,1,0,0,0,0},
{1,0,1,0,0,0}
};
int visited[V] = {0};
int stack[100], top = -1;
void topoDFS(int v) {
visited[v] = 1;
for(int i=0; i<V; i++)
if(graph[v][i] && !visited[i])
topoDFS(i);
stack[++top] = v;
}
int main() {
for(int i=0; i<V; i++)
if(!visited[i])
topoDFS(i);
while(top >= 0)
printf("%d ", stack[top--]);
return 0;
}
1. What is Hashing?
Hashing is a way of mapping data to a fixed-size table (array) using a hash function.
It allows fast insertion, search, and deletion (average O(1) time).
Common use: symbol tables, password storage, indexing in databases.
How it works:
Example:
Key → Value
10 → "Apple"
25 → "Mango"
35 → "Banana"
Drawback:
Slow search for large datasets → O(n).
C Example:
#include <stdio.h>
#include <string.h>
struct Item {
int key;
char value[20];
};
int main() {
struct Item list[3] = {
{10, "Apple"},
{25, "Mango"},
{35, "Banana"}
};
Structure:
Example:
Table size = 10
Hash function: index = key % 10
Be fast to compute.
Distribute keys uniformly across the table.
Minimize collisions.
#include <stdio.h>
#define SIZE 10
int hashTable[SIZE];
void init() {
for(int i=0; i<SIZE; i++)
hashTable[i] = -1; // Empty slot
}
void display() {
for(int i=0; i<SIZE; i++)
printf("%d : %d\n", i, hashTable[i]);
}
int main() {
init();
insert(25);
insert(35);
insert(15);
display();
return 0;
}
✅ Summary Table:
Search
Technique Storage Pros Cons
Time
Linear List
Array/List O(n) Simple Slow for large data
Representation
Hash Table Array + Fast Needs good hash
O(1) avg
Representation Hash search/insertion func.
Hash Functions Formula O(1) Maps key → index Collisions possible
Example:
Hash table size = 10
Hash function: index = key % 10
Key: 25 → 25 % 10 = 5
Key: 35 → 35 % 10 = 5 (Collision with 25)
Each hash table slot points to a linked list of elements that hash to the same index.
On collision, new key is added to the list at that index.
Example:
Advantages:
Simple to implement.
Table size can be smaller than number of elements.
No clustering problem.
Disadvantages:
C Example (Chaining):
#include <stdio.h>
#include <stdlib.h>
#define SIZE 10
struct Node {
int key;
struct Node* next;
};
void display() {
for(int i=0; i<SIZE; i++) {
printf("%d: ", i);
struct Node* temp = hashTable[i];
while(temp) {
printf("%d -> ", temp->key);
temp = temp->next;
}
printf("NULL\n");
}
}
int main() {
insert(25);
insert(35);
insert(15);
display();
return 0;
}
B. Open Addressing
Instead of using linked lists, find another empty slot in the table.
1. Linear Probing
On collision, check the next index sequentially until an empty slot is found.
Formula:
h(k, i) = (h(k) + i) % m
where i = number of attempts.
Example:
Insert 25 → index 5
Insert 35 → index 5 occupied → check index 6 → place 35 there.
Pros:
Easy to implement.
Cache-friendly.
Cons:
C Example:
#include <stdio.h>
#define SIZE 10
int hashTable[SIZE];
void init() {
for(int i=0; i<SIZE; i++)
hashTable[i] = -1;
}
void display() {
for(int i=0; i<SIZE; i++)
printf("%d : %d\n", i, hashTable[i]);
}
int main() {
init();
insert(25);
insert(35);
insert(15);
display();
return 0;
}
2. Quadratic Probing
Example:
Insert 25 → index 5
Insert 35 → index 5 occupied → check 5 + 1² = 6
If 6 is occupied → check 5 + 2² = 9, etc.
Pros:
Cons:
✅ Comparison Table:
Method Extra Memory Performance Avg Clustering Issue
1. Double Hashing
Double hashing is an open addressing technique that uses two different hash functions to
reduce clustering.
How it Works
This means that the gap between probes is determined by h2(k), so we avoid consecutive
clusters.
Example:
Table size m = 10
h1(k) = k % 10
h2(k) = 7 - (k % 7)
Disadvantages:
#include <stdio.h>
#define SIZE 10
int hashTable[SIZE];
void init() {
for (int i = 0; i < SIZE; i++)
hashTable[i] = -1;
}
void display() {
for (int i = 0; i < SIZE; i++)
printf("%d : %d\n", i, hashTable[i]);
}
int main() {
init();
insert(25);
insert(35);
insert(15);
display();
return 0;
}
2. Rehashing
When a hash table becomes too full, performance drops due to more collisions.
Rehashing is the process of:
When to Rehash?
Load factor:
Example:
Advantages:
Disadvantages:
#include <stdio.h>
#include <stdlib.h>
int size = 5;
int *hashTable;
int count = 0;
void init() {
hashTable = malloc(size * sizeof(int));
for (int i = 0; i < size; i++)
hashTable[i] = -1;
}
void rehash() {
int oldSize = size;
size = size * 2 + 1; // new size (simple expansion)
int *oldTable = hashTable;
init();
count = 0;
for (int i = 0; i < oldSize; i++) {
if (oldTable[i] != -1) {
// reinserting old elements
int index = hash(oldTable[i]);
while (hashTable[index] != -1)
index = (index + 1) % size;
hashTable[index] = oldTable[i];
count++;
}
}
free(oldTable);
}
void display() {
for (int i = 0; i < size; i++)
printf("%d : %d\n", i, hashTable[i]);
}
int main() {
init();
insert(12);
insert(44);
insert(13);
insert(88);
insert(23); // triggers rehash
display();
return 0;
}