Fenwick & Segment Trees
A prefix-sum array answers a range sum in but pays per update; a plain array updates in but pays per range sum. Fenwick and segment trees give us both in .
╌╌╌╌
We have an array and two operations we want to interleave freely: update a single entry, and ask for the sum of a contiguous range . The two obvious data structures each ace one operation and fail the other. Keep as is and an update is a single write in , but a range sum scans the range in . Precompute a prefix-sum array and a range sum collapses to in , but now a single update to disturbs every with , an repair. We want a structure that splits the difference and does both in .
The idea, in the spirit of the previous lessons on augmenting trees with subtree summaries,1 is to store partial sums over blocks so that any prefix is the sum of a few blocks and any single element lives in only a few blocks. Two classic structures realize this: the Fenwick tree, which is compact and exploits the binary representation of the index, and the segment tree, which is more general and handles any associative aggregate plus range updates.
Fenwick trees: indexing by the low bit
A Fenwick tree (or binary indexed tree) is a 1-indexed array where stores the sum of a contiguous block of ending at index . The length of that block is , the value of the lowest set bit of :
That is, covers the half-open range . This relies on two's-complement arithmetic: is flip every bit of , then add one. Trace it for in five bits:
Why does this always isolate the lowest set bit? Write as some prefix of bits, then the lowest , then a run of trailing zeros: . Flipping gives , and adding carries through the trailing ones and stops at the flipped : . Below the lowest set bit both numbers are all zeros; at it both have a ; above it every bit of is the complement of the corresponding bit of . The AND therefore keeps exactly one bit — the lowest set bit. Concretely: since , so covers indices ; since , so covers the whole prefix ; and for every odd , so odd entries cover just themselves.
Here is the whole structure for the running array . Each bracket is one Fenwick entry, storing the sum of the cells it spans:
Reading the brackets off into an array: . The entry ; the entry . Nothing else is stored — the structure is this one array.
Prefix sum. To compute we peel off blocks from the right. accounts for the topmost block ending at ; the rest of the prefix ends at , so we jump there and repeat, clearing one set bit each step until we reach .
- 1
- 2while do
- 3
- 4clear the lowest set bit
- 5return
Trace it on the running array, , for :
| step | (binary) | read | running | next |
|---|---|---|---|---|
| 1 | ||||
| 2 | ||||
| 3 |
The loop stops at and returns ; a direct check gives . Each subtraction clears the lowest set bit of — — and each cleared bit contributed one block: covers , covers , covers . The three blocks tile with no gaps and no overlaps.
That tiling is not an accident of . The binary expansion of any index is a sum of powers of two, and the walk peels those powers off from the smallest up. For a larger index like , the visit sequence is :
Point update. When changes by , every whose block contains must change by . Repeatedly adding the low bit visits those indices and no others: starting at , each step moves to the next larger block that covers , until we run past .
- 1while do
- 2
- 3move to the next covering block
Trace — add to — on the running array:
| step | (binary) | write | next |
|---|---|---|---|
| 1 | |||
| 2 | |||
| 3 | , stop |
Exactly the right entries changed: covers alone, covers , and covers — every block that contains index , and no other. covers only , so the walk correctly skips it. A follow-up now reads , as it must.
The two walks are mirror images on the same number line. climbs to larger indices by adding the low bit, visiting every block that owns the changed element; descends to smaller indices by subtracting it, peeling off the blocks that tile the prefix. The same hop drives both, in opposite directions.
A range sum is then two prefix queries:
so both update and range sum cost , with occupying a single array of words and no pointers.2 Building takes by a linear in-place pass described below, rather than separate updates.
The implicit tree
The array is a flattened forest, and seeing the tree explains both walks. Define . Under this map every index at most hangs below a power of two, and each node's block is the disjoint union of its own array cell and its children's blocks:
Check it at : the children of are (since ), (since ), and (since ), and indeed
The two operations are just the two natural walks in this forest. follows parent pointers from toward the root — the leaf-to-root path — which, by the correctness lemma, is the set of blocks containing . hops across the forest from one subtree root to the next one on its left; each hop discards a fully-counted subtree. Since always has a strictly larger low bit, no root-ward path is longer than the number of bit positions, — the tree is implicitly balanced, with no rotations, no pointers, and no bookkeeping beyond the index arithmetic itself.
The parent map also gives the construction promised above in one left-to-right pass: initialize , then for add into if that parent exists. By the time the loop reaches , every descendant of has already deposited its sum, so each entry is finished exactly when it is passed along — additions total, versus for separate calls to .
The one catch: this works because sums are invertible — we recover by subtracting two prefixes. For non-invertible aggregates like or , is meaningless, and we need a structure that queries an arbitrary range directly. That is the segment tree.
Segment trees: a balanced tree of canonical ranges
A segment tree over is a balanced binary tree whose leaves are the array entries and whose every internal node stores the aggregate of the contiguous range its subtree spans. The root covers ; a node covering with splits at into children covering and . The stored aggregate can be sum, , , or : any associative operation (formally, any monoid), since a node's value is its two children's values combined.
Query. To aggregate we descend from the root. At a node covering : if lies entirely inside we return its stored value without recursing (a canonical node); if it is disjoint from we return the monoid identity; otherwise we recurse into both children and combine. The query range decomposes into canonical nodes, at most two per level of the tree, so a range query costs . In the figure, is covered by the three shaded nodes , , , and their union is exactly .
A worked query
Build the tree over the running array bottom-up: the leaves take 's values, and each internal node sums its children — , , , , then and , and the root . Now run and record every node the recursion touches:
| node | value | relation to | action |
|---|---|---|---|
| straddles | recurse into both children | ||
| straddles | recurse into both children | ||
| straddles | recurse into both children | ||
| disjoint | return | ||
| inside | return (canonical) | ||
| inside | return (canonical) | ||
| straddles | recurse into both children | ||
| inside | return (canonical) | ||
| disjoint | return |
The answer is , and directly: . The recursion visited of the tree's nodes; on a larger tree the proportion collapses, since only the two root-to-endpoint paths are ever explored.
Point update. To change , update the corresponding leaf and walk back up to the root, recomputing each ancestor as the combination of its (now-updated) children — one node per level, work. Setting (it was ) in the tree above rewrites exactly one root-to-leaf path: the leaf becomes , then , then , then the root . Four writes, no other node consulted. Building the tree bottom-up visits each of the nodes once, so construction is , and the tree needs at most (commonly allocated as ) nodes, roughly to a Fenwick tree's memory.
Lazy propagation: range updates in
A point-update segment tree still pays to add a value to a whole range element by element. Lazy propagation fixes this. When an update applies to a range that exactly covers a node's interval, we apply it to that node's aggregate and stash a pending tag on the node instead of recursing into its children. The tag is pushed down to the children only later, lazily, when a subsequent query or update actually needs to enter that subtree.
A worked range update
Run on the original tree for . The range decomposes into the canonical nodes and — the same decomposition a query would compute. At each canonical node we apply the update to the stored sum in (a over a node covering cells adds ) and record the tag:
- : sum , tag ;
- : sum , tag ;
- on the way back up, recompute the ancestors: and the root .
Six nodes touched in total; the ten nodes below the two tags still hold their old sums. They are stale, but harmlessly so — the tags above them record the correction, and no read can reach a stale node without first passing a tag.
Now query against this state. The recursion enters the root and must descend past the tagged node , because straddles . Before recursing, it pushes the tag down: gets sum and tag ; gets sum and tag ; the tag on is cleared. The query then proceeds normally — is inside and returns ; on the right, is inside and returns without touching the tag below it. The answer is , which checks out against the updated array : .
Two details make the scheme correct in general. First, tags must compose: two pending and tags on the same node collapse to , so a node never holds more than one tag. Second, a node's stored aggregate is always correct for its own subtree assuming all tags strictly above it have been applied — that is the invariant the push-down preserves, and it is what lets a canonical node answer a query without any descent.
With lazy tags both range update and range query run in . This is the segment tree's decisive advantage over the Fenwick tree: it supports non-invertible aggregates (, ) and whole-range modifications, at the cost of more memory and a more involved implementation.3
Choosing between them
Both give point-update and range-query; the choice is about generality versus footprint.
- Fenwick tree. Pick it when the aggregate is an invertible group operation (sum, xor) and you only need point updates. It is a single array, cache-friendly, a dozen lines of code, and the constant factors are tiny. Range sum is .
- Segment tree. Pick it when you need or any non-invertible aggregate, or range updates via lazy propagation. It is strictly more general, and you pay for it with to the memory and a more involved implementation.
One extension stretches the Fenwick tree further than it first appears. To support range update + point query for sums, keep a Fenwick tree over the difference array : adding to becomes two point updates (, ), and reading becomes . With a second Fenwick tree tracking a correction term, even range update + range sum works. What no Fenwick variant recovers is a non-invertible aggregate — a range cannot be assembled from prefix information, because has no inverse to subtract with.
| workload | structure |
|---|---|
| point update, range sum / xor | Fenwick tree |
| range add, point read | Fenwick tree over the difference array |
| point update, range | segment tree |
| range update, range query | segment tree with lazy propagation |
In short: Fenwick is the specialist, the segment tree the
generalist. For range-sum-query-mutable, a Fenwick tree
suffices; when the skyline or a range-assign problem demands over a
mutable range, use the segment tree with lazy propagation.
The segment tree's larger family
Neither structure is in the classic textbooks; they come from competitive programming and the systems literature, and both extend into a large family of range-query structures.
Persistence and offline queries. Because a point update touches only the nodes on one root-to-leaf path, a segment tree is naturally made persistent by path-copying (the same trick that persists a balanced BST): each update spawns a new version in extra space, and old versions stay queryable. A persistent segment tree answers offline questions like "the -th smallest value in the subarray " by querying the difference of two versions, a standard tool for range-rank queries.
When per side isn't enough. For simpler needs, sqrt decomposition splits the array into blocks and answers range queries in with almost no code, sometimes the pragmatic choice for non-associative or awkward aggregates. At the other extreme, segment tree beats (Ji Ruyi's technique) supports range operations like "clamp every element to at most " in amortized , which no lazy tag alone can do, by storing the two largest distinct values per node and pruning branches where the update is a no-op.
Higher dimensions and richer keys. The 2-D Fenwick tree in this lesson generalizes to a Fenwick tree of Fenwick trees for rectangle sums, and a merge-sort tree (a segment tree whose nodes store sorted subarrays) answers "how many values in are " in . The common thread: any aggregate you can compute from two children in slots into the segment tree's divide-and-combine skeleton.4
Takeaways
- A static prefix-sum array answers range sums in but updates in ; a plain array updates in but sums in . Fenwick and segment trees achieve both in .
- A Fenwick tree is a 1-indexed array where holds the sum of the block , with . Prefix sum walks down clearing low bits; update walks up adding low bits, each .
- Fenwick range sum relies on invertibility: . It fails for .
- A segment tree stores each node's range aggregate (any associative op). Build , point update , and range query by decomposing into canonical nodes.
- Lazy propagation defers a range update by tagging canonical nodes and pushing tags down only when needed, giving range update + range query.
- Fenwick = tiny, fast, sum-like invertible aggregates; segment tree = general and lazy range ops, at to the memory.
Footnotes
- CLRS, Ch. 14, Augmenting Data Structures (§14.2): attach summary fields to nodes and maintain them through updates, the general method both structures specialize. ↩
- Skiena, §3.x, Range Queries / Augmented Structures: the binary indexed tree as a minimal-overhead structure for dynamic prefix sums. ↩
- Erickson, Ch., Data Structures: segment trees over canonical ranges, range decomposition, and lazy propagation for range updates. ↩
- Fenwick,
A new data structure for cumulative frequency tables
(1994), the binary indexed tree; the persistent segment tree, sqrt decomposition, segment-tree-beats, and merge-sort-tree techniques are standard in the competitive-programming literature (e.g. the CP-Algorithms references). ↩
╌╌ END ╌╌