X Fast Trie Search

Search for an integer key in an X fast trie using hashed prefixes over a binary trie.

X Fast Trie Search

X fast trie search looks up an integer key from a fixed word universe by combining a binary trie with hash tables over prefixes.

A normal binary trie follows one bit at a time. An X fast trie accelerates this process by storing every prefix length in a hash table. This allows the search to find the deepest existing prefix by binary search over prefix length.

Problem

Given an X fast trie storing integer keys with word size $w$, decide whether a target key x is present.

Return true if x exists, otherwise false.

Structure

An X fast trie stores keys as fixed width binary strings.

For word size $w$, each key has bits:

$$ b_0 b_1 \cdots b_{w-1} $$

The data structure maintains:

component purpose
binary trie represents prefixes of stored keys
prefix hash tables map prefixes at each depth to trie nodes
linked leaves store all keys in sorted order
jump pointers help locate predecessor or successor

For exact membership, the deepest prefix of length $w$ is the full key.

Algorithm

Check whether the full key appears in the hash table for depth $w$.

For predecessor or successor search, the structure first finds the longest existing prefix using binary search over depths. Exact membership is simpler because the full length prefix is enough.

x_fast_trie_member(T, x):
    prefix = bits(x, T.word_size)

    if prefix in T.level[T.word_size]:
        return true

    return false

For the more general search path:

x_fast_trie_deepest_prefix(T, x):
    lo = 0
    hi = T.word_size

    while lo < hi:
        mid = (lo + hi + 1) // 2
        p = prefix(x, mid)

        if p in T.level[mid]:
            lo = mid
        else:
            hi = mid - 1

    return lo

Example

Use word size $w = 4$.

Stored keys:

decimal bits
3 0011
9 1001
12 1100

Search for 9. Its full binary key is 1001.

depth prefix
1 1
2 10
3 100
4 1001

Since prefix 1001 exists at depth 4, the key is present.

Correctness

Every stored key appears as a leaf in the binary trie. The hash table at depth $w$ contains exactly the full length prefixes of stored keys.

If the lookup finds the full bit string for x, then a leaf for x exists, so the key is present. If the full bit string is absent, no stored key has exactly the same $w$ bits, so x is absent.

The deepest prefix routine is correct because prefixes are monotone by depth. If a prefix of length $d$ exists for x, then every shorter prefix also exists. Therefore binary search over prefix length finds the maximum existing depth.

Complexity

Let $w$ be the number of bits in the universe.

operation expected time
exact membership $O(1)$
deepest prefix search $O(\log w)$
predecessor or successor $O(\log w)$

Space usage is:

$$ O(nw) $$

because every stored key may contribute one prefix per level.

When to Use

X fast tries are useful for bounded integer keys when fast predecessor, successor, and membership operations are needed.

They are appropriate when:

  • keys are fixed width integers
  • expected hash table performance is acceptable
  • memory overhead of storing many prefixes is acceptable
  • ordered operations are needed faster than comparison tree bounds

For lower memory usage, a Y fast trie is often preferred.

Implementation

class XFastTrie:
    def __init__(self, word_size):
        self.word_size = word_size
        self.level = [set() for _ in range(word_size + 1)]

    def bits(self, x, length):
        shift = self.word_size - length
        return x >> shift if length > 0 else 0

    def insert_key_prefixes(self, x):
        for depth in range(self.word_size + 1):
            self.level[depth].add(self.bits(x, depth))

    def member(self, x):
        return self.bits(x, self.word_size) in self.level[self.word_size]

    def deepest_prefix(self, x):
        lo = 0
        hi = self.word_size

        while lo < hi:
            mid = (lo + hi + 1) // 2
            p = self.bits(x, mid)

            if p in self.level[mid]:
                lo = mid
            else:
                hi = mid - 1

        return lo
type XFastTrie struct {
	WordSize int
	Level    []map[uint64]bool
}

func NewXFastTrie(wordSize int) *XFastTrie {
	level := make([]map[uint64]bool, wordSize+1)

	for i := range level {
		level[i] = make(map[uint64]bool)
	}

	return &XFastTrie{
		WordSize: wordSize,
		Level:    level,
	}
}

func (t *XFastTrie) Prefix(x uint64, length int) uint64 {
	if length == 0 {
		return 0
	}

	shift := t.WordSize - length
	return x >> shift
}

func (t *XFastTrie) InsertKeyPrefixes(x uint64) {
	for depth := 0; depth <= t.WordSize; depth++ {
		t.Level[depth][t.Prefix(x, depth)] = true
	}
}

func (t *XFastTrie) Member(x uint64) bool {
	p := t.Prefix(x, t.WordSize)
	return t.Level[t.WordSize][p]
}

func (t *XFastTrie) DeepestPrefix(x uint64) int {
	lo, hi := 0, t.WordSize

	for lo < hi {
		mid := lo + (hi-lo+1)/2
		p := t.Prefix(x, mid)

		if t.Level[mid][p] {
			lo = mid
		} else {
			hi = mid - 1
		}
	}

	return lo
}