線段樹+RMQ問題第二彈
線段樹+RMQ問題第二彈
上篇文章講到了基於Sparse Table 解決 RMQ 問題,不知道大家還有沒有印象,今天我們會從線段樹的方法對 RMQ 問題再一次討論。
正式介紹今天解決 RMQ 問題的方法之前,我先對 RMQ 問題的概念再一次進行說明。RMQ (Range Minimum/Maximum Query ):中文名為“區間最值查詢”。RMQ 問題指的是給定一段區間,針對給定區間進行若幹次查詢,每次給出不同的待查詢子區間範圍,要求返回子區間內的最大值或者最小值。
RMQ 問題可以看作是線段樹的一個應用,本來今天的主角是 RMQ 問題,但我思前想後覺得草草解釋線段樹或者只展示RMQ一個方面的應用留個小尾巴有點意猶未盡,之前沒有接觸過線段樹的朋友也會覺得雲裏霧裏,因此我改變主意決定以線段樹的基本概念和應用作為今天的主要內容,RMQ問題的解決作為整篇文章的一個實例,下面就要進入主題了。
線段樹是什麽?線段樹是一種完美的樹,這種樹對於處理區間問題有相當高的效率。線段樹滿足這樣一些特點:
一、是一棵完全二叉樹,除最後一層之外其余各層均是滿的,所有的葉節點均排布在最後一層,所有的節點不是葉節點就是擁有兩棵子樹的節點。
二、每個節點維護一段區間,其中根節點維護的是整個區間,其余節點均維護的是直接父節點的二分之一區間,也就意味著將父節點維護的區間二等分分別分給左右兩個孩子維護。
三、根據節點維護的數據不同,線段樹可以實現不同的功能。
四、並非所有的區間問題都可以用線段樹來解決。
這樣不形象的用文字描述就想讓沒接觸過的人了解線段樹實在有點強人所難,我還是畫一張醜圖來說明一下吧。如下圖所示:
以求 RMQ 問題為例我們來深入了解一下有關線段樹的性質,假設本篇文章我們的要求是求區間最小值。
由上圖可知,該圖中的線段樹維護的是 n = 8 的區間的數據,每個節點存儲的是所維護區間內的最小值,該線段樹的深度為 L 。通常情況下整個線段樹各節點的值會存儲在數組裏邊,因此我們需要知道線段樹的節點數。假設 區間的元素個數為 n ,線段樹的深度為 L ,線段樹的節點數為 m 。
由上圖可知三者滿足的關系為:
L = log n +1(對數以 2 為底);即 2^(L-1) = n
m = 2^0 + 2^1 + 2^2 + 2^3 + ......+ 2^( L - 1)
m =( 2^0 + 2^0 )+ 2^1 + 2^2 + 2^3 +...... + 2^( L-1) -1
m = (2^1 + 2^1) + 2^2 + 2^3 +......+ 2^(L-1) - 1
... ...
m = 2^(L-1) + 2^(L-1) -1
m = n + n -1
m = 2n-1
由上面的推導過程可以知道,m = 2n-1是妥妥的。沒求證之前,可能很多人看到線段樹的圖之後,都會覺得規模應該是 O(n log n),雖然不知道這種錯覺的理由是什麽,但是知道了一點,以後凡事都需要自己親自驗證一番才能徹底打消錯誤的念頭。這也就意味著對線段樹的初始化是O(n)的時間復雜度。
現在我們來看一下求區間最值具體是怎樣實現的。首先我們知道線段樹上存儲的是一些固定區間的最值,因此我們要求一些自定義區間的最值時,需要同時結合線段樹上的若幹個連續區間的多個最值,在其中選擇最小的作為最後的結果,由於將整個區間分成若幹子區間分別求最值最後合並結果的方法對最後結果的正確性沒有影響,因此存在實現上的可行性。那麽對於自定義的一個區間,在線段樹上具體需要選取哪些區間呢?我們需要選取的是能夠完全被自定義區間覆蓋的那些區間。對於線段樹上的任何一個區間,可能執行的操作有三個:
若該區間中的所有元素均包含在自定義區間中,那麽直接返回線段樹上存儲的該區間最值。
若該區間的所有元素中任何一個元素都不曾出現在自定義區間中,那麽自定義區間的最值一定不會來自該區間,這時候返回一個不影響結果判斷的值即可。
若該區間既不能被自定義區間完全包含,又存在若幹元素包含在自定義區間中,那麽遞歸的對該區間對應的兩個子區間分別執行這三個判斷。
大概思路就是如此。
雖然將普通區間改造為線段樹來維護沒有增加過多的任務量,由上面的分析可以知道這麽做的效果卻是很驚人的,因為利用線段樹求區間最值的時候,在樹的每一層至多需要訪問兩個節點,由上至下共 L = log n +1層,也就意味著,找到一個區間的最值需要訪問的節點數 至多都不會超過 2 * L - 1 ,因此查找一次區間最值時間復雜度穩定在 O( log n)的時間復雜度,這可是個驚人的發現呢。
線段樹的另一個比較有名的應用就是修改區間上某一位置的值,可以從線段樹的最底層開始修改,由於在任何一層每個元素只可能出現在一個區間,因此修改一個值的時間復雜度恒為O(log n)。
線段樹的兩個比較常見的應用都在這裏,理論描述上面已經給出,接下來我將核心代碼部分附上供大家對細節進行推敲。圖片看不清楚的朋友可打開網頁http://paste.ubuntu.com/25418076/查看網頁版的代碼。
今天的分享就到這裏,有朋友發現文章中寫的不合適的地方可以直接在下方留言區留言糾正。
沒有關註公眾號的朋友,可以識別下方二維碼關註我。
線段樹+RMQ問題第二彈