淺談用極大化思想解決最大子矩陣問題

【摘要】
本文針對一類近期經常出現的有關最大(或最優)子矩形及相關變形問題,介紹了極大化思想在這類問題中的應用。分析了兩個具有一定通用性的算法。並通過一些例題講述了這些算法選擇和使用時的一些技巧。

【關鍵字】 矩形,障礙點,極大子矩形

【正文】

一、 問題

最大子矩形問題:在一個給定的矩形網格中有一些障礙點,要找出網格內部不包含任何障礙點,且邊界與座標軸平行的最大子矩形。

這是近期經常出現的問題,例如冬令營2002的《奶牛浴場》,就屬於最大子矩形問題。

Winter Camp2002,奶牛浴場
題意簡述:(原題見論文附件)
John要在矩形牛場中建造一個大型浴場,但是這個大型浴場不能包含任何一個奶牛的產奶點,但產奶點可以出在浴場的邊界上。John的牛場和規劃的浴場都是矩形,浴場要完全位於牛場之內,並且浴場的輪廓要與牛場的輪廓平行或者重合。要求所求浴場的面積儘可能大。
參數約定:產奶點的個數S不超過5000,牛場的範圍N×M不超過30000×30000。

二、 定義和說明

首先明確一些概念。

1、定義有效子矩形爲內部不包含任何障礙點且邊界與座標軸平行的子矩形。如圖所示,第一個是有效子矩形(儘管邊界上有障礙點),第二個不是有效子矩形(因爲內部含有障礙點)。


2、極大有效子矩形:一個有效子矩形,如果不存在包含它且比它大的有效子矩形,就稱這個有效子矩形爲極大有效子矩形。(爲了敘述方便,以下稱爲極大子矩形)

3、定義最大有效子矩形爲所有有效子矩形中最大的一個(或多個)。以下簡稱爲最大子矩形。

三、 極大化思想

  • 【定理1】在一個有障礙點的矩形中的最大子矩形一定是一個極大子矩形。

證明:如果最大子矩形A不是一個極大子矩形,那麼根據極大子矩形的定義,存在一個包含A且比A更大的有效子矩形,這與“A是最大子矩形”矛盾,所以【定理1】成立。

四、 從問題的特徵入手,得到兩種常用的算法

定理1雖然很顯然,但卻是很重要的。根據定理1,我們可以得到這樣一個解題思路:
通過枚舉所有的極大子矩形,就可以找到最大子矩形。下面根據這個思路來設計算法。
約定:爲了敘述方便,設整個矩形的大小爲n×m,其中障礙點個數爲s。

算法1

算法的思路是通過枚舉所有的極大子矩形找出最大子矩形。根據這個思路可以發現,如果算法中有一次枚舉的子矩形不是有效子矩形、或者不是極大子矩形,那麼可以肯定這個算法做了“無用功”,這也就是需要優化的地方。怎樣保證每次枚舉的都是極大子矩形呢,我們先從極大子矩形的特徵入手。

  • 【定理2】:一個極大子矩形的四條邊一定都不能向外擴展。更進一步地說,一個有效子矩形是極大子矩形的充要條件是這個子矩形的每條邊要麼覆蓋了一個障礙點,要麼與整個矩形的邊界重合。

    定理2的正確性很顯然,如果一個有效子矩形的某一條邊既沒有覆蓋一個障礙點,又沒有與整個矩形的邊界重合,那麼肯定存在一個包含它的有效子矩形。根據定理2,我們可以得到一個枚舉極大子矩形的算法。爲了處理方便,首先在障礙點的集合中加上整個矩形四角上的點。每次枚舉子矩形的上下左右邊界(枚舉覆蓋的障礙點),然後判斷是否合法(內部是否有包含障礙點)。這樣的算法時間複雜度爲O(S^5),顯然太高了。考慮到極大子矩形不能包含障礙點,因此這樣枚舉4個邊界顯然會產生大量的無效子矩形。
    這裏寫圖片描述
    考慮只枚舉左右邊界的情況。對於已經確定的左右邊界,可以將所有處在這個邊界內的點按從上到下排序,如圖1中所示,每一格就代表一個有效子矩形。這樣做時間複雜度爲O(S^3)。由於確保每次得到的矩形都是合法的,所以枚舉量比前一種算法小了很多。但需要注意的是,這樣做枚舉的子矩形雖然是合法的,然而不一定是極大的。所以這個算法還有優化的餘地。通過對這個算法不足之處的優化,我們可以得到一個高效的算法。
    回顧上面的算法,我們不難發現,所枚舉的矩形的上下邊界都覆蓋了障礙點或者與整個矩形的邊界重合,問題就在於左右邊界上。只有那些左右邊界也覆蓋了障礙點或者與整個矩形的邊界重合的有效子矩形纔是我們需要考察的極大子矩形,所以前面的算法做了不少“無用功”。怎麼減少“無用功”呢,這裏介紹一種算法(算法1),它可以用在不少此類題目上。
    算法的思路是這樣的,先枚舉極大子矩形的左邊界,然後從左到右依次掃描每一個障礙點,並不斷修改可行的上下邊界,從而枚舉出所有以這個定點爲左邊界的極大子矩形。考慮如圖2中的三個點,現在我們要確定所有以1號點爲左邊界的極大矩形。先將1號點右邊的點按橫座標排序。然後按從左到右的順序依次掃描1號點右邊的點,同時記錄下當前的可行的上下邊界。
    開始時令當前的上下邊界分別爲整個矩形的上下邊界。然後開始掃描。第一次遇到2號點,以2號點作爲右邊界,結合當前的上下邊界,就得到一個極大子矩形(如圖3)。同時,由於所求矩形不能包含2號點,且2號點在1號點的下方,所以需要修改當前的下邊界,即以2號點的縱座標作爲新的下邊界。第二次遇到3號點,這時以3號點的橫座標作爲右邊界又可以得到一個滿足性質1的矩形(如圖4)。類似的,需要相應地修改上邊界。以此類推,如果這個點是在當前點(確定左邊界的點)上方,則修改上邊界;如果在下方,則修改下邊界;如果處在同一行,則可中止搜索(因爲後面的矩形面積都是0了)。
    這裏寫圖片描述
    這裏寫圖片描述
    這裏寫圖片描述
    這樣做是否將所有的極大子矩形都枚舉過了呢?可以發現,這樣做只考慮到了左邊界覆蓋一個點的矩形,因此我們還需要枚舉左邊界與整個矩形的左邊界重合的情況。這還可以分爲兩類情況。一種是左邊界與整個矩形的左邊界重合,而右邊界覆蓋了一個障礙點的情況,對於這種情況,可以用類似的方法從右到左掃描每一個點作爲右邊界的情況。另一種是左右邊界均與整個矩形的左右邊界重合的情況,對於這類情況我們可以在預處理中完成:先將所有點按縱座標排序,然後可以得到以相鄰兩個點的縱座標爲上下邊界,左右邊界與整個矩形的左右邊界重合的矩形,顯然這樣的矩形也是極大子矩形,因此也需要被枚舉到。加了整個矩形右上角和右下角的兩個點,所以不會遺漏右邊界與整個矩形的右邊重合的極大子矩形(如圖5)。需要注意的是,如果掃描到的點不在當前的上下邊界內,那麼就不需要對這個點進行處理。

這裏寫圖片描述
通過前面兩步,可以枚舉出所有的極大子矩形。算法1的時間複雜度是O(S2)。這樣,可以解決大多數最大子矩形和相關問題了。

雖然以上的算法(算法1)看起來是比較高效的,但也有使用的侷限性。可以發現,這個算法的複雜度只與障礙點的個數s有關。但對於某些問題,s最大有可能達到n×m,當s較大時,這個算法就未必能滿足時間上的要求了。能否設計出一種依賴於n和m的算法呢?這樣在算法1不能奏效的時候我們還有別的選擇。我們再重新從最基本的問題開始研究。

算法2

首先,根據定理1:最大有效子矩形一定是一個極大子矩形。不過與前一種算法不同的是,我們不再要求每一次枚舉的一定是極大子矩形而只要求所有的極大子矩形都被枚舉到。看起來這種算法可能比前一種差,其實不然,因爲前一種算法並不是完美的:雖然每次考察的都是極大子矩形,但它還是做了一定量的“無用功”。可以發現,當障礙點很密集的時候,前一種算法會做大量沒用的比較工作。要解決這個問題,我們必須跳出前面的思路,重新考慮一個新的算法。注意到極大子矩形的個數不會超過矩形內單位方格的個數,因此我們有可能找出一種時間複雜度是O(N×M)的算法。
定義:

  • 有效豎線:除了兩個端點外,不覆蓋任何障礙點的豎直線段。
  • 懸線:上端點覆蓋了一個障礙點或達到整個矩形上端的有效豎線。如圖所示的三個有效豎線都是懸線。
    這裏寫圖片描述對於任何一個極大子矩形,它的上邊界上要麼有一個障礙點,要麼和整個矩形的上邊界重合。那麼如果把一個極大子矩形按x座標不同切割成多個(實際上是無數個)與y軸垂直的線段,則其中一定存在一條懸線。而且一條懸線通過儘可能地向左右移動恰好能得到一個子矩形(未必是極大子矩形,但只可能向下擴展)。通過以上的分析,我們可以得到一個重要的定理。

  • 【定理3】:如果將一個懸線向左右兩個方向儘可能移動所得到的有效子矩形稱爲這個懸線所對應的子矩形,那麼所有懸線所對應的有效子矩形的集合一定包含了所有極大子矩形的集合。

定理3中的“儘可能”移動指的是移動到一個障礙點或者矩形邊界的位置。
根據【定理3】可以發現,通過枚舉所有的懸線,就可以枚舉出所有的極大子矩形。由於每個懸線都與它底部的那個點一一對應,所以懸線的個數=(n-1)×m(以矩形中除了頂部的點以外的每個點爲底部,都可以得到一個懸線,且沒有遺漏)。如果能做到對每個懸線的操作時間都爲O(1),那麼整個算法的複雜度就是O(NM)。這樣,我們看到了解決問題的希望。

現在的問題是,怎樣在O(1)的時間內完成對每個懸線的操作。我們知道,每個極大子矩形都可以通過一個懸線左右平移得到。所以,對於每個確定了底部的懸線,我們需要知道有關於它的三個量:頂部、左右最多能移動到的位置。對於底部爲(i,j)的懸線,設它的高爲hight[i,j],左右最多能移動到的位置爲left[i,j],right[i,j]。爲了充分利用以前得到的信息,我們將這三個函數用遞推的形式給出。

對於以點(i,j)爲底部的懸線:
如果點(i-1,j)爲障礙點,那麼,顯然以(i,j)爲底的懸線高度爲1,而且左右均可以移動到整個矩形的左右邊界,即

height[i,j]=l
left[i,j]=0
right[i,j]=m+1

如果點(i-1,j)不是障礙點,那麼,以(i,j)爲底的懸線就等於以(i-1,j)爲底的懸線+點(i,j)到點(i-1,j)的線段。因此,height[i,j]=height[i-1,j]+1。比較麻煩的是左右邊界,先考慮left[i,j]。如下圖所示,(i,j)對應的懸線左右能移動的位置要在(i-1,j)的基礎上變化。
即left[i,j]=max( left[i-1,j] , (i-1,j)左邊第一個障礙點位置 ) 如圖

right[i,j]的求法類似。綜合起來,可以得到這三個參數的遞推式:

height[i,j]=height[i-1,j]+1
left[i,j]=max( left[i-1,j] , (i,j)左邊第一個障礙點位置,邊界0也是障礙點 )
right[i,j]=min( right[i-1,j] , (i,j)右邊第一個障礙點位置,邊界m+1也是障礙點 )

這裏寫圖片描述
這樣做充分利用了以前得到的信息,使每個懸線的處理時間複雜度爲O(1)。對於以點(i,j)爲底的懸線對應的子矩形,它的面積爲(right[i,j]-left[i,j])*height[i,j]。
這樣最後問題的解就是:
Result=max(right[i,j]-left[i,j])*height[i,j] (l <= i < n, l <= j<= m)
整個算法的時間複雜度爲O(NM),空間複雜度是O(NM)。

兩個算法的對比:
以上說了兩種具有一定通用性的處理算法,時間複雜度分別爲O(S2)和O(NM)。兩種算法分別適用於不同的情況。從時間複雜度上來看,第一種算法對於障礙點稀疏的情況比較有效,第二種算法則與障礙點個數的多少沒有直接的關係(當然,障礙點較少時可以通過對障礙點座標的離散化來減小處理矩形的面積,不過這樣比較麻煩,不如第一種算法好),適用於障礙點密集的情況。

五、 例題

1、 Winter Camp2002,奶牛浴場

分析:
題目的數學模型就是給出一個矩形和矩形中的一些障礙點,要求出矩形內的最大有效子矩形。這正是我們前面所討論的最大子矩形問題,因此前兩種算法都適用於這個問題。

下面分析兩種算法運用在本題上的優略:
對於第一種算法,不用加任何的修改就可以直接應用在這道題上,時間複雜度爲O(S2),S爲障礙點個數;空間複雜度爲O(S)。
對於第二種算法,需要先做一定的預處理。由於第二種算法複雜度與牛場的面積有關,而題目中牛場的面積很大(30000×30000),因此需要對數據進行離散化處理。離散化後矩形的大小降爲S×S,所以時間複雜度爲O(S2),空間複雜度爲O(S)。說明:需要注意的是,爲了保證算法能正確執行,在離散化的時候需要加上S個點,因此實際需要的時間和空間較大,而且編程較複雜。

從以上的分析來看,無論從時空效率還是編程複雜度的角度來看,這道題採用第一種算法都更優秀。

最大子矩陣問題模板

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