C Mastery / Tries and Graphs
Part 8 — Data Structures and Algorithms in C

Tries and Graphs

This chapter implements a trie (prefix tree) and the two fundamental graph representations (adjacency list and adjacency matrix).

Why This Matters

Tries give prefix-based lookup in O(key-length), powering autocomplete, IP routing, and dictionaries. Graphs model relationships and are the substrate for routing, dependency resolution, social networks, and compiler analyses.

Prerequisites

Core Concept

Trie

A trie stores strings as paths from a root; each edge is a character. A node may be marked as "end of a word." Lookup/insert are O(L) where L is the key length, independent of the number of keys.

typedef struct TrieNode {
    struct TrieNode *children[26];  /* for a-z */
    _Bool is_end;
} TrieNode;

Graphs

A graph is vertices plus edges. Two common representations:

Space O(V + E), efficient for sparse graphs.

an edge i→j. Space O(V²), efficient for dense graphs.

Examples

Trie insert/search (lowercase a-z)

#include <stdlib.h>

TrieNode *trie_new(void)
{
    TrieNode *n = calloc(1, sizeof *n);
    return n;
}

int trie_insert(TrieNode *root, const char *s)
{
    TrieNode *n = root;
    for (; *s; s++) {
        int i = *s - 'a';
        if (i < 0 || i >= 26) return -1;
        if (n->children[i] == NULL) {
            n->children[i] = trie_new();
            if (!n->children[i]) return -1;
        }
        n = n->children[i];
    }
    n->is_end = 1;
    return 0;
}

_Bool trie_search(TrieNode *root, const char *s)
{
    TrieNode *n = root;
    for (; *s; s++) {
        int i = *s - 'a';
        if (i < 0 || i >= 26 || !n->children[i]) return 0;
        n = n->children[i];
    }
    return n->is_end;
}

Adjacency list graph

typedef struct Edge {
    int to;
    struct Edge *next;
} Edge;

typedef struct {
    Edge **adj;   /* array of adjacency lists */
    int V;
} Graph;

How It Works

A trie shares prefixes, so common prefixes are stored once. Lookup walks the path character by character. An adjacency list stores neighbors per vertex; an adjacency matrix stores all pairs directly.

Variations

Compressed tries

Compress single-child paths to save space (radix/Patricia trees), used in routing tables.

Weighted and directed graphs

Edges may be directed/undirected and carry weights. Adjacency lists store weights in the edge; matrices store weights in the cell.

Common Mistakes

Undefined Behavior

Portability

ASCII/lowercase-specific; use 256 for full bytes or a dynamic map for Unicode.

Under the Hood

A trie trades memory for speed (many child pointers). A graph's adjacency list chases pointers (cache-unfriendly); the matrix is dense but gives O(1) edge checks.

Practical Usage

Exercises

1. Implement trie insert/search/delete and a prefix count function. 2. Implement an adjacency-list graph with add-edge and DFS/BFS traversal. 3. Implement an adjacency-matrix graph and compare memory for a sparse graph.

Deep Challenge

Implement a trie that counts the number of words with a given prefix, and explain how to extend it to return all completions. Then discuss the memory trade-offs of a 26-way vs. 256-way trie.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.ds.trie06
c.ds.graph06