Range Minimum Query( RMQ )

RMQ (Range Minimum/Maximum Query)問題是指:對於長度爲n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在i,j裏的最小(大)值,也就是說,RMQ問題是指求區間最值的問題。

方法一:

蠻力搜索,時間時間複雜度 O( n )。


方法二:O(N^2), O(1)

利用動態規劃進行預處理,計算出M:

M[ i ][ j-1 ] 爲 i 到 j-1 最小值索引,則M[i][j] = A[j] < A[ M[ i ][ j-1] ] ? j : M[ i ][ j - 1 ]。


方法三:O(NlogN), O(1)

Sparse Table (ST) algorithm    

預處理RMQ 是對2k 的長度的子數組進行動態規劃。我們將使用數組M[0, N-1][0, logN]進行保存,其中M[i][j]是以i 開始,長度爲 2j的子數組的最小值的索引。

查找算法:Q(i, j)

尋找一個k = log( j-i + 1 ), 然後尋找 區間(i, i+2^k)即M[i, k] 及 區間(j-2^k+1, k)即M[j-2^k+1][k]的最小值。


方法四:

線段樹:O(nlog(n)), O(log(n))

線段樹能在對數時間內在數組區間上進行更新與查詢。 
定義線段樹在區間[i, j] 上如下: 
第一個節點維護着區間 [i, j] 的信息。 
if i<j , 那麼左孩子維護着區間[i, (i+j)/2] 的信息,右孩子維護着區間[(i+j)/2+1, j] 的信息。 

由於線段樹的葉節點有N個(在最後兩層),故其高度 爲 height =  floor(logN) + 1(只有根節點的樹高度爲0) . 

所以, 此線段樹節點總數必然小於:  2^(height+1) - 1。(這是調試爲height的滿2叉樹結點的個數)

深度——二叉樹的層數,就是高度。 )

空樹的高度爲0, 樹只有一個節點的高度爲1。

下面是區間 [0, 9]  的一個線段樹: 


線段樹和堆有一樣的結構, 因此如果一個節點編號爲 x ,那麼左孩子編號爲2*x  右孩子編號爲2*x+1. 
使用線段樹解決RMQ問題,關鍵維護一個數組M[num],num=2^(線段樹高度+1). 
M[i]:維護着被分配給該節點(編號:i 線段樹根節點編號:1)的區間的最小值元素的下標。
該數組初始狀態爲-1.
 

建立線段樹算法:調用爲( 0, N-1, 0 )

build( begin, end, node):

if begin == end, Min[node] = begin, return begin

        mid = (begin+end)/2,  left = 2*node+1,  right = 2*node + 2

        s1 = build( begin, mid, left )

        s2 = build( mid+1, end, right)

        if A[s1] < A[s2]  Min[node] = s1, return s1

        else  Min[node] = s2, return s2                

查找算法:

對於編號爲[b, e]的線段樹,查找(i, j)之間的最小值, 即query( b, e, i, j), 則:

query( begin, end, node, i, j ):

        if i>end || j < begin,  return -1;                  // (i, j) 與 ( begin, end )無交集。

        if i<=begin && j >= end, return Min[node];  // (i, j) 包括了( begin, end )

        mid = (begin+end)/2, left = 2*node+1, right = 2*node+2

        s1 = query( begin, mid, left, i, j ),  s2 = query( mid+1, end,  right, i, j)  

       // i, j保持不變 即查詢區間保持不變,將其分配到已經完成計算的子區間中。

        if -1 == s1 , return s2

        if -1 == s2 , return s1

        if A[s1] < A[s2], return s1

        else  return s2  

查找(i, j)內最小值調用爲:query(0, N-1, 0, i, j) 


python 代碼:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import math

def build_tree( A, node = 0, begin = None, end = None, M = None  ):
    if begin == None:
        begin = 0
        end = len( A ) - 1
        M = [None] *int(math.pow( 2, 2 + math.floor( math.log(end+1, 2) ) ) )
        #print( len(M) )
    if begin == end:
        #print( node )
        M[ node ] = begin
        return M
    mid = int(math.floor( (begin + end)/2 ))
    left = 2*node +1
    right = 2*node + 2
    build_tree( A, left, begin, mid, M )
    build_tree( A, right, mid+1, end, M )
    #print( left, M[left] )
    #print( right, M[right])
    #print( A )
    if A[ M[left] ] < A[ M[right] ]:
        M[node] = M[left]
    else:
        M[node] = M[right]
    return M

def query(A, M, i, j, node = 0, begin = None, end = None):
    if None == begin: 
        begin = 0
        end = len(A) - 1
    if i>end or j<begin:
        return -1
    if i<=begin and j>= end:
        return M[node]
    mid = math.floor( (begin + end)/2 )
    left = 2*node+1
    right = 2*node+2
    s1 = query(A, M, i, j, left, begin, mid)
    s2 = query(A, M, i, j, right, mid+1, end)
    if -1 == s1:
        return s2
    if -1 == s2:
        return s1
    if A[s1]<A[s2]:
        return s1
    else:
        return s2
    print( "impossible line..")
    

if __name__ == "__main__":
    A = [3, 1, 5, 7, 2, 9, 0, 3, 4, 5]
    M = build_tree( A )
    print( M )
    print(A)
    print( 0, 3,  query(A, M, 0, 3) )
    print( 1, 3,  query(A, M, 1, 3) )
    print( 6, 8,  query(A, M, 6, 8) )
    print( 5, 8,  query(A, M, 5, 8) )

運行結果爲:






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章