Data Structures in C
Building data structures in C teaches you exactly how computers work with data. No magic, no hidden allocation—just pure memory manipulation.Dynamic Array (Vector)
Copy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int *data;
size_t size;
size_t capacity;
} Vector;
Vector *vector_create(size_t initial_capacity) {
Vector *v = malloc(sizeof(Vector));
if (!v) return NULL;
v->data = malloc(initial_capacity * sizeof(int));
if (!v->data) {
free(v);
return NULL;
}
v->size = 0;
v->capacity = initial_capacity;
return v;
}
void vector_destroy(Vector *v) {
if (v) {
free(v->data);
free(v);
}
}
static int vector_grow(Vector *v) {
size_t new_capacity = v->capacity * 2;
int *new_data = realloc(v->data, new_capacity * sizeof(int));
if (!new_data) return -1;
v->data = new_data;
v->capacity = new_capacity;
return 0;
}
int vector_push(Vector *v, int value) {
if (v->size == v->capacity) {
if (vector_grow(v) != 0) return -1;
}
v->data[v->size++] = value;
return 0;
}
int vector_pop(Vector *v) {
if (v->size == 0) {
return 0; // Or handle error
}
return v->data[--v->size];
}
int vector_get(Vector *v, size_t index) {
if (index >= v->size) {
return 0; // Or handle error
}
return v->data[index];
}
void vector_set(Vector *v, size_t index, int value) {
if (index < v->size) {
v->data[index] = value;
}
}
// Usage
int main(void) {
Vector *v = vector_create(4);
for (int i = 0; i < 20; i++) {
vector_push(v, i * 10);
}
for (size_t i = 0; i < v->size; i++) {
printf("%d ", vector_get(v, i));
}
printf("\n");
vector_destroy(v);
return 0;
}
Linked List
Singly Linked List
Copy
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
typedef struct {
Node *head;
Node *tail;
size_t size;
} LinkedList;
LinkedList *list_create(void) {
LinkedList *list = calloc(1, sizeof(LinkedList));
return list;
}
void list_destroy(LinkedList *list) {
Node *current = list->head;
while (current) {
Node *next = current->next;
free(current);
current = next;
}
free(list);
}
int list_push_front(LinkedList *list, int value) {
Node *node = malloc(sizeof(Node));
if (!node) return -1;
node->data = value;
node->next = list->head;
list->head = node;
if (!list->tail) {
list->tail = node;
}
list->size++;
return 0;
}
int list_push_back(LinkedList *list, int value) {
Node *node = malloc(sizeof(Node));
if (!node) return -1;
node->data = value;
node->next = NULL;
if (list->tail) {
list->tail->next = node;
} else {
list->head = node;
}
list->tail = node;
list->size++;
return 0;
}
int list_pop_front(LinkedList *list) {
if (!list->head) return 0;
Node *old_head = list->head;
int value = old_head->data;
list->head = old_head->next;
if (!list->head) {
list->tail = NULL;
}
free(old_head);
list->size--;
return value;
}
void list_print(LinkedList *list) {
for (Node *n = list->head; n; n = n->next) {
printf("%d -> ", n->data);
}
printf("NULL\n");
}
Intrusive Linked List (Linux Kernel Style)
Copy
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
// The list node is embedded in your structure
typedef struct list_head {
struct list_head *next;
struct list_head *prev;
} list_head;
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD(list_head *list) {
list->next = list;
list->prev = list;
}
static inline void list_add(list_head *new, list_head *head) {
head->next->prev = new;
new->next = head->next;
new->prev = head;
head->next = new;
}
static inline void list_del(list_head *entry) {
entry->prev->next = entry->next;
entry->next->prev = entry->prev;
}
static inline int list_empty(const list_head *head) {
return head->next == head;
}
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
// Usage
typedef struct {
int id;
char name[32];
list_head list; // Embed the list node
} Task;
int main(void) {
LIST_HEAD(task_list);
// Add tasks
for (int i = 0; i < 5; i++) {
Task *t = malloc(sizeof(Task));
t->id = i;
snprintf(t->name, sizeof(t->name), "Task %d", i);
list_add(&t->list, &task_list);
}
// Iterate
Task *task;
list_for_each_entry(task, &task_list, list) {
printf("Task: %d - %s\n", task->id, task->name);
}
// Cleanup (in real code, be more careful)
list_head *pos, *tmp;
for (pos = task_list.next; pos != &task_list; ) {
tmp = pos->next;
Task *t = list_entry(pos, Task, list);
list_del(pos);
free(t);
pos = tmp;
}
return 0;
}
Hash Table
Copy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define INITIAL_CAPACITY 16
#define LOAD_FACTOR 0.75
typedef struct Entry {
char *key;
int value;
struct Entry *next;
} Entry;
typedef struct {
Entry **buckets;
size_t capacity;
size_t size;
} HashMap;
// FNV-1a hash
static uint64_t hash_string(const char *str) {
uint64_t hash = 0xcbf29ce484222325ULL;
while (*str) {
hash ^= (uint8_t)*str++;
hash *= 0x100000001b3ULL;
}
return hash;
}
HashMap *hashmap_create(void) {
HashMap *map = malloc(sizeof(HashMap));
if (!map) return NULL;
map->buckets = calloc(INITIAL_CAPACITY, sizeof(Entry*));
if (!map->buckets) {
free(map);
return NULL;
}
map->capacity = INITIAL_CAPACITY;
map->size = 0;
return map;
}
void hashmap_destroy(HashMap *map) {
for (size_t i = 0; i < map->capacity; i++) {
Entry *e = map->buckets[i];
while (e) {
Entry *next = e->next;
free(e->key);
free(e);
e = next;
}
}
free(map->buckets);
free(map);
}
static int hashmap_resize(HashMap *map) {
size_t new_capacity = map->capacity * 2;
Entry **new_buckets = calloc(new_capacity, sizeof(Entry*));
if (!new_buckets) return -1;
// Rehash all entries
for (size_t i = 0; i < map->capacity; i++) {
Entry *e = map->buckets[i];
while (e) {
Entry *next = e->next;
size_t new_index = hash_string(e->key) % new_capacity;
e->next = new_buckets[new_index];
new_buckets[new_index] = e;
e = next;
}
}
free(map->buckets);
map->buckets = new_buckets;
map->capacity = new_capacity;
return 0;
}
int hashmap_put(HashMap *map, const char *key, int value) {
if ((double)map->size / map->capacity > LOAD_FACTOR) {
if (hashmap_resize(map) != 0) return -1;
}
size_t index = hash_string(key) % map->capacity;
// Check for existing key
for (Entry *e = map->buckets[index]; e; e = e->next) {
if (strcmp(e->key, key) == 0) {
e->value = value;
return 0;
}
}
// New entry
Entry *entry = malloc(sizeof(Entry));
if (!entry) return -1;
entry->key = strdup(key);
if (!entry->key) {
free(entry);
return -1;
}
entry->value = value;
entry->next = map->buckets[index];
map->buckets[index] = entry;
map->size++;
return 0;
}
int hashmap_get(HashMap *map, const char *key, int *value) {
size_t index = hash_string(key) % map->capacity;
for (Entry *e = map->buckets[index]; e; e = e->next) {
if (strcmp(e->key, key) == 0) {
*value = e->value;
return 1; // Found
}
}
return 0; // Not found
}
int hashmap_remove(HashMap *map, const char *key) {
size_t index = hash_string(key) % map->capacity;
Entry *prev = NULL;
Entry *e = map->buckets[index];
while (e) {
if (strcmp(e->key, key) == 0) {
if (prev) {
prev->next = e->next;
} else {
map->buckets[index] = e->next;
}
free(e->key);
free(e);
map->size--;
return 1;
}
prev = e;
e = e->next;
}
return 0;
}
Binary Search Tree
Copy
#include <stdio.h>
#include <stdlib.h>
typedef struct TreeNode {
int key;
int value;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
typedef struct {
TreeNode *root;
size_t size;
} BST;
BST *bst_create(void) {
return calloc(1, sizeof(BST));
}
static void bst_destroy_node(TreeNode *node) {
if (node) {
bst_destroy_node(node->left);
bst_destroy_node(node->right);
free(node);
}
}
void bst_destroy(BST *tree) {
bst_destroy_node(tree->root);
free(tree);
}
static TreeNode *create_node(int key, int value) {
TreeNode *node = malloc(sizeof(TreeNode));
if (node) {
node->key = key;
node->value = value;
node->left = node->right = NULL;
}
return node;
}
int bst_insert(BST *tree, int key, int value) {
TreeNode **current = &tree->root;
while (*current) {
if (key < (*current)->key) {
current = &(*current)->left;
} else if (key > (*current)->key) {
current = &(*current)->right;
} else {
// Key exists, update value
(*current)->value = value;
return 0;
}
}
*current = create_node(key, value);
if (!*current) return -1;
tree->size++;
return 0;
}
int bst_find(BST *tree, int key, int *value) {
TreeNode *current = tree->root;
while (current) {
if (key < current->key) {
current = current->left;
} else if (key > current->key) {
current = current->right;
} else {
*value = current->value;
return 1;
}
}
return 0;
}
static TreeNode *find_min(TreeNode *node) {
while (node->left) {
node = node->left;
}
return node;
}
static TreeNode *bst_remove_node(TreeNode *node, int key, int *removed) {
if (!node) return NULL;
if (key < node->key) {
node->left = bst_remove_node(node->left, key, removed);
} else if (key > node->key) {
node->right = bst_remove_node(node->right, key, removed);
} else {
*removed = 1;
if (!node->left) {
TreeNode *right = node->right;
free(node);
return right;
}
if (!node->right) {
TreeNode *left = node->left;
free(node);
return left;
}
// Two children: find inorder successor
TreeNode *successor = find_min(node->right);
node->key = successor->key;
node->value = successor->value;
node->right = bst_remove_node(node->right, successor->key, removed);
}
return node;
}
int bst_remove(BST *tree, int key) {
int removed = 0;
tree->root = bst_remove_node(tree->root, key, &removed);
if (removed) tree->size--;
return removed;
}
// Traversals
void bst_inorder(TreeNode *node, void (*visit)(int, int)) {
if (node) {
bst_inorder(node->left, visit);
visit(node->key, node->value);
bst_inorder(node->right, visit);
}
}
Min Heap (Priority Queue)
Copy
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data;
size_t size;
size_t capacity;
} MinHeap;
MinHeap *heap_create(size_t capacity) {
MinHeap *h = malloc(sizeof(MinHeap));
if (!h) return NULL;
h->data = malloc(capacity * sizeof(int));
if (!h->data) {
free(h);
return NULL;
}
h->size = 0;
h->capacity = capacity;
return h;
}
void heap_destroy(MinHeap *h) {
if (h) {
free(h->data);
free(h);
}
}
static void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
static void sift_up(MinHeap *h, size_t index) {
while (index > 0) {
size_t parent = (index - 1) / 2;
if (h->data[index] >= h->data[parent]) break;
swap(&h->data[index], &h->data[parent]);
index = parent;
}
}
static void sift_down(MinHeap *h, size_t index) {
while (1) {
size_t smallest = index;
size_t left = 2 * index + 1;
size_t right = 2 * index + 2;
if (left < h->size && h->data[left] < h->data[smallest]) {
smallest = left;
}
if (right < h->size && h->data[right] < h->data[smallest]) {
smallest = right;
}
if (smallest == index) break;
swap(&h->data[index], &h->data[smallest]);
index = smallest;
}
}
int heap_push(MinHeap *h, int value) {
if (h->size == h->capacity) {
size_t new_cap = h->capacity * 2;
int *new_data = realloc(h->data, new_cap * sizeof(int));
if (!new_data) return -1;
h->data = new_data;
h->capacity = new_cap;
}
h->data[h->size] = value;
sift_up(h, h->size);
h->size++;
return 0;
}
int heap_pop(MinHeap *h) {
if (h->size == 0) return 0; // Or error
int min = h->data[0];
h->data[0] = h->data[--h->size];
if (h->size > 0) {
sift_down(h, 0);
}
return min;
}
int heap_peek(MinHeap *h) {
return h->size > 0 ? h->data[0] : 0;
}
Ring Buffer (Circular Queue)
Copy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
typedef struct {
char *buffer;
size_t capacity;
size_t head; // Read position
size_t tail; // Write position
} RingBuffer;
RingBuffer *ringbuf_create(size_t capacity) {
RingBuffer *rb = malloc(sizeof(RingBuffer));
if (!rb) return NULL;
// Add 1 to distinguish full from empty
rb->buffer = malloc(capacity + 1);
if (!rb->buffer) {
free(rb);
return NULL;
}
rb->capacity = capacity + 1;
rb->head = 0;
rb->tail = 0;
return rb;
}
void ringbuf_destroy(RingBuffer *rb) {
if (rb) {
free(rb->buffer);
free(rb);
}
}
bool ringbuf_is_empty(RingBuffer *rb) {
return rb->head == rb->tail;
}
bool ringbuf_is_full(RingBuffer *rb) {
return ((rb->tail + 1) % rb->capacity) == rb->head;
}
size_t ringbuf_size(RingBuffer *rb) {
if (rb->tail >= rb->head) {
return rb->tail - rb->head;
}
return rb->capacity - rb->head + rb->tail;
}
size_t ringbuf_write(RingBuffer *rb, const char *data, size_t len) {
size_t written = 0;
while (written < len && !ringbuf_is_full(rb)) {
rb->buffer[rb->tail] = data[written++];
rb->tail = (rb->tail + 1) % rb->capacity;
}
return written;
}
size_t ringbuf_read(RingBuffer *rb, char *data, size_t len) {
size_t read = 0;
while (read < len && !ringbuf_is_empty(rb)) {
data[read++] = rb->buffer[rb->head];
rb->head = (rb->head + 1) % rb->capacity;
}
return read;
}
// Lock-free single producer, single consumer variant
typedef struct {
char *buffer;
size_t capacity;
_Atomic size_t head;
_Atomic size_t tail;
} LockFreeRingBuffer;
Generic Data Structure with void*
Copy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef int (*CompareFunc)(const void*, const void*);
typedef void (*FreeFunc)(void*);
typedef void (*PrintFunc)(const void*);
typedef struct {
void **data;
size_t size;
size_t capacity;
size_t elem_size;
CompareFunc compare;
FreeFunc free_elem;
} GenericVector;
GenericVector *gvec_create(size_t elem_size, size_t initial_cap,
CompareFunc cmp, FreeFunc free_fn) {
GenericVector *v = malloc(sizeof(GenericVector));
if (!v) return NULL;
v->data = malloc(initial_cap * sizeof(void*));
if (!v->data) {
free(v);
return NULL;
}
v->size = 0;
v->capacity = initial_cap;
v->elem_size = elem_size;
v->compare = cmp;
v->free_elem = free_fn;
return v;
}
void gvec_destroy(GenericVector *v) {
if (v) {
if (v->free_elem) {
for (size_t i = 0; i < v->size; i++) {
v->free_elem(v->data[i]);
}
}
free(v->data);
free(v);
}
}
int gvec_push(GenericVector *v, const void *elem) {
if (v->size == v->capacity) {
size_t new_cap = v->capacity * 2;
void **new_data = realloc(v->data, new_cap * sizeof(void*));
if (!new_data) return -1;
v->data = new_data;
v->capacity = new_cap;
}
v->data[v->size] = malloc(v->elem_size);
if (!v->data[v->size]) return -1;
memcpy(v->data[v->size], elem, v->elem_size);
v->size++;
return 0;
}
void *gvec_get(GenericVector *v, size_t index) {
if (index >= v->size) return NULL;
return v->data[index];
}
// Usage with different types
typedef struct {
char name[50];
int age;
} Person;
int compare_person(const void *a, const void *b) {
return strcmp(((Person*)a)->name, ((Person*)b)->name);
}
void print_person(const void *p) {
const Person *person = p;
printf("%s (%d)\n", person->name, person->age);
}
int main(void) {
GenericVector *people = gvec_create(sizeof(Person), 10,
compare_person, free);
Person p1 = {"Alice", 30};
Person p2 = {"Bob", 25};
gvec_push(people, &p1);
gvec_push(people, &p2);
for (size_t i = 0; i < people->size; i++) {
print_person(gvec_get(people, i));
}
gvec_destroy(people);
return 0;
}
Exercises
1
LRU Cache
Implement an LRU cache using a hash map and doubly linked list.
2
Red-Black Tree
Implement a self-balancing red-black tree with insert and delete.
3
Graph with Adjacency List
Implement a graph with BFS and DFS traversals.
4
Trie
Implement a trie for efficient string prefix matching.
Next Up
Undefined Behavior
Understand the dragons lurking in C