Structures

The tree the operation insists on

The compound walk means "everything that went left is smaller", which is true only if the leaves are in the alphabet's order. Huffman's tree is the smallest and its leaves are in frequency order, so the operation that makes a bidirectional search affordable costs the shape that makes an index small.

The compound walk down a wavelet tree returns, from one descent, both the rank of a symbol and the count of every symbol smaller than it.

The second half of that is a claim about the tree. At a level where the code goes right, the walk adds the number of positions that went left — and calls them smaller. That is true when the tree’s leaves run left to right in the alphabet’s order, and false otherwise.

Where each symbol's leaf actually sitsThe first 12 symbols of the alphabet, placed at the position their code word puts them in — reading a code as a binary fraction is reading the path to its leaf, so this is the tree's bottom row. In the top two rows the symbols run left to right in their own order, which is what makes "everything that went left is smaller" true. In Huffman's tree they do not, because the construction merges the two lightest weights wherever they are. A compound walk in that tree still runs and still returns a count; the count is of whatever happens to be to the left.a fixed-length code — leaves in order 0c2e4g6i8m10the best ordered tree — leaves in order 0a1c2e4f5i8n11the best tree of any shape — leaves in frequency order 0a1e4h7n11d3l9c2the number under each symbol is its place in the alphabet4,096 characters of english12 symbols drawn
Fig. 1 Where each symbol’s leaf sits in each of the three shapes. Reading a code word as a binary fraction is reading the path to its leaf, so this is the tree’s bottom row.

Huffman’s tree does not have that property, and Huffman’s tree is the smallest one there is.

Why Huffman’s leaves are not in order

The construction takes the two lightest weights and merges them, wherever they are in the alphabet. If q and z are the two rarest letters they become siblings, and they sit at the bottom of the tree next to each other regardless of everything between them.

The result is optimal — no binary tree over those weights has a smaller weighted path length, which is what makes n·H₀ the size of a Huffman-shaped wavelet tree — and its leaf order is the order the merges happened to produce, which is a frequency order and not a symbol order.

So a compound walk in it still runs and still returns a count. The count is of whatever went left, which is a set of symbols with no relation to the one being asked about: measured over two hundred queries on twenty-one symbols, the answer is wrong on 81% of them, worst case 706 against a true 3,058.

That is the worst kind of failure — a plausible number, silently — and it is why the index that uses the compound operation refuses a Huffman-shaped tree at construction rather than at query time.

The best tree that is in order

If order is a constraint, the question becomes: among trees whose leaves are in the alphabet’s order, which is smallest?

That is a different optimisation and it has a different algorithm. Huffman’s merge is unavailable, because merging two non-adjacent symbols is exactly what breaks the order. What is available is a split: the best tree over symbols i…j is a split point k, plus the best trees over i…k and k+1…j, and every symbol below a split pays one more bit.

That recurrence is a dynamic program over intervals, cubic in the alphabet with the plain form. At 90 symbols it is 121,485 steps; at 104 symbols, 187,460. Both are nothing, and both are measured rather than assumed — the step count is returned by the construction so that “cheap at this alphabet” is a number rather than a feeling.

Hu and Tucker’s construction finds the same optimum in n log n and is not built here, in the same way this collection names every structure it prices without implementing.

What order costs

Three shapes, and only two of them can be walkedA wavelet tree over 16,384 characters of english text, built three ways. Huffman's tree is the smallest — it is the smallest tree of any shape, which is what Huffman's construction means — and its leaves are in frequency order, so "everything to the left is smaller" is false in it and the compound walk returns a number with no meaning. The best tree whose leaves ARE in order costs 1.43% more than Huffman's and 20.29% less than a fixed-length code. That is the price of the operation, and it is the whole price.a fixed-length code97,718 bits5.00 ranks · in orderthe best ordered tree77,890 bits3.98 ranks · in orderthe best tree of any shape76,789 bits3.92 ranks · unorderedσ 21 · the ordered tree is 1.43% above the unordered optimum16,384 characters of englishorder costs 1.43%
Fig. 2 The three shapes on sixteen thousand characters of English: a fixed-length code, the best ordered tree, and the best tree of any shape.

On generated English at 21 symbols: Huffman 76,789 bits, the ordered optimum 77,890, a fixed-length code 97,718.

Order costs 1.43%. A fixed-length code costs 27%.

On real text the alphabet is larger and the distribution more skewed, so the numbers move — and they move in the direction that matters, which is up:

Twelve real essays, 90 symbols: Huffman 352,475, ordered 369,940 — order costs 4.95%.

Eight source modules, 104 symbols: 386,772 against 400,185 — 3.47%.

Ten revisions of one file, 79 symbols: 355,886 against 368,600 — 3.57%.

So the price of the compound operation is between one and five per cent of the wavelet tree, and the wavelet tree is about half of an FM-index. Call it one to two and a half per cent of an index.

And what it saves in query depth

The size is only half of what a tree’s shape decides. The other half is how deep the average symbol’s leaf is, which is how many bit-vector ranks an access costs.

A fixed-length code: 5.00 ranks an access on 21 symbols, 7.00 on 90.

Huffman’s tree: 3.92 and 4.49 — because the mean depth of a Huffman tree is the entropy, which is the tidiest result in this collection’s index strand and the reason a Huffman-shaped tree is used at all.

The ordered optimum: 3.98 and 4.72.

The ordered tree keeps almost all of the depth saving. Against a fixed-length code it is 33% shallower on real prose; against Huffman it is 5% deeper. So choosing it costs a few per cent of bits and a few per cent of query depth, and buys the operation that removes a factor of fourteen from an extension.

Assigning the code words

There is a step between the lengths and the tree that is easy to get wrong, and getting it wrong produces a tree that is the right size and the wrong order.

Given lengths satisfying Kraft’s equality, the canonical assignment hands out code words in increasing numeric order — and canonical Huffman sorts the symbols by length first, which is what makes its code words compact and is exactly what destroys the order this page needs.

The assignment for an alphabetic code takes the symbols in alphabet order instead, and shifts in both directions: the lengths of an alphabetic code do not increase along the alphabet, because a frequent symbol can sit between two rare ones.

That two-directional shift is the whole difference, and it has a trap in it worth recording: a left shift by a negative amount, in this language, shifts by thirty-two minus it and returns a plausible wrong number rather than failing. The first version of the assignment did exactly that, and it produced a “tree” that was 2.38 times Huffman’s on source code — a number large enough to notice, which is the only reason it was caught rather than shipped.

The check standing on it now takes the lengths of an optimal alphabetic code, hands them out canonically, and requires the result not to be in order: 17 of 21 code words move, and a construction that produced the same words either way would mean the check was testing nothing.

Why the constraint is not obviously worth it

Stated as a trade it sounds one-sided — a few per cent of bits for a factor of fourteen in operations — and it is worth being careful, because the two are not the same kind of quantity.

The bits are permanent. An index is built once and held; a few per cent larger is a few per cent larger for its whole life, on every machine that loads it.

The operations are per query, and only per bidirectional query. An index used for plain backward search never performs an extension of the kind this operation accelerates, so it pays the size and gets nothing. An index used for approximate search under a covering scheme performs thousands per pattern and gets the factor.

So the answer depends on the workload, which is the honest form of nearly every trade here: the cost model is an input, and the input is what the index is for. What the measurement does is give both numbers, so that the decision is arithmetic rather than taste.

What the operation saves, and what the shape costsBoth currencies on one frame, against the alphabet. The rising line is the factor by which the compound walk cuts the ranks an extension performs — 14.0 at σ 26 — and the flat one along the bottom is what the ordered tree costs in bits, in per cent, on the same texts: never more than 0.17%. On a uniform alphabet the two trees are the same tree and the cost is zero; on real text with a skewed distribution it is a few per cent, which is the number the plate about shapes draws.051015510152025symbols in the alphabetfactor saved, and per cent paidranks saved, as a factorbits paid, per cent8,192 characters · 2 errorsx14.0 for 0.04%
Fig. 3 Both quantities on one frame, against the alphabet: the factor saved in operations, and the per cent paid in bits.

When the constraint is free

There is one case where the whole page is unnecessary, and it is the case this collection measured first.

On a uniform alphabet — DNA, or any text whose symbols are equally frequent — Huffman’s tree is the balanced tree, its leaves are in whatever order the tie-breaking produces, and an ordered tree of the same shape has the same size. Order costs exactly 0.00%.

That is why the compound operation never came up while the strand was measuring DNA. At four equally frequent symbols there is no size argument for a Huffman shape, no ordering to lose, and a loop over four symbols is cheap enough that nobody counts it.

Every part of this page’s difficulty appears together: a large alphabet, a skewed distribution, and a search that performs thousands of extensions. That is English, or source code, or any real text.

Three shapes, and only two of them can be walkedA wavelet tree over 16,384 characters of dna text, built three ways. Huffman's tree is the smallest — it is the smallest tree of any shape, which is what Huffman's construction means — and its leaves are in frequency order, so "everything to the left is smaller" is false in it and the compound walk returns a number with no meaning. The best tree whose leaves ARE in order costs 0.00% more than Huffman's and 0.00% less than a fixed-length code. That is the price of the operation, and it is the whole price.a fixed-length code38,920 bits2.00 ranks · in orderthe best ordered tree38,920 bits2.00 ranks · in orderthe best tree of any shape38,920 bits2.00 ranks · in orderσ 4 · the ordered tree is 0.00% above the unordered optimum16,384 characters of dnaorder costs 0.00%
Fig. 4 The uniform case, where all three shapes are the same tree and the whole question does not arise.
The factor the extension was paying, and what it isBit-vector ranks per interval extension, against the size of the alphabet, for the loop over every smaller symbol and for one compound walk. The loop's line rises with σ because it is σ walks; the compound line rises with log σ, because it is one walk down a tree that deep. At twenty-six symbols that is 139.7 operations against 10.0 — a factor of 14.0, on a search that performs 1,614 extensions. The two structures find the same rows at every point on this plate; only the arithmetic between them changed.1010100symbols in the alphabetranks an extension918110140a rank per smaller symbolone walk8,192 characters · 2 errorsx14.0 at σ 26
Fig. 5 And the reason to accept the constraint anyway: the operation count it makes available, against the alphabet.

Where the dynamic program comes from

The recurrence deserves a paragraph, because it is the reason this is a different algorithm rather than a variation on Huffman.

Huffman builds bottom-up: take the two lightest weights, merge them into a node whose weight is their sum, repeat. The merge is a choice of which two, and it is unconstrained — the two lightest can be anywhere in the alphabet.

An alphabetic tree cannot merge non-neighbours, because the leaves must stay in order. So the natural formulation is top-down: choose where the root splits the alphabet, and every symbol below that split pays one more bit. The best tree over symbols i…j is then the best split k plus the best trees on either side, and the cost of a subtree is its own weighted path length plus the total weight beneath it — the second term being the extra bit every symbol under the split pays.

That is an interval dynamic program of exactly the shape used for optimal binary search trees and for matrix-chain multiplication, and it is cubic without Knuth’s monotonicity optimisation. At the alphabets this field runs on — under 128 symbols — cubic is 350,000 steps, which is nothing, and the construction reports its own step count so that “nothing” is a number.

Three shapes, and only two of them can be walkedA wavelet tree over 16,384 characters of repetitive text, built three ways. Huffman's tree is the smallest — it is the smallest tree of any shape, which is what Huffman's construction means — and its leaves are in frequency order, so "everything to the left is smaller" is false in it and the compound walk returns a number with no meaning. The best tree whose leaves ARE in order costs 1.25% more than Huffman's and 22.79% less than a fixed-length code. That is the price of the operation, and it is the whole price.a fixed-length code97,710 bits5.00 ranks · in orderthe best ordered tree75,439 bits3.86 ranks · in orderthe best tree of any shape74,509 bits3.81 ranks · unorderedσ 20 · the ordered tree is 1.25% above the unordered optimum16,384 characters of repetitiveorder costs 1.25%
Fig. 6 The three shapes on a third kind of text, where the alphabet is twenty and the distribution is between English’s and DNA’s.

What the sizes mean in an index

Bits in a wavelet tree are not bits in an index, and the conversion is worth doing once.

The tree is one part of an FM-index. On the measurements here it is 18,377 bits of 35,305 at a four-symbol alphabet — about half — and a larger share at a larger alphabet, because the other parts do not grow with σ.

So an ordered tree costing 4.95% more on real prose makes the tree 4.95% larger and the index about 2.5% larger. A bidirectional index is two of them, so 2.5% again.

Against that, a factor of fourteen, for four per cent is the operation count the ordering buys. Both numbers are on one plate there, which is the only way this collection is willing to report a trade.

What Hu and Tucker did

Named rather than built, and worth knowing what is being skipped.

The dynamic program above finds the optimal alphabetic tree in cubic time. Hu and Tucker’s algorithm finds the same optimum in n log n by a construction that is genuinely surprising: it merges pairs the way Huffman does, but only pairs that are tentatively adjacent in a sequence that ignores already-merged internal nodes — and then, having found the code lengths, it rebuilds the tree from the lengths alone in alphabet order.

The second half is the part this page uses anyway: an alphabetic code is determined by its lengths, and the assignment from lengths to code words in alphabet order is what orderedCodesFromLengths does. So the two constructions differ only in how the lengths are found.

At σ = 128 the cubic version is 350,000 steps and Hu–Tucker is about 900. Both are instant, and the reason to name the second is that this field’s alphabets are small and somebody else’s are not: a wavelet tree over words rather than characters has an alphabet of a hundred thousand, and cubic is then a million million.

Where each symbol's leaf actually sitsThe first 12 symbols of the alphabet, placed at the position their code word puts them in — reading a code as a binary fraction is reading the path to its leaf, so this is the tree's bottom row. In the top two rows the symbols run left to right in their own order, which is what makes "everything that went left is smaller" true. In Huffman's tree they do not, because the construction merges the two lightest weights wherever they are. A compound walk in that tree still runs and still returns a count; the count is of whatever happens to be to the left.a fixed-length code — leaves in order 0b2d4f6h8l10the best ordered tree — leaves in order 0a1b2e5f6i9l10the best tree of any shape — leaves in frequency order 0a1e5d4h8m11c3b2the number under each symbol is its place in the alphabet4,096 characters of repetitive12 symbols drawn
Fig. 7 The property the whole construction exists to preserve, on a third text: leaves in alphabet order in the first two rows and not in the third.

The trap in the assignment, in full

Worth recording properly, because it is the kind of defect that ships.

Canonical code assignment walks the symbols in some order and computes each code word from the previous one: add one, then shift left by the difference in lengths. That works when the lengths are non-decreasing along the walk, which is exactly what sorting by length guarantees — and canonical Huffman sorts by length.

An alphabetic code’s lengths are not monotone along the alphabet. A frequent letter sits between two rare ones and has a shorter code than both, so the walk sometimes shifts left and sometimes right.

Written as a single left shift by a possibly negative amount, this language evaluates x << -2 as x << 30, which is a plausible number rather than an error. The result was a code that satisfied Kraft’s inequality, produced a tree, and measured 2.38 times Huffman’s size on source code — large enough to notice only because a plate drew it beside two other shapes.

The fix is three lines and the check is one: hand the same lengths out in canonical order and require the result not to be in alphabet order. Seventeen of twenty-one code words move, and a construction that produced the same words either way would mean the check was testing nothing.

Three shapes, and only two of them can be walkedA wavelet tree over 32,768 characters of english text, built three ways. Huffman's tree is the smallest — it is the smallest tree of any shape, which is what Huffman's construction means — and its leaves are in frequency order, so "everything to the left is smaller" is false in it and the compound walk returns a number with no meaning. The best tree whose leaves ARE in order costs 1.47% more than Huffman's and 20.44% less than a fixed-length code. That is the price of the operation, and it is the whole price.a fixed-length code194,920 bits5.00 ranks · in orderthe best ordered tree155,077 bits3.98 ranks · in orderthe best tree of any shape152,829 bits3.92 ranks · unorderedσ 21 · the ordered tree is 1.47% above the unordered optimum32,768 characters of englishorder costs 1.47%
Fig. 8 The three shapes again at twice the size, where the ordered tree’s cost relative to Huffman’s is stable and the fixed-length code’s is not.

Where this sits in the strand

Every child at once is the operation this tree exists to support, and the count that was already there is why it costs nothing in operations. This page is the price in bits, and a factor of fourteen, for four per cent puts the two together.

The shape being given up is the one a floor on the bits is about — Huffman’s tree, whose mean depth is the text’s zeroth-order entropy — and the structure all of it lives in is the index that is smaller than the text.

What an alphabetic code is worth outside this

A last note, because optimal alphabetic codes have a life of their own and this page uses them for one purpose.

They are the right code whenever the order of the symbols carries meaning that has to survive the encoding: search trees over ordered keys, where a comparison has to be answerable from the code; range queries over an alphabet, where “everything below c” has to be a subtree; and any structure that wants to be both a code and an index, which is exactly what a wavelet tree is.

Huffman’s code is better where order does not matter, which is nearly everywhere a code is used for compression alone. So the two constructions are not competitors: they answer different questions, and this collection needed the second only when it wanted a tree to be searched as well as stored.

That is a tidy way to hold the whole page. A wavelet tree is a code being used as an index, and the moment it is used as an index, the order of its leaves stops being an implementation detail.

The three shapes, as a decision

Put plainly, since the page has three constructions in it and a reader has to choose one.

Use a Huffman-shaped tree when the index will only ever be searched in one direction. It is the smallest and the shallowest, and the compound operation is not needed — rank is the only thing it does is the whole requirement, and a plain backward search never asks for a smaller-symbol count.

Use the optimal alphabetic tree when the index supports bidirectional search. It costs three to five per cent more on real text, keeps most of the depth saving, and makes every child at once available — which is a factor of fourteen on the operation that dominates an approximate search.

Use a fixed-length code when the alphabet is uniform, or when the construction has to be trivial. On uniform text it is the same tree as the other two; on skewed text it costs a quarter more in bits and half again in depth, and a factor of fourteen, for four per cent is where that comparison is drawn.

The middle option is the one this collection now builds, and it is worth noticing that it did not exist here until an operation demanded it. A constraint arriving from the query side, changing which code a structure is built with, is the tidiest example this collection has of the structure decides the count running in the other direction.

Which collapses to one question, and it is worth saying so because a three-way decision reads as harder than it is.

The two costs are wildly asymmetric. The middle option costs three to five per cent of the bits and buys a factor of fourteen on the operation an approximate search spends most of its time in. No plausible weighting makes four per cent of a structure worth more than an order of magnitude on its dominant query, so the only thing to decide is whether that query is ever asked.

Is the index searched bidirectionally? If yes, the alphabetic tree; if no, the Huffman one; and the fixed-length code only where the construction has to be trivial or the alphabet is already uniform, in which case all three are the same tree anyway.

What this makes readable

Essays that name this one as a prerequisite.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

What links here

Every essay whose body links to this one.

The objects this essay names

Each one links to every other essay that touches it.

AlphabetAlphabetic codeCode lengthCompound operationDynamic programmingEntropyHuffman codeIndex sizeTradeTree shapeWavelet tree