想像你有一長排每天的營業額,老闆動不動就問「第 3 到第 6 天總共賺多少?」或「這幾天最低是哪一天?」。 每次都從頭一個個加太慢,這頁的三種結構就是專門幫你「一次問一整段範圍」的加速索引:線段樹最萬用、 可以邊改資料邊查;Fenwick(BIT)程式碼最精簡、只做加總類的問題;稀疏表查詢快到 O(1), 代價是建好就不能改。取捨不同,適用場合就不同。下面你會看到三者處理「同一份資料」的方式差在哪。
三種資料結構都能回答「區間 [l, r] 的和/最小值是多少?」,但取捨完全不同:線段樹用遞迴分治換來
「查詢 + 任意單點更新」皆 O(log n)、最通用;Fenwick(BIT) 只靠 lowbit(x&-x) 沿二進位路徑跳躍,
功能限於可差量表示的操作(如 sum),但程式碼更精簡、空間常數更小;稀疏表用 O(n log n) 預處理換來
查詢 O(1),代價是不可更新、且僅限冪等操作(min/max/gcd,重疊窗口不影響結果)。三個 section 共用同一份
固定資料 BASE_ARR = [5, 2, 7, 1, 9, 3, 6, 4](n=8),方便直接對照三者處理同一份資料的方式差異。
build(node,start,end) 遞迴後序:葉節點 tree[node]=arr[start],非葉遞迴左右子後 tree[node]=左+右。
query(node,start,end,l,r) 三分支:不相交回傳 0、完全覆蓋([l,r] 完整包住 [start,end])直接回傳 tree[node]、
部分重疊遞迴左右子相加。update(node,start,end,index,diff) 沿 root→葉的路徑,每個經過節點 tree[node]+=diff。
本頁樹狀渲染(TreeRenderer)每幀比較 JSON.stringify(state.tree) 與上一幀快照:不同(build 填值
或 update 改值)就清空重建整棵樹(節點值變了,佈局座標本身不變但需要重新讀值),相同(單純比較中尚未寫值)
則只更新高亮,不重建(避免不必要的重繪)。
update(index,delta) 轉 1-indexed 後,沿 index += lowbit(index)(lowbit(x)=x&-x)往上跳,
每個經過的索引 tree[index]+=delta,直到 index>n。prefixSum(index) 轉 1-indexed 後,沿
index -= lowbit(index) 往前跳,每個經過的索引累加 sum+=tree[index],直到 index=0。
rangeSum(left,right) 借用兩次 prefixSum 相減(left==0 時直接回傳 prefixSum(right))。內部 tree 陣列維持
1-indexed 慣例,長度 n+1、tree[0] 恆為 0(對齊佔位,非資料)。
build 先算 k=0(長度 1)基底:minTable[i][0]=arr[i];之後 k=1..maxK 遞推合併: minTable[i][k]=min(minTable[i][k-1], minTable[i+2^(k-1)][k-1])。queryMin(left,right) 取 k=log2(right-left+1),用兩個長度 2^k、起點分別為 left 與 right-2^k+1 的區間(可能重疊)覆蓋 [left,right], 回傳 min(minTable[left][k], minTable[right-2^k+1][k])——因為 min 是冪等操作,重疊部分不影響結果,才能不必 像 sum 那樣切成互斥區段,直接 O(1) 查詢。下方格線圖由上到下為 k=0..3 層(第 k 列每格代表長度 2^k 的區間), 由左到右為原始陣列索引 0..7;越界(i+2^k-1 ≥ n)的格顯示空白。