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) )
運行結果爲: