樹狀陣列

小纸条發表於2024-11-29

字首和之樹狀陣列

樹狀陣列(Fenwick Tree)是一種用於高效處理區間查詢與修改的重要工具。它可以在 (O(log n)) 的時間複雜度內完成單點更新和字首區間求和的操作。

一、樹狀陣列的基本思想

樹狀陣列透過一個輔助陣列 (c[i]) 實現,將原陣列的資訊以一種特殊的方式儲存,使得查詢和更新都能快速完成。其核心思想是利用陣列的二進位制表示,按區間段儲存部分和。

例如,假設陣列下標從 (1) 開始:

  • (c[i]) 儲存的是從 (i) 減去 (lowbit(i)) 再加 (1) 到 (i) 的區間和。這裡的 (lowbit(i)) 表示 (i) 的二進位制表示中最低位的 (1) 所對應的值,其計算方式是 (lowbit(x)=x) 與 (-x) 的按位與運算結果(即 (lowbit(x)=x & (-x)))。
    image

二、樹狀陣列的結構特點

透過樹狀陣列,我們可以快速實現以下兩個操作:

(一)單點更新

更新陣列中的某個值,同時維護相關區間和。

(二)區間查詢

計算從起點到某點的字首和。

1. 示例:計算字首和

以陣列 (a[1..n]) 為例,假設使用樹狀陣列儲存資訊,我們可以用如下虛擬碼完成字首和計算:

// 查詢從索引1到指定index的字首和
    public int query(int index) {
        int res = 0;
        while (index > 0) {
            res += tree[index];
            index -= index & -index;
        }
        return res;
    }

2. 示例:單點更新

當我們想更新 (a[k]) 的值(例如加上 (y)),樹狀陣列中的相關節點也需要同步更新。具體實現如下:

// 對指定索引index處的元素進行單點更新,增加val的值
    public void add(int index, int val) {
        while (index <= n) {
            tree[index] += val;
            index += index & -index;
        }
    }

三、樹狀陣列的實際應用

樹狀陣列具有廣泛的實際應用,特別是在需要頻繁查詢和更新的場景中:

(一)區間求和問題

計算任意子區間的和。

(二)動態排序統計

例如,求解逆序對數。

(三)二維平面問題

如處理棋盤上的子矩陣求和。

程式碼實現

以下是樹狀陣列的基本實現程式碼:

public class BinaryIndexedTree {
    // 表示樹狀陣列所處理的序列長度
    private int n;
    // 樹狀陣列本身
    private int[] tree;

    // 建構函式,用於初始化樹狀陣列,這裡假設傳入的陣列長度就是要處理的長度n
    public BinaryIndexedTree(int[] arr) {
        n = arr.length;
        tree = new int[n + 1];
        // 初始化樹狀陣列,可根據具體需求調整初始化邏輯
        for (int i = 0; i < n; i++) {
            add(i + 1, arr[i]);
        }
    }

    // 查詢從索引1到指定index的字首和
    public int query(int index) {
        int res = 0;
        while (index > 0) {
            res += tree[index];
            index -= index & -index;
        }
        return res;
    }

    // 對指定索引index處的元素進行單點更新,增加val的值
    public void add(int index, int val) {
        while (index <= n) {
            tree[index] += val;
            index += index & -index;
        }
    }

    // 示例用法展示
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};
        BinaryIndexedTree bit = new BinaryIndexedTree(arr);

        // 查詢索引為3的字首和
        int prefixSum = bit.query(3);
        System.out.println("索引為3的字首和: " + prefixSum);

        // 對索引為2的元素進行單點更新,增加2
        bit.add(2, 2);

        // 再次查詢索引為3的字首和
        prefixSum = bit.query(2);
        System.out.println("更新後索引為2的字首和: " + prefixSum);
    }
}

相關文章