Comparison-Based Tree
Navigation depends on whole-key comparisons and tree height.
CodeBhavya
Store and search keys by their characters or bits using Standard Tries, Binary Tries, Patricia/Radix Tries and Suffix Tries.
After completing this topic, you should be able to:
A comparison tree asks whether one whole key is smaller or larger. A digital search tree examines the key one symbol at a time: a character for words or a bit for integers.
Navigation depends on whole-key comparisons and tree height.
Navigation depends on positions inside the key.
The root represents an empty prefix.
Select the edge for the next character or bit.
Follow an existing edge or allocate a node.
Distinguish complete keys from prefixes.
โAll operationsโ can include many application-specific queries. The following is the complete practical set normally expected in data-structures courses, examinations, interviews and coding problems.
| Structure | Core update operations | Core query operations | Typical time |
|---|---|---|---|
| Standard Trie | Create, insert, delete | Exact search, prefix search, traversal, autocomplete, word/prefix count | O(L), plus reported output |
| Binary Trie | Create, insert, delete | Exact search, minimum XOR, maximum XOR, bit-prefix matching | O(B) |
| Patricia / Radix Trie | Insert, split edge, delete, merge path | Exact search, prefix search, traversal, autocomplete | O(L) |
| Suffix Trie | Build all suffixes, rebuild after text change | Substring, suffix, occurrences, occurrence count, longest repeated substring | O(m) query after O(nยฒ) build |
Start at root. For each character, create the missing child and move down. Mark the final node terminal.
Follow every character. Return true only when the path exists and the final node is terminal.
Follow all prefix characters. Return true when the path exists; terminal status is not required.
Find the word, clear its terminal flag, then remove empty non-terminal nodes while returning toward the root.
Reach the prefix node, then perform DFS and report every terminal descendant.
DFS in character order gives lexicographic words; increment a counter at each terminal node.
Read bits from MSB to LSB, following or creating the 0/1 child.
Clear the leaf and prune unused bit nodes upward; retain shared prefixes.
Prefer the opposite query bit at every level; use the same bit only when necessary.
Prefer the same query bit at every level; use the opposite bit only when necessary.
Follow matching bits and remember the deepest terminal prefix encountered.
Store a frequency at the leaf when repeated integers must be supported.
Compare a remaining key with an edge label; follow, create or split according to the longest common prefix.
Every compressed edge must match fully and the finishing node must be terminal.
The prefix succeeds even when it ends in the middle of a compressed edge.
Clear terminal status, remove an empty leaf, then merge any non-terminal one-child node with its child.
Reach the matching edge or node and traverse terminal descendants.
Append whole edge labelsโnot single charactersโwhile performing DFS.
Insert text[iโฆnโ1] for every starting position i and store suffix positions.
A pattern exists when its complete path can be followed from the root.
Follow the path and additionally require the final node to mark a suffix end.
Reach the pattern node, then collect the k suffix positions stored below it.
Count stored suffix positions below the pattern node.
Find the deepest node whose subtree contains at least two suffix positions.
A Trie stores one character per edge. Keys with a common prefix share the same path. A terminal flag is essential because a path such as app may be both a word and a prefix of apple.
Create missing character edges and mark the last node terminal.
Every character edge must exist and the final node must be terminal.
Every prefix edge must exist; terminal status is not required.
Insert and search take O(L).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ALPHABET 26
#define MAX_WORD 50
typedef struct TrieNode {
struct TrieNode *child[ALPHABET];
int terminal;
} TrieNode;
TrieNode *createNode(void) {
TrieNode *node = malloc(sizeof(TrieNode));
if (node == NULL) exit(EXIT_FAILURE);
node->terminal = 0;
for (int i = 0; i < ALPHABET; i++) node->child[i] = NULL;
return node;
}
void insertWord(TrieNode *root, const char *word) {
TrieNode *current = root;
for (int i = 0; word[i] != '\0'; i++) { /* trie insert loop */
int index = word[i] - 'a';
if (current->child[index] == NULL)
current->child[index] = createNode(); /* trie create child */
current = current->child[index]; /* trie move child */
}
current->terminal = 1; /* trie mark terminal */
}
int searchWord(TrieNode *root, const char *word) {
TrieNode *current = root;
for (int i = 0; word[i] != '\0'; i++) { /* trie search loop */
int index = word[i] - 'a'; /* trie search step */
if (current->child[index] == NULL)
return 0; /* trie search miss */
current = current->child[index];
}
return current->terminal; /* trie search found */
}
int main(void) {
int n;
char word[MAX_WORD], query[MAX_WORD];
TrieNode *root = createNode(); /* trie create root */
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%49s", word);
insertWord(root, word); /* trie insert call */
}
scanf("%49s", query);
int found = searchWord(root, query); /* trie search call */
printf("%s\n", found ? "Found" : "Not Found");
/* trie complete */
return 0;
}5
app apple bat ball bag
appleFoundA Binary Trie has at most two children per node: 0 and 1. It is useful for IP routing, integer sets, minimum/maximum XOR and bit-prefix matching.
Only 0 and 1 children.
Fixed by integer width, such as 8, 32 or 64 bits.
Greedily prefer the opposite bit.
O(B), where B is bit width.
#include <stdio.h>
#include <stdlib.h>
#define BITS 8
typedef struct BinaryNode {
struct BinaryNode *child[2];
int value;
int terminal;
} BinaryNode;
BinaryNode *createBinaryNode(void) {
BinaryNode *node = malloc(sizeof(BinaryNode));
if (node == NULL) exit(EXIT_FAILURE);
node->child[0] = node->child[1] = NULL;
node->value = 0;
node->terminal = 0;
return node;
}
void insertNumber(BinaryNode *root, int value) {
BinaryNode *current = root;
for (int bit = BITS - 1; bit >= 0; bit--) { /* binary insert loop */
int digit = (value >> bit) & 1;
if (current->child[digit] == NULL)
current->child[digit] = createBinaryNode(); /* binary create child */
current = current->child[digit]; /* binary move child */
}
current->terminal = 1;
current->value = value; /* binary store value */
}
int bestXorPartner(BinaryNode *root, int query) {
BinaryNode *current = root;
for (int bit = BITS - 1; bit >= 0; bit--) { /* binary xor loop */
int digit = (query >> bit) & 1;
int preferred = 1 - digit; /* binary prefer opposite */
if (current->child[preferred] != NULL)
current = current->child[preferred]; /* binary xor move */
else
current = current->child[digit];
}
return current->value; /* binary xor result */
}
int main(void) {
int n, value, query;
BinaryNode *root = createBinaryNode(); /* binary create root */
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &value);
insertNumber(root, value); /* binary insert call */
}
scanf("%d", &query);
int partner = bestXorPartner(root, query); /* binary xor call */
printf("Partner = %d\nMaximum XOR = %d\n", partner, query ^ partner);
/* binary complete */
return 0;
}5
5 25 10 2 8
5Partner = 25
Maximum XOR = 28A Patricia Trie compresses every non-terminal one-child chain into one edge label. The name is commonly expanded as Practical Algorithm To Retrieve Information Coded In Alphanumeric.
bear may use four individual character edges.
If no branching occurs, the same path can be one edge labelled bear.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ALPHABET 26
#define MAX_WORD 64
typedef struct RadixNode {
char label[MAX_WORD];
int terminal;
struct RadixNode *child[ALPHABET];
} RadixNode;
RadixNode *createRadixNode(const char *label) {
RadixNode *node = malloc(sizeof(RadixNode));
if (node == NULL) exit(EXIT_FAILURE);
strcpy(node->label, label);
node->terminal = 0;
for (int i = 0; i < ALPHABET; i++) node->child[i] = NULL;
return node;
}
int commonPrefix(const char *first, const char *second) {
int length = 0;
while (first[length] && second[length] &&
first[length] == second[length]) length++;
return length;
}
void insertRadix(RadixNode *root, const char *word) {
RadixNode *current = root;
const char *remaining = word;
while (*remaining) { /* patricia insert loop */
int index = remaining[0] - 'a';
RadixNode *next = current->child[index];
if (next == NULL) {
next = createRadixNode(remaining);
next->terminal = 1;
current->child[index] = next; /* patricia new edge */
return;
}
int common = commonPrefix(remaining, next->label); /* patricia compare prefix */
if (common == (int)strlen(next->label)) {
remaining += common;
current = next; /* patricia descend */
if (*remaining == '\0') current->terminal = 1; /* patricia mark terminal */
continue;
}
char prefix[MAX_WORD], oldSuffix[MAX_WORD];
strncpy(prefix, next->label, common);
prefix[common] = '\0';
strcpy(oldSuffix, next->label + common);
RadixNode *split = createRadixNode(prefix);
strcpy(next->label, oldSuffix);
split->child[oldSuffix[0] - 'a'] = next;
current->child[index] = split;
const char *newSuffix = remaining + common;
if (*newSuffix == '\0') {
split->terminal = 1;
} else {
RadixNode *leaf = createRadixNode(newSuffix);
leaf->terminal = 1;
split->child[newSuffix[0] - 'a'] = leaf;
}
/* patricia split edge */
return;
}
current->terminal = 1;
}
int searchRadix(RadixNode *root, const char *word) {
RadixNode *current = root;
const char *remaining = word;
while (*remaining) { /* patricia search loop */
RadixNode *next = current->child[remaining[0] - 'a']; /* patricia search edge */
if (next == NULL) return 0;
int length = (int)strlen(next->label);
if (strncmp(remaining, next->label, length) != 0) return 0;
remaining += length;
current = next;
}
return current->terminal; /* patricia search result */
}
int main(void) {
int n;
char word[MAX_WORD], query[MAX_WORD];
RadixNode *root = createRadixNode(""); /* patricia create root */
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%63s", word);
insertRadix(root, word); /* patricia insert call */
}
scanf("%63s", query);
int found = searchRadix(root, query); /* patricia search call */
printf("%s\n", found ? "Found" : "Not Found");
/* patricia complete */
return 0;
}5
bear bell bid bull buy
bellFoundA Suffix Trie inserts every suffix of a text. Therefore, every substring becomes a prefix of at least one stored suffix.
O(nยฒ) for the direct construction.
O(nยฒ) in the worst case.
O(m), where m is pattern length.
A Suffix Tree compresses one-child paths.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ALPHABET 26
#define MAX_TEXT 60
typedef struct SuffixNode {
struct SuffixNode *child[ALPHABET];
int terminal;
} SuffixNode;
SuffixNode *createSuffixNode(void) {
SuffixNode *node = malloc(sizeof(SuffixNode));
if (node == NULL) exit(EXIT_FAILURE);
node->terminal = 0;
for (int i = 0; i < ALPHABET; i++) node->child[i] = NULL;
return node;
}
void insertSuffix(SuffixNode *root, const char *text, int start) {
SuffixNode *current = root;
for (int i = start; text[i] != '\0'; i++) { /* suffix insert loop */
int index = text[i] - 'a';
if (current->child[index] == NULL)
current->child[index] = createSuffixNode(); /* suffix create child */
current = current->child[index]; /* suffix move child */
}
current->terminal = 1; /* suffix mark terminal */
}
int containsSubstring(SuffixNode *root, const char *pattern) {
SuffixNode *current = root;
for (int i = 0; pattern[i] != '\0'; i++) { /* suffix search loop */
int index = pattern[i] - 'a'; /* suffix search step */
if (current->child[index] == NULL) return 0;
current = current->child[index];
}
return 1; /* suffix search result */
}
int main(void) {
char text[MAX_TEXT], pattern[MAX_TEXT];
SuffixNode *root = createSuffixNode(); /* suffix create root */
scanf("%59s", text);
for (int start = 0; text[start] != '\0'; start++)
insertSuffix(root, text, start); /* suffix insert call */
scanf("%59s", pattern);
int found = containsSubstring(root, pattern); /* suffix search call */
printf("%s\n", found ? "Substring Found" : "Substring Not Found");
/* suffix complete */
return 0;
}banana
anaSubstring Found| Structure | Stored Unit | Primary Strength | Typical Time | Memory |
|---|---|---|---|---|
| Standard Trie | Character per edge | Exact and prefix search | O(L) | High |
| Binary Trie | Bit per edge | XOR and bit-prefix queries | O(B) | Moderate |
| Patricia/Radix Trie | String segment per edge | Compressed prefix index | O(L) | Lower than Trie |
| Suffix Trie | All suffix characters | Substring search | O(m) search | Very high |
Choose a structure and operation, then follow every character, bit, compressed-edge change and query decision.
CodeBhavyaStep 0 of 0
Select a structure. The matching complete C program is loaded into the same compact tracer, and the highlighted cursor follows its representative construction and query operation automatically.
โ
Step 0 of 0
Use a Standard Trie when prefix operations dominate and memory is acceptable.
Use a Binary Trie because each decision depends on one bit.
Use a Patricia/Radix Trie to eliminate long one-child chains.
Use a Suffix Trie for teaching or small inputs; use a Suffix Tree/Array for large inputs.
A path does not prove an exact word exists.
Validate lowercase input before using ch-'a'.
Maximum-XOR traversal must start from the most significant bit.
Preserve both the old suffix and new suffix after the common prefix.
Every suffix is a substring, but most substrings are not suffixes.
Freeing recursively allocated trie nodes is required in long-running applications.