CSE3144 · Advanced Data Structures · Jul–Nov Semester 2026 · Lecture 13 of 36 · CO CSE3144.2

Tries & Digital Search Trees

Every structure since Lecture 7 has searched strings by comparing them whole. Today we stop comparing strings and start reading them — one character, or one bit, at a time.

Dr. Manu ShrivastavaCourse Instructor · Consultation Fri 2–5 PM, LHC 308F
~65 minutesSession outcome: apply prefix searching and dictionary operations
L13 · 00 — Agenda ~65 min

Today, part by part

Refresher: the catch with storing strings in a BST

00–03

O(M log n) instead of O(log n) — comparisons themselves get expensive.

Tries: definition

03–06

One node per shared prefix; the alphabet becomes the branching factor.

Animation: building a trie, 8 words

06–13

bear, bell, bid, bull, buy, sell, stock, stop — every shared prefix reused.

Animation: searching — a hit and a fast-failing miss

13–17

Lookup cost depends only on the word's length, never on how many words are stored.

Animation: prefix search — the autocomplete operation

17–21

Find every word starting with "bu" without touching anything else.

Animation: deleting a word

21–24

Unmark, then prune upward — but only as far as it's safe to.

Space cost, and compressing the trie

24–28

Θ(N·|Σ|) is the price of one array per node — chains of single children collapse away.

Why Digital Search Trees? Binary branching on bits

28–31

Trade the alphabet-wide array for two pointers and a stored key, like a BST fused with a trie.

Digital Search Tree: definition

31–34

Branch on bit i at depth i; any key can be the root.

Animation: building a DST, 5 keys

34–40

0110, 0010, 1001, 1011, 0000 — every bit comparison traced.

Animation: searching a DST — a hit and a miss

40–45

Unlike a trie, every node visited needs its own key comparison.

Animation: deleting from a DST — the tricky case

45–48

Why you can't just promote any child, the way you would in a BST.

Complexity, and trie vs. DST head-to-head

48–51

One comparison vs. one-per-level; array space vs. two pointers.

Application: IP routing

51–57

Longest-prefix matching — a worked router, traced packet by packet.

Recap & what's next

57–62

Lecture 14: Suffix Trees and String Processing Applications.

CSE3144 — Lecture 13
L13 · 01 — Refresher & the catch ~3 min

Every ordered dictionary you've built assumed cheap comparisons

Refresher — Lectures 7–10

AVL trees, Red-Black trees, Splay trees: all give O(log n) insert, delete, search, successor, predecessor, min/max — as long as comparing two keys is O(1). That's true for integers. It is not true for strings.

Comparing two strings of lengths r and s costs O(min(r, s)) — you have to walk both strings to find where they first differ (or confirm they're equal).

The catch

Put strings in a balanced BST and every one of those O(log n) comparisons secretly costs O(M), where M is the longest string in the tree. Total: O(M log n), not O(log n). The tree structure is fine — the comparisons are the hidden cost.

Question: can we design a structure where the cost of an operation depends only on the length of the word we're searching for — never on how many words are stored, and never with a repeated re-comparison of characters we've already matched? That structure is the trie.

CSE3144 — Lecture 13
L13 · 02 — Tries: definition ~3 min

One node per shared prefix — the name comes from "retrieval"

Formal definition

Fix an alphabet Σ. A trie is a tree where every node stores:

  • A bit marking whether the string spelled out from the root to this node is itself a stored word.
  • An array of |Σ| pointers — one possible child per character of the alphabet.

Every node corresponds to exactly one string: the sequence of characters on the path from the root down to it.

Why this is fast

Looking up a string w means following at most |w| pointers — each an O(1) array lookup. Total cost: O(|w|). Notice what's missing from that bound: n, the number of strings stored. A trie with 10 words and a trie with 10 million words answer the same query in the same time, provided the query word itself is short. That is the entire promise of a trie — and it directly answers last slide's question.

CSE3144 — Lecture 13 · Trie definition
L13 · 03 — Animation: building a trie ~7 min

Insert bear, bell, bid, bull, buy, sell, stock, stop

The insertion rule
insert(word):
  v = root
  for each character c in word:
      if v has no child for c:
          create that child       // new node
      v = v.child[c]              // descend
  mark v as "end of word"
How to read the animation

Before you watch: each box below is one trie node, and its letter is the edge that reaches it from its parent — not the node's entire storage. Every node really holds a full array of 26 pointer slots (one per possible next letter); the diagram only draws whichever slot is actually filled and leaves the other ~25 empty ones out, purely for readability. Those hidden empty slots are exactly what L13·07's space cost — Θ(N·|Σ|) — is warning about: they're still allocated in real memory, one box just doesn't draw the rule to show you the exception.

Amber nodes mark the end of a stored word (a "●" confirms it); teal nodes are pure branching points, not words themselves. Watch which letters get reused from an earlier word and which are genuinely new — that reuse is the entire space-saving idea of a trie. "bear" and "bell" are walked one character at a time so you can see exactly where the pseudocode's create-vs-reuse decision happens; every word after that inserts in a single step, since by then the pattern is established.

What node "b" really looks like in memory, right after "bear" is inserted
node "b"  ·  isWord = false
a 26-slot array — 25 are null
abcdefghijklmnopqrstuvwxyz
only the filled slot is ever drawn →
b
e

The highlighted "e" slot is the only non-null pointer — it points to the child node built while inserting "bear". The other 25 slots are null, but they're still there, still allocated. The animation's single "e" box is that one filled slot; the 25 empty ones are simply never drawn.

Interactive — insert one word at a time
CSE3144 — Lecture 13 · Trie construction
L13 · 04 — Animation: searching a trie ~4 min

A hit costs |word| steps. A miss can cost far fewer.

Search 1: look up "bull", which is stored. Search 2: look up "buzz", which is not — watch exactly where it falls off the tree.

Interactive — trace both searches
CSE3144 — Lecture 13 · Trie search
L13 · 05 — Animation: prefix search ~4 min

Autocomplete: every word that starts with "bu"

The operation

Descend along the prefix's own characters to find the node for "bu" — exactly like a search, but you don't need that node to be a word-end itself. Then explore every node in the subtree below it, collecting every word-end found. This is precisely what an autocomplete box, or a phone contact search, does on every keystroke.

Why a BST can't do this as cheaply

A BST ordered by string comparison would need a range query ("everything between 'bu' and 'bv'") — correct, but still paying string-comparison costs at O(log n) nodes. A trie finds the prefix node in O(|prefix|) and then just walks a subtree — no comparisons against unrelated words at all.

Interactive — find the prefix node, then collect every word below it
CSE3144 — Lecture 13 · Prefix search
L13 · 06 — Animation: deleting from a trie ~3 min

Unmark, then prune upward — but stop the moment it's unsafe

The deletion rule
delete(word):
  v = the node found by searching for word
  unmark v as "end of word"
  while v has no children and v is not a word-end:
      parent = v's parent
      remove v from parent
      v = parent
What to watch for

Delete "bull". Its path shares letters with "buy" — the pruning must stop exactly at the node still needed by "buy", and go no further. Removing one node too many would silently delete a different word.

Interactive — delete "bull"
CSE3144 — Lecture 13 · Trie deletion
L13 · 07 — Space, and compressing the trie ~4 min

The price of speed: one array per node

The space problem

A trie with N nodes needs Θ(N · |Σ|) space — every node carries a full array of |Σ| pointers, even when only one or two are ever used. For English text (|Σ| = 26) that's wasteful; for a genome (|Σ| = 4) it's less severe, but the idea generalizes badly to large alphabets.

The fix: compress single-child chains

Whenever a node has exactly one child and is not itself a word-end, it's carrying no branching information — merge it into the edge above it. Repeat until every remaining node either branches or ends a word. This is a compressed trie (also called a PATRICIA-style trie), and it can be stored in O(s) space, where s is the number of words, using index ranges instead of full substrings at each node.

Interactive — before and after, same 8 words
CSE3144 — Lecture 13 · Compressed tries
L13 · 08 — Why Digital Search Trees? ~3 min

Count the nodes: 16 to store 5 keys, or 5?

Suppose your keys aren't English words but fixed-length bit strings — IPv4 addresses, IDs, hash codes. Here |Σ| = 2, so a trie node's array is only 2 pointers, and the alphabet-size problem from L13·07 has gone away. So why change anything? Put the same five 4-bit keys into both structures and count.

Binary trie — 16 nodes for 5 keys

Every key is 4 bits, so every key needs a 4-edge path, and only the leaf is the key. Everything above it exists purely to be walked through:

root
├─0─ a ─0─ b ─0─ c ─0─ [0000]
│         └──1─ d ─0─ [0010]
│    └──1─ e ─1─ f ─0─ [0110]
└─1─ g ─0─ h ─0─ i ─1─ [1001]
              └──1─ j ─1─ [1011]

1 root + 2 + 3 + 5 + 5 leaves = 16 nodes. Eleven of them (a…j and the root) store nothing. For n keys of b bits, that's up to O(n·b) nodes — the cost now scales with key length, not alphabet size.

Digital Search Tree — 5 nodes for 5 keys

Store the actual key at every node — like a BST — but decide left-or-right using one bit of the key at each depth instead of comparing whole keys. Depth 1 uses bit 1, depth 2 uses bit 2, and so on.

Every node is now a real, useful key; nothing is a pure router. Five keys, exactly five nodes — you'll build this very tree in the next animation, and it comes out at height 3. That is a Digital Search Tree (DST).

And why not just use a BST?

A plain BST over these keys can degenerate into a chain depending on insertion order; AVL and Red-Black fix that, but only by carrying balance factors or colour bits and doing rotations on the way back up. A DST branches on bits of the key rather than on comparisons against what's already stored — so it stays short by construction. Watch the next animation finish with zero rotations and no balance metadata anywhere.

What it costs — the honest trade

Nothing is free. In a trie only the final node needs a real check; every step before it is a bare array lookup. In a DST every node holds a genuine key, so you pay a full key comparison at every level (L13·11 traces exactly this). That trade only pays off when a comparison is cheap — which is precisely the case for fixed-length keys that fit in a machine word: a 32-bit address, an ID, a hash code.

CSE3144 — Lecture 13
L13 · 09 — Digital Search Tree: definition ~3 min

Branch on bit i at depth i — any key may be the root

Formal definition (fixed-length keys)
  • An empty DST is empty. A non-empty DST's root holds any one key-value pair from the set — there's no ordering requirement on which key goes where.
  • All remaining pairs whose key's first bit is 0 go into the left subtree; all whose first bit is 1 go into the right subtree.
  • The left and right subtrees are themselves Digital Search Trees, built on the remaining bits (bit 2 decides the split one level down, bit 3 the level after that, and so on).
Insertion, precisely

The first key inserted becomes the root. Every later key descends from the root using its own bits, read most-significant-bit first, choosing left (bit = 0) or right (bit = 1) at each level — until it reaches an empty pointer, where it is inserted as a brand-new node. Its final depth is however many bits it took to find that empty slot.

CSE3144 — Lecture 13 · DST definition
L13 · 10 — Animation: building a DST ~6 min

Insert 0110, 0010, 1001, 1011, 0000 — 4-bit keys, MSB first

Every level is its own step, and the panel above the tree shows all three things happening at that level: the key in hand with the bit currently being read highlighted, the node we are standing at and which bit its depth makes it branch on, and the comparison that follows — first "is this a duplicate?", then "which way does that bit send us?". Empty slots are drawn as NIL, so you can see the gap before the key drops into it.

Interactive — insert five 4-bit keys
CSE3144 — Lecture 13 · DST construction
L13 · 11 — Animation: searching a DST ~5 min

Every node visited needs its own key comparison

The crucial difference from a trie

In a trie, only the last node reached needs its isWord bit checked — every earlier step is just an array lookup. In a DST, every node holds a real key, so at every level you must compare the target against the key stored there before deciding whether to continue.

Two searches

Search 1: look up 1011, which is stored — found after 2 comparisons and a match on the third node. Search 2: look up 1010, which is not stored — watch it fail only after reaching a node with no further child to follow.

Interactive — trace both searches
CSE3144 — Lecture 13 · DST search
L13 · 11a — Why you can't reuse BST deletion logic ~4 min

One child or two — promotion breaks either way

The rule this all comes down to

In a DST, whatever sits at depth d is reached because bit d of its key was read at that level — depth 1 reads bit 1, depth 2 reads bit 2, and so on, for every node, always. A BST splits deletion into three cases by child count (0, 1, or 2) and has a safe move for each. A DST doesn't get that luxury: promoting any subtree to a shallower depth can break this rule for everything beneath it — and that danger shows up whether the node you're deleting has one child or two.

What the animation covers

Two small examples, one after another: first, deleting a node with exactly one child — the obvious "just promote it" move — and where it silently loses a key. Then a two-child deletion, using BST's actual rule (copy the in-order successor's key up, remove the successor from below) — showing that it only survives by luck, not by any real guarantee.

Interactive — one-child promotion, then two-child promotion, both examined
CSE3144 — Lecture 13 · DST deletion — the limitation
L13 · 12 — Animation: deleting from a DST ~3 min

Why you can't delete a DST node the way you delete a BST node

The trap

In a BST, deleting an internal node promotes its in-order predecessor or successor — any descendant will do. In a DST that's wrong: every node's depth encodes which bit it's supposed to branch on. Promoting an internal descendant to a shallower depth would silently misalign its own children's bit rule.

The fix

Delete key 0110, the root. Instead of promoting any descendant, find a leaf anywhere in its subtree (a leaf has no descendants of its own to misalign), copy that leaf's key into the deleted spot, and remove the leaf from its original position — a trivial removal, since leaves have no children.

Interactive — delete the root, 0110
CSE3144 — Lecture 13 · DST deletion
L13 · 13 — Complexity, then trie vs. DST ~3 min

Same family of ideas, two different trade-offs

OperationTrieDigital Search Tree
Search / insert / deleteO(|w|) — length of the wordO(b) worst case, O(log n) expected — b = key length in bits
Comparisons needed per operationOne (at the final node only)One per level visited
AspectTrieDigital Search Tree
Branching factor|Σ| (alphabet size)2 (one bit at a time)
What's stored at a nodeAn array of pointers + an isWord bit — no key valueAn actual key, plus two child pointers
Space per nodeΘ(|Σ|) — can be largeO(1) — always exactly 2 pointers
Best suited toText, natural-language dictionaries, variable-length keysFixed-length bit keys — IP addresses, IDs, hash values
Typical real useAutocomplete, spell-checkers, search-engine indicesIP routing / packet classification, firewalls
CSE3144 — Lecture 13 · Comparison
L13 · 14 — Application: IP routing ~6 min

One packet in, one cable out — longest-prefix matching

A router is a box with a few cables leaving it. A packet arrives carrying a destination address, and the router must pick exactly one cable to send it out on. That is the entire job — and a trie is how it gets done, several hundred million times a second.

Why prefixes, not addresses

IPv4 has 4 billion addresses. No router can hold one rule per address, so it holds rules for blocks instead — and a block is written as a prefix: 10* means "every address whose first two bits are 1, 0".

Sorting post, same idea. Two rules on the wall: PIN starting 30… → Rajasthan bag; PIN starting 3020… → Jaipur bag. A letter for 302017 matches both. It goes in the Jaipur bag, because 3020… is the more specific rule. That is longest-prefix matching, and it is the whole problem.

Two different things — this is the part that trips people up
  • What goes INTO the trie: the routing table's prefixes — the rules on the wall. Each prefix is a path down from the root, and the node where that path ends is marked with its cable.
  • What you SEARCH with: one arriving packet's complete destination address. Not a prefix — a full address.
  • The answer: the deepest marked node you passed on the way down. Deepest = longest = most specific.
A worked router — 4-bit addresses, three cables (real ones are 32-bit; the idea is identical)

The routing table. Only this left column goes into the trie:

prefixcoverssend out
10*1000, 1001, 1010, 1011A
101*1010, 1011B
11*1100, 1101, 1110, 1111C

Notice 101* sits inside 10*. That is deliberate — it is how you say "send this whole block to A, except this smaller sub-block, which goes to B."

The trie built from it. Three marked nodes, nothing else stored:

Each node's label is the bit you read to reach it, exactly as in every trie this lecture has built. Amber and the "●" mean the same thing they always have — a stored entry ends here — and the badge names the cable it stores. The teal "1" node carries neither: no rule is just 1*, so nothing is stored there and the walk simply passes through.

A packet for 1011 arrives — walk it down
read bitnow atmarked?best so far
1"1"no
0"10"✦ AA (length 2)
1"101"✦ BB (length 3)
1nothing therestop

Out it goes on cable B. The rule is simply: keep walking, remember the last mark you passed, stop when you fall off the tree.

Three rules answering all sixteen addresses
addresscablewhy
1000, 1001Amatches 10* only
1010, 1011Bmatches 10* and 101* — longer wins
1100 … 1111Cmatches 11*
0000 … 0111no rule matches → default route

Scale it up: roughly a million prefix rules covering 4 billion addresses, every packet resolved in at most 32 steps. That compression is why the structure exists.

This is not the prefix search from L13·05 — it runs the other way

Prefix search (L13·05): you are given a prefix and want every key underneath it — "st" → stock, stop. Walk down, then collect everything below.

Longest-prefix match (here): you are given a full key and want the deepest stored prefix sitting above it on the path. Walk down, remember the last thing you passed. Same tree, opposite directions.

Why not a hash table — and why not a DST

A hash table needs an exact key. To use one here you would have to look up all 32 possible prefix lengths of the address separately and keep the longest hit. The trie answers it in a single walk, because the tree's shape already encodes "more specific = further down".

A DST is the wrong tool for this one: it stores each whole key at the first free node it finds, in a position that has nothing to do with what that path spells. Longest-prefix matching needs exactly the opposite — a prefix must sit at the node its own path spells out. Real routers use bit-tries and their compressed PATRICIA form.

CSE3144 — Lecture 13 · IP routing
L13 · 15 — Recap & what's next ~5 min

Stop comparing whole keys — read them instead

Lecture 14 — next

Suffix Trees and String Processing Applications

Every suffix of a text, in one structure — pattern matching, longest repeated substring, and more, all in linear preprocessing time.

Homework — bring to Lecture 14
  • Build a trie for the words: cat, car, cart, dog, do. Mark which nodes are word-ends and which are pure branching points.
  • On that trie, trace a prefix search for "ca" and list every word found.
  • Insert the 4-bit keys 1100, 0101, 0111, 1110, 1000 (MSB first) into an empty Digital Search Tree, one at a time. Draw the resulting tree and state its height.
  • Using the DST built in this lecture, trace search(0010) and search(0011); how many key comparisons does each need?
  • In 3–4 sentences: why would a DST be a poor choice for storing English dictionary words, and why would a trie be a poor choice for storing 128-bit IPv6 addresses?
  • Reading: Horowitz, Sahni & Anderson-Freed, Fundamentals of Data Structures, tries/digital search trees chapter; CLRS §12 (BSTs, for contrast).
CSE3144 — Lecture 13

Questions?

Dr. Manu Shrivastava — LHC 308F — Friday 2:00–5:00 PM

Next: Lecture 14 — Suffix Trees and String Processing Applications.