Fix maximum Fenwick tree range values after updates - #15485
Conversation
|
Please add one or more tests (without removing or modifying any existing tests) that fail with the current algorithm but pass with the proposed algorithm. Please add a timeit or similar benchmark that measures the performance difference on a large tree (like 2k items). |
Preserve all existing doctests while covering lost range maxima and decreases. Add an optional timeit benchmark for update and query workloads, as requested in TheAlgorithms#15485.
|
Implemented in Added 12 doctest examples covering preserved sibling maxima, decreasing/resetting the maximum, and the ancestor's own value. All 21 existing examples are unchanged. The original algorithm fails the four new result assertions; the proposed version passes all 33 examples. The same file now has an optional benchmark: python3.15t data_structures/binary_tree/maximum_fenwick_tree.py --benchmarkUsing the identical benchmark block with the original algorithm from
Updates are about 6.41× slower because they now recompute correct range maxima. The original update workload leaves 428 of 2,000 suffix-query results incorrect; the proposed version leaves zero. Query code is unchanged; the small observed timing difference does not establish a query-performance change. Setup and correctness checks are outside timing; every update repetition starts with a fresh zero tree, and query repetitions use identical correct buckets prepared outside timing. All applicable Reproduce the original-algorithm benchmark with the identical harnessFrom the repository root, export the original class with the new benchmark block: from pathlib import Path
from subprocess import check_output
path = "data_structures/binary_tree/maximum_fenwick_tree.py"
base = "84b73d08f8bfa4e6bfae0243369c24f5a7539745"
original = check_output(["git", "show", f"{base}:{path}"], text=True)
proposed = Path(path).read_text()
marker = 'if __name__ == "__main__":'
Path("/tmp/maximum_fenwick_tree_before.py").write_text(
original[:original.index(marker)] + proposed[proposed.index(marker):]
)taskset -c 0 nice -n 10 python3.15t /tmp/maximum_fenwick_tree_before.py --benchmark
taskset -c 0 nice -n 10 python3.15t data_structures/binary_tree/maximum_fenwick_tree.py --benchmark |
|
These multiples are too high. We will keep the current implementation until we find a more performant solution. |
Propagate only changed maxima and rebuild necessary buckets directly from disjoint child buckets. Preserve arbitrary overwrites, regression doctests and the benchmark.
|
Published in I revised the updater to avoid recomputing maxima that cannot change. Increasing a bucket maximum now assigns it directly; decreasing a previous maximum combines disjoint child Fenwick buckets. Propagation stops once a bucket maximum is unchanged. Arbitrary decreases, ties, and the existing negative-value behavior are preserved. The same 2,000-item benchmark now gives these fresh best-of-five times on CPython 3.15.0rc2 free-threaded. Each update workload contains 4,000 assignments; setup and correctness checks are outside timing.
The worst cases still cost more than the original: decreasing updates are 4.49×, and repeated unique-maximum removal 2.285×, slower than the broken updater. The worst-case bound remains O(log² N). Query code is unchanged; its small timing differences are noise. I am not claiming universal speed parity. Both correct versions have zero incorrect full-range answers, final suffix answers, or final buckets in these workloads. The original mixed workload leaves 428 incorrect suffix answers. The revision also passes all 33 unchanged doctests, the module's pytest doctest, 1,053,076 external range comparisons, 46,830 exact bucket comparisons, and every applicable configured pre-commit hook. Independent review separately checked signed overwrites and tied maxima. The existing
For those comparisons, each repetition starts from fresh, correct buckets constructed from the initial array outside timing. These remaining costs are included explicitly so you can assess whether this narrower repair meets the performance requirement. AI assistance was used for implementation and independent review. |
Describe your change
MaxFenwickTree.update()currently storesmax(value, current_left_border, index)in internal nodes. Indices become candidate values, and previous maxima are lost. For example:Performance feedback from the maintainer is addressed by
c25e1e9e3f81407e3f2205a842b0f2907aad8b94.The revised updater preserves arbitrary overwrites and decreases while limiting recomputation:
Query code and the existing zero-floor semantics for negative-only ranges are unchanged. The worst-case update bound remains O(log² N).
The maintainer requested regressions and a 2,000-item benchmark. All 21 original examples remain verbatim, with 12 previously added examples retained (33 total); the original updater fails four new result assertions. This performance revision changes only
update(), preserving all 33 examples and the complete benchmark block.Fresh best-of-five timings on CPython 3.15.0rc2 free-threaded, identical workload and setup outside timing:
The mixed workload is faster in this run, but decreases and repeated unique-maximum removal remain 4.49× and 2.285× slower than the broken original. This is not a universal speedup or performance-parity claim. The unchanged query timings reflect noise. The original mixed update workload leaves 428 wrong suffix answers; both correct versions give zero. Additional increasing, tied-maximum and unchanged-value workloads also pass, with full-range answers checked after every update and final suffix answers/buckets verified.
Validation: all 33 doctests, the targeted pytest module doctest, and every applicable configured pre-commit hook passed under CPython 3.15.0rc2 free-threaded. External regression checks passed 1,053,076 range comparisons and 46,830 exact bucket comparisons across arbitrary overwrites, ties, decreases, signed values and non-power-of-two sizes. Independent review separately verified the bucket invariants and additional signed sequences. AI assistance was used for implementation and independent review.
Checklist