← 回首頁

區間查詢(Range Query)— 線段樹 / Fenwick(BIT) / 稀疏表

想像你有一長排每天的營業額,老闆動不動就問「第 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),方便直接對照三者處理同一份資料的方式差異。

1. 線段樹(Segment Tree)— 建樹、區間求和查詢與單點更新

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 改值)就清空重建整棵樹(節點值變了,佈局座標本身不變但需要重新讀值),相同(單純比較中尚未寫值) 則只更新高亮,不重建(避免不必要的重繪)。

固定示範腳本(共用 BASE_ARR,n=8):build() → query(2,5)(=20)→ update(3,8) → query(2,5)(=27)。

操作腳本

    節點訪問次數(visits):0
    紅色(current)= 當前處理的節點;綠色(visited)= 本次操作(單一 build/query/update)已走過的節點路徑; 黃框(frontier)= query 完全覆蓋、直接貢獻到 sum 的命中節點;節點上方文字 = 該節點對應的區間 [start,end]。

    查詢結果

    (尚無查詢結果)

    已執行操作紀錄

    2. Fenwick 樹(Binary Indexed Tree)— 單點更新與前綴和查詢

    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(對齊佔位,非資料)

    固定示範腳本(共用 BASE_ARR,n=8):buildAll() → prefixSum(5)(=27)→ rangeSum(2,5)(=20)→ update(3,+7)(=delta) → prefixSum(5)(=34)。

    操作腳本

      lowbit 跳躍次數(hops):0
      索引 1..n 為實際資料槽,索引 0 恆顯示高度 0 的柱(對齊佔位,非資料,因 BarsRenderer 逐槽畫柱不省略); 橘色(checking)= 本次 update/prefixSum 沿 lowbit 路徑累積走過的索引;藍框(cursor)= 當前正在處理的單一索引。
      lowbit 明細:(無) 目前累加和(sumSoFar):(無)

      查詢結果

      (尚無查詢結果)

      已執行操作紀錄

      3. 稀疏表(Sparse Table)— 倍增建表與 O(1) 區間最小值查詢

      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)的格顯示空白。

      固定示範腳本(共用 BASE_ARR,n=8):build() → queryMin(1,6)(=1)→ queryMin(2,5)(=1)→ queryMin(4,7)(=3)。

      操作腳本

        已寫入格數(cells):0
        列(由上到下)= k 層,代表區間長度 2^k;欄 = 原始陣列索引 0..7。藍框(active)= 本步驟參與的來源格 (build 合併時為兩個 k-1 來源格 + 本格;query 時為左右兩個重疊窗格);黃框(pivot)= build 階段當前寫入格。
        查詢公式(formula):(無)

        查詢結果

        (尚無查詢結果)

        已執行操作紀錄