V

算法可视化

ZHEN
分享到微博分享到 XGitHub 仓库个人主页
学习工具算法复杂度速查算法学习路径
数据结构数组链表栈队列树堆哈希表图字典树并查集LRU 缓存跳表线段树B+ 树布隆过滤器树状数组
经典排序算法冒泡排序鸡尾酒排序双调排序选择排序插入排序二分插入排序希尔排序归并排序自顶向下归并快速排序三路快排双轴快排堆排序计数排序基数排序桶排序
图算法Dijkstra 最短路Kruskal 最小生成树Prim 最小生成树Bellman-Ford 最短路拓扑排序Floyd 多源最短路强连通分量2-SAT最大流二分图匹配LCA 倍增欧拉路径
动态规划编辑距离0-1 背包完全背包最长公共子序列最长递增子序列硬币找零方案数石子合并旅行商 TSP树形 DP数位 DP换根 DP
回溯与搜索N 皇后子集生成全排列组合总和迷宫寻路岛屿数量单词搜索数独A* 寻路
字符串KMP 字符串匹配Rabin-KarpBoyer-MooreManacher后缀数组LCP / height 数组AC 自动机Z 函数
数学与数论埃氏筛线性筛欧几里得算法快速幂扩展欧几里得中国剩余定理欧拉函数米勒-拉宾FFTPollard's Rho
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

树状数组(Fenwick / BIT)

数据结构 · lowbit 链 · 改查双 O(log n)

改与查的两难

要频繁地「单点加」和「求前缀和」:普通数组改 O(1) 查 O(n);前缀和数组查 O(1) 改 O(n)——两头总有一头是 O(n)。树状数组用一行位运算魔法两头兼得:lowbit(i) = i & -i(取出 i 二进制的最低位 1)。

管辖区间:每个格子管一段

tree[i] 存「以 i 结尾、长度 lowbit(i)」那段的和:tree[4](100₂,lowbit=4)管 a₁..a₄,tree[6](110₂,lowbit=2)只管 a₅+a₆。query(6):沿 i -= lowbit(i) 往前跳(6 → 4 → 0),两块拼出前缀和;update(3, +2):沿 i += lowbit(i) 往后跳(3 → 4 → 8),把每个「管到 a₃」的区段都通知一遍——注意柱子当场长高。跳几步?i 的二进制位数——O(log n)。右侧代码随每一步同步高亮。

3
5
5
11
4
6
3
21

8 根柱子是 tree[1..8],不是原数组:tree[i] 管辖「以 i 结尾、长 lowbit(i) = i & -i」的区段和——tree[4]=11 管 a₁..a₄、tree[6]=6 只管 a₅+a₆、tree[8]=21 管全部。两种操作都只沿 lowbit 链跳 O(log n) 步

1class BIT {
2 tree: number[]; // tree[i] 管辖长 lowbit(i) 的区段和
3 constructor(n: number) { this.tree = new Array(n + 1).fill(0); }
4 lowbit(i: number) { return i & -i; } // 最低位的 1
5 query(i: number): number { // 前缀和 a[1..i]
6 let s = 0;
7 for (; i > 0; i -= this.lowbit(i)) // 沿链往前跳
8 s += this.tree[i];
9 return s;
10 }
11 update(i: number, d: number) { // a[i] += d
12 for (; i < this.tree.length; i += this.lowbit(i))
13 this.tree[i] += d; // 通知每个管辖者
14 }
15}
原数组 a[3, 2, 5, 1, 4, 2, 3, 1]
读法tree[i] 管辖长 lowbit(i)
1 / 9

十行代码的性价比

三方案对比:数组(改 O(1)/查 O(n))、前缀和(改 O(n)/查 O(1))、BIT(双 O(log n))。
lowbit 直觉:i 的二进制每去掉一个最低位 1,就吞并一段更靠前的区间。
区间和:query(r) − query(l−1);区间加单点查用差分 BIT。
经典应用:逆序对计数、动态第 k 小(配倍增)、滑动统计。

与线段树相比:线段树全能(区间改/区间查/最值),BIT 只做前缀可加信息——但十行代码、常数小一半,覆盖日常 90% 的场景。竞赛里「能 BIT 不线段树」是常识;这也是数据结构大类的第 16 块拼图:数组结构从静态查询走到了动态统计。