我理解的數據結構(八)—— 線段樹(SegmentTree)

我理解的數據結構(八)—— 線段樹(SegmentTree)

1、什麼是線段樹

1.最經典的線段樹問題:區間染色
有一面牆,長度爲n,每次選擇一段牆進行染色,m次操做後,咱們能夠看見多少種顏色?m次操做後,咱們能夠在[i, j]區間內看見多少種顏色?
圖片描述java

數據結構 染色操做 查詢操做
數組 O(n) O(n)

2.其餘應用場景
2017年註冊用戶中消費最高的用戶?消費最少的用戶?學習時間最長的用戶?數組

3.複雜度比較數據結構

數據結構 更新 查詢
數組 O(n) O(n)
線段樹 O(logn) O(logn)

4.線段樹原理圖
以求和爲例:
圖片描述app

2、線段樹基礎

1.基礎ide

  • 線段樹不是徹底二叉樹
  • 線段樹是平衡二叉樹(整棵樹最大深度和最小深度最大差爲1)
  • 線段樹依然能夠用數組表示函數

    • 把不存在的節點當作null,線段樹便是徹底二叉樹

2.數組存儲線段樹的空間需求學習

0層 1層 ... h-1層
1 2 ... 2^(h-1)
  • 對滿二叉樹:ui

    • h層,一共有2^h-1個節點(大約2^h)
    • 最後一層(h-1)層,有2^(h-1)個節點
    • 最後一層的節點數大體等於前面全部層節點之和
  • 若是須要存儲n個元素this

    • n=2^k,只須要2n的空間
    • n=2^k+1,須要4n的空間

3、線段樹基礎代碼

public class SegmentTree<E> {

    private E[] data;
    private E[] tree;

    public SegmentTree(E[] arr) {
        data = (E[])new Object[arr.length];

        for (int i = 0; i < arr.length; i++) {
            data[i] = arr[i];
        }
        tree = (E[])new Object[4 * arr.length];
    }

    // 獲取元素個數
    public int getSize() {
        return data.length;
    }

    // 獲取某個索引上的值
    private E get(int index) {
        if (index < 0 || index >= data.length) {
            throw new IllegalArgumentException("index is illegal");
        }

        return data[index];
    }

    // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的左子樹的索引
    private int leftChild(int index) {
        return 2 * index + 1;
    }

    // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的右子樹的索引
    private int rightChild(int index) {
        return 2 * index + 2;
    }
}

4、建立線段樹代碼

1.定義融合器接口spa

public interface Merge<E> {
    // 區間的元素如何定義由用戶決定
    E merge(E a, E b);
}

2.建立線段樹代碼

private Merge<E> merge;

public SegmentTree(E[] arr, Merge<E> merge) {

    // 線段樹的融合器,用於定義線段樹的區間元素到底如何存儲
    this.merge = merge;

    data = (E[])new Object[arr.length];

    for (int i = 0; i < arr.length; i++) {
        data[i] = arr[i];
    }
    tree = (E[])new Object[4 * arr.length];
    buildSegmentTree(0, 0, data.length - 1);
}

// 遞歸:在treeIndex的位置建立表示區間[l,r]的線段樹
private void buildSegmentTree(int treeIndex, int l, int r) {
    if (l == r) {
        tree[treeIndex] = data[l];
        return;
    }

    int leftTreeIndex = leftChild(treeIndex);
    int rightTreeIndex = rightChild(treeIndex);
    int min = (l + r) / 2;

    buildSegmentTree(leftTreeIndex, l, min);
    buildSegmentTree(rightTreeIndex, min + 1, r);
    tree[treeIndex] = merge.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
}

@Override
public String toString() {

    StringBuilder res = new StringBuilder();

    res.append('[');
    for (int i = 0; i < tree.length; i++) {
        if (tree[i] == null) {
            res.append("null");
        } else {
            res.append(tree[i]);
        }

        if (i != tree.length - 1) {
            res.append(", ");
        }

    }
    res.append(']');

    return res.toString();
}

5、線段樹的查詢

圖片描述

// 線段樹的查詢操做,區間[queryL, queryR]
public E query(int queryL, int queryR) {

   if (queryL < 0 || queryL >= data.length || queryR < 0 || queryR >= data.length) {
       throw new IllegalArgumentException("queryL or queryR is illegal");
   }

   return query(0, 0, data.length - 1, queryL, queryR);
}

// 遞歸,以treeIndex爲根節點,區間爲[l, r],查詢區間爲[queryL, queryR]
private E query(int treeIndex, int l, int r, int queryL, int queryR) {

   if (l == queryL && r == queryR) {
       return tree[treeIndex];
   }

   int leftChildIndex = leftChild(treeIndex);
   int rightChildIndex= rightChild(treeIndex);
   int mid = (l + r) / 2;

   if (queryL >= mid + 1) {
       return query(rightChildIndex, mid + 1, r, queryL, queryR);
   } else if (queryR <= mid) {
       return query(leftChildIndex, l, mid, queryL, queryR);
   }

   E left = query(leftChildIndex, l, mid, queryL, mid);
   E right = query(rightChildIndex, mid + 1, r, mid + 1, queryR);
   return merge.merge(left, right);
}

6、LeetCode上303號問題

題目:303. 區域和檢索 - 數組不可變
描述:給定一個整數數組 nums,求出數組從索引 i 到 j (i ≤ j) 範圍內元素的總和,包含 i, j 兩點。
示例:

給定 nums = [-2, 0, 3, -5, 2, -1],求和函數爲 sumRange()

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3

解題代碼:

// 注意,若是要在leetcode上提交解答,必須把Merge接口和SegmentTree類的代碼一併提交,這裏並無在寫NumArray類中
public class NumArray {

    private SegmentTree<Integer> segTree;

    public NumArray(int[] nums) {

        if (nums.length > 0) {

            Integer[] data = new Integer[nums.length];
            for (int i = 0; i < nums.length; i++) {
                data[i] = nums[i];
            }

            segTree = new SegmentTree<>(data, (a, b) -> a + b);
        }
    }

    public int sumRange(int i, int j) {
        if (segTree == null) {
                throw new IllegalArgumentException("segment tree is null");
        }
        return segTree.query(i, j);
    }

}

7、線段樹的更新

public void set(int index, E e) {

    if (index < 0 || index >= data.length) {
        throw new IllegalArgumentException("index is illegal");
    }

    set(0, 0, data.length - 1, index, e);
}

private void set(int treeIndex, int l, int r, int index, E e) {
    if (l == r) {
        tree[treeIndex] = e;
        return;
    }

    int leftChildIndex = leftChild(treeIndex);
    int rightChildIndex= rightChild(treeIndex);
    int mid = (l + r) / 2;

    if (index >= mid + 1) {
        set(rightChildIndex, mid + 1, r, index, e);
    } else if (index <= mid) {
        set(leftChildIndex, l, mid, index, e);
    }

    tree[treeIndex] = merge.merge(tree[leftChildIndex], tree[rightChildIndex]);
}

8、LeetCode上307號問題

題目:307. 區域和檢索 - 數組可修改
描述:給定一個整數數組 nums,求出數組從索引 i 到 j (i ≤ j) 範圍內元素的總和,包含 i, j 兩點。
示例:

Given nums = [1, 3, 5]

sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8

解題代碼:

class NumArray {

    // 注意,若是要在leetcode上提交解答,必須把Merge接口和SegmentTree類的代碼一併提交,這裏並無在寫NumArray類中    
    private SegmentTree<Integer> segTree;
    
    public NumArray(int[] nums) {
        if (nums.length > 0) {

            Integer[] data = new Integer[nums.length];
            for (int i = 0; i < nums.length; i++) {
                data[i] = nums[i];
            }

            segTree = new SegmentTree<>(data, (a, b) -> a + b);
        }
    }

    public void update(int i, int val) {
        if (segTree == null) {
            throw new IllegalArgumentException("segment tree is null");
        }
        segTree.set(i, val);
    }

    public int sumRange(int i, int j) {
        if (segTree == null) {
            throw new IllegalArgumentException("segment tree is null");
        }
        return segTree.query(i, j);
    }
}
相關文章
相關標籤/搜索