Knowledge / Computer Science and Engineering / Tries and string data structures

Tries and string data structures

Computer Science and Engineering (B.E./B.Tech) · Data structures · free study notes for exams, placements and interviews

In short

A trie (prefix tree, also called a digital search tree) stores a set of strings as a tree of characters, where every path from the root spells a prefix, so lookup costs O(L) in the key length L and never depends on how many keys are stored. Around it sits a family of string structures - compressed/Patricia tries, suffix tries, suffix trees, suffix arrays with LCP, and the Aho-Corasick automaton - that answer prefix, substring and multi-pattern queries far faster than scanning. These appear in the Data Structures and Algorithms papers of almost every Indian university, carry marks in GATE CS/DA (complexity of trie operations, suffix arrays, KMP preprocessing), and are standard interview material at Amazon, Google, Microsoft, Flipkart, Zoho, Oracle, Samsung R&D, Arcesium and the DE Shaw/Goldman style quant-dev rounds. On the job they power search autosuggest, spell check, IP routing tables, AML name screening and Indic text input.

Explained

The trie: structure and operations

Variants and space optimisations

Trie compared with hashing and balanced trees

Single-pattern matching: KMP, Z-function and Rabin-Karp

Aho-Corasick: a trie that matches many patterns at once

  1. Build a trie of all k patterns (the goto function).
  2. BFS the trie computing failure links: fail(v) = the deepest node whose string is a proper suffix of v's string. For depth-1 nodes, fail = root.
  3. Compute output links (dictionary-suffix links) so that a match of a shorter pattern ending at the same position is also reported.
  4. Scan the text once, following goto, falling back along fail on mismatch, and emitting outputs at each node.
  5. Complexity: build O(Σ|Pᵢ| · σ) time and O(Σ|Pᵢ| · σ) space for an automaton-style transition table; search O(n + z) where z is the number of occurrences - independent of the number of patterns.
  6. This is the engine behind virus/IDS signature scanning (Snort), profanity and keyword filters, and AML/sanctions name screening in Indian banks.

Suffix structures: trie, tree, array and LCP

Key points

Formulas and examples

Trie operation cost: T(insert) = T(search) = T(delete) = O(L), L = key length; space O(Σ|wᵢ| · σ) with array children, O(Σ|wᵢ|) with map children
Trie node upper bound: N ≤ 1 + Σ|wᵢ|; compressed trie: N ≤ 2n − 1 for n keys
Child-array memory = N × σ × p bytes (p = pointer size, σ = alphabet size)
KMP: π[i] = max{k < i+1 : P[0..k−1] = P[i−k+1..i]}; total time O(n + m), space O(m)
Shortest period of S (length n) = n − π[n−1]
Polynomial hash: h(S) = (Σ_{i=0}^{m−1} S[i] · b^{m−1−i}) mod q
Rolling update: h' = ((h − S[i]·b^{m−1}) · b + S[i+m]) mod q
Aho-Corasick: build O(Σ|Pᵢ|·σ), search O(n + z), z = occurrences reported
Suffix array: construction O(n log n) (doubling + radix) or O(n) (SA-IS); search O(m log n)
Distinct substrings of S = n(n+1)/2 − Σ_{i=1}^{n−1} LCP[i]
Suffixes of a string of length n = n (n+1 with the sentinel $)

Example 1 - Trie size and memory. Insert {car, card, care, cat, dog}.

Example 2 - KMP failure function and search. P = "ababaca" (m = 7).

i0123456
P[i]ababaca
π[i]0012301

Example 3 - Suffix array, LCP and distinct substrings. S = "banana", n = 6.

RankSA[i]SuffixLCP[i]
05a-
13ana1
21anana3
30banana0
44na0
52nana2

Example 4 - Rabin-Karp rolling hash. Map a→1 … z→26, base b = 31, modulus q = 101, window m = 3.

Example 5 - Binary trie for maximum XOR. A = {3, 10, 5, 25, 2, 8}, 5-bit keys.

class Node:
    __slots__ = ("ch", "end")
    def __init__(self): self.ch, self.end = {}, False

class Trie:
    def __init__(self): self.root = Node()
    def insert(self, w):
        cur = self.root
        for c in w:
            cur = cur.ch.setdefault(c, Node())
        cur.end = True                      # O(L)
    def _walk(self, w):
        cur = self.root
        for c in w:
            if c not in cur.ch: return None
            cur = cur.ch[c]
        return cur
    def search(self, w):      n = self._walk(w); return n is not None and n.end
    def starts_with(self, p): return self._walk(p) is not None
StructureBuildQuerySpaceBest for
Hash tableO(Σ\wᵢ\)O(L) avgLowExact lookup only
Balanced BSTO(nL log n)O(L log n)LowSorted keys, few prefix queries
TrieO(Σ\wᵢ\)O(L)High (σ per node)Prefix search, autocomplete
Compressed trieO(Σ\wᵢ\)O(L)O(n) nodesRouting, sparse long keys
Suffix treeO(n)O(m)~20n bytesAny-substring queries
Suffix array + LCPO(n log n)O(m log n)~8n bytesSame queries, far less memory
Aho-CorasickO(Σ\Pᵢ\·σ)O(n + z)O(Σ\Pᵢ\·σ)Thousands of patterns, one pass

Where it is used

Interview questions

Common mistakes

Related topics

Arrays and strings · Hashing and hash tables · Trees and binary search trees · Balanced trees: AVL, red-black and B-trees · Graphs and their representation · Dynamic programming (edit distance and LCS)

Open in the Knowledge reader →Find jobs →