1. 程式人生 > >LogN級別的區間查詢演算法(線段樹), 你學會了嗎

LogN級別的區間查詢演算法(線段樹), 你學會了嗎

原博地址https://laboo.top/2018/11/24/xds/#more

簡介

線段樹演算法是一種快速查詢一段區間內的資訊的演算法, 由於其實現簡單, 所以廣泛應用於程式設計競賽中。
線段樹是一棵完美二叉樹, 即所有的葉子節點的深度均相同, 並且所有的非葉子節點都有兩個子節點。每個節點維護一個區間, 這個區間為父節點二分後的子區間, 根節點維護整個區間, 葉子節點維護單個元素, 當元素個數為n時, 對區間的操作都可以在O(log n)的時間內完成, 因為此時樹的深度為log2 n + 1, 每次操作只需從葉子節點開始, 往上更新至根節點, 每層只需更新相關的一個區間即可, 操作次數log2 n + 1

, 即在O(log n)的時間內可完成。

可實現的功能

線段樹可以提供不同的功能, 例如最常見的求區間內的最大最小值和求區間內的和, 還有其他類似的功能, 實現思路基本相同

求區間最小值(最小值)

給定任意數列[a0, a1,...,an-1], 在O(log n)的時間內完成下列的兩種操作

  • query(s, t)[as,as+1,...,at-1] 內的最小值(最小值)
  • update(i, x)ai 的值改為 x

求區間的和

給定初始值全為0的數列[a0, a1,...,an-1], 在O(log n)的時間內完成下列的兩種操作

  • query(s, t)
    [as,as+1,...,at-1] 內的和
  • add(i, x) 執行 ai += x

程式碼實現

這裡我們以求區間最小值內的最小值為例, 用Python來實現原始的一棵線段樹

初始化

這裡建立一個數組dat[]並賦予初始最大值, 為了讓其成為一棵完美的二叉樹, 便於計算, 我們把n擴大到2的冪, 由於我們在陣列中填充了int32的最大整數2147483647, 所以多餘出來的的元素總是最大值, 不會影響原來區間的結果

def init(self, n):
    self.INT_MAX = 2147483647
    self.n = 1
        
    while self.n < n:
        self.n *= 2

    self.dat = [self.INT_MAX for i in range(2 * self.n - 1)]

更新元素

我們把一棵完美二叉樹壓成一個數組, 下標為i的子節點為i*2+1 和 i*2+2, a0為根節點, 每次更新時, 首先更新葉子節點, 之後一層層往上更新, 節點a[k] = min(a[k * 2 + 1],a[k * 2 + 2]), 操作在O(log n)的時間內完成

def update(self, k, a):
    k += self.n - 1
    self.dat[k] = a
    while k > 0:
        k = (k - 1) // 2
        self.dat[k] = min(self.dat[k * 2 + 1],self.dat[k * 2 + 2])

查詢元素

query的功能為查詢[a, b)區間內的最小值, 引數k, l, r是輔助引數

  • k 當前計算的節點
  • l, r 當前節點區間的範圍

[a,b), 不在k節點管理的區間[l, r)內時, 直接返回INT_MAX
[a,b), 重合於k節點管理的區間[l, r)時, 直接返回k節點的值
否則, 遞迴k的兩個子節點, 返回其中的最小值

def query(self, a, b, k, l, r):
    if r <= a or b <= l:
        return self.INT_MAX

    if a <= l and r <= b:
        return self.dat[k]
    else:
        vl = self.query(a, b, k * 2 + 1, l, (l + r) // 2)
        vr = self.query(a, b, k * 2 + 2, (l + r) // 2, r)
        return min(vl, vr)

結尾

至此我們就簡單地實現了一棵線段樹, 這只是線段樹的其中一種形式, 線段樹還有其他的變體。線段樹的使用例項可以看我的另一篇文章https://laboo.top/2018/11/02/acm-lc-45/#more

歡迎關注我的部落格公眾號
2018_11_16_0048241709.png