說到演算法,我們都知道,它是乙個能夠有效解決問題的指令序列。
說到模式,我們都會想到design pattern,它是在軟體設計中不斷出現的可重用的解決方案。
那麼,演算法中有沒有模式呢?答案是yes。
為了和design pattern區分,我把演算法中的模式定義為,在各種演算法中不斷出現的類似的解決問題方式。
這裡我想講乙個在很多演算法中都出現過的過程,我把它命名為「棧式遍歷」。
我們先定義一下這個過程:
首先,有乙個陣列a[i] (0<=i
1: for i=0 to n-1
2: while(棧裡面至少有k個元素 and
3: f(top-k+1,top-k+2,..., top, i) = false)
4: 令棧頂元素退棧
5: end while
6: 令a[i]進棧
7: end for
這個過程很簡單,通俗的講就是,如果遍歷過程中的a[i]和棧頂的若干數不相容,就一直退棧,直到它們相容了或者棧頂元素不夠了。然後把a[i]放進棧裡。在這個過程中,每個數進棧一次,至多出棧一次,所以時間複雜度是o(n)。
下面,我們就看看它是如何在具體問題中發揮作用的。
第乙個例子是乙個很常見的面試問題:
給定乙個n*n的陣列a[i,j],陣列的每個元素不是0就是1。我們要求的是,由1組成的最大矩形面積。比如下面的陣列:
0000
1110
0110
0111
最大矩形面積是6。
對於這個問題,這裡只討論如何在o(n^2)的時間複雜度下求出該面積。為了解決問題,我們先解決乙個稍微容易一些的子問題,有了這個子問題的結論,原問題就不費吹灰之力了。
考慮下列問題:
假設平地有n個高高低低的木棍排成一排,其高度用a[0..n-1]表示,其寬度為1。我是否能在o(n)的時間內,求出這些木棍所覆蓋的最大矩形面積?比如我有高度為1,3,2,4,1的5根木棍,如下圖所示
|| |
||||||||
其覆蓋的最大矩形區域由下圖中的*號表示,面積為6。
|| |
***|***|
如果能夠在o(n)的時間解決這個子問題,原問題一定可以在o(n^2)的時間內解決。這是因為,如果把a[i,j]陣列的每一行切開向上看去,連續的1就是子問題中的木棍!
下面我們看看棧式遍歷是如何來解決這個子問題的。
經觀察我們發現,對於每一根木棍,總是有乙個由它的高度所決定的最大矩形,這個矩形有乙個左邊界,乙個右邊界,在這兩個邊界裡面,所有的其它木棍都不會比這根木棍矮(上例中由*號覆蓋的區域就是由高度為2的木棍決定的)。而問題的解就是每個木棍所決定的最大矩形中的最大者。為了尋找由某個木棍高度決定的最大矩形,我們必須找到這兩個邊界。顯然,如果樸素地對每個木棍分別向兩邊迴圈查詢,時間複雜度一定高o(n)。那麼有什麼不樸素的方法呢?這個時候,棧式遍歷閃亮出場了!
我們定義判別函式為f(top, i) = true if a[i]>=棧頂元素
= false if a[i]《棧頂元素
(這裡只需要跟棧頂元素比較,所以k為1)
然後我們按照上面所講的棧式遍歷,對陣列a[i]從左到右進行遍歷。如果我們把0..n-1看做時間的話,那麼對於每個a[i],我們發現,它出棧的那個時刻,即是它所決定的那個最大矩形的右邊界!!對於最後沒有出棧的木棍,我們把n作為右邊界。也就是說,在一次棧式遍歷的過程中,我們可以同時找到所有木棍所決定的最大矩形的右邊界。同理,我們再從右往左遍歷一次,就可以找到它們的左邊界。因此,在o(n)的時間內,我們把問題解決了。再回到原問題,我們利用棧式遍歷得到了乙個o(n^2)的演算法。
第二個例子,我想講講計算幾何中求凸殼的graham scan演算法。這個經典演算法,就是用棧式遍歷得到的。
對於平面中的一堆點,我們要求它的凸殼。graham scan演算法可以描述為:
1: 將所有點p(x,y)按照y從小到大排序,如果y相同則比較x
2: 將p0和p1入棧
3: for i=2 to n-1
4: while(棧中至少有2個元素 and
5: s(p_top-1, p_top, pi)<=0) (s函式用來計算3個點所組成的有向面積)
6: 將棧頂元素退棧
7: end while
8: 將pi入棧
9: end for
我們看到了,這個就是棧式遍歷!這裡的k為2,即對每個點pi要根據棧頂的2個點才能判別是否相容。
事實上,上述演算法只找到了凸包中右半邊的點。我們令i從大到小再做一遍,即可得到凸包左半邊的點。
對於graham scan演算法的更多的資訊,可以參考wiki:http://en.wikipedia.org/wiki/graham_scan。
最後總結一下,這篇文章裡講述了乙個利用棧對資料進行處理的方式。這個方式很簡單,但是通過對判別函式的定義,我們可以用它發掘出資料中潛在的相互關係,並通過一些遍歷過程中的附加資訊(比如進棧和出棧的時間)解決問題。我們可以把這個演算法的模式看做一種解決問題的思路,當面對新問題的時候,嘗試一下也許能有意想不到的效果。
乙個簡單遍歷的演算法優化
include include define n 100000 int main int number 0,temp1,temp2 for number 1 number剛開始以為這已經算行了,但是在想了想過後,若是不是求加100再加168等等呢,萬一要求的範圍是一億呢?如果還是使用遍歷無疑這個程式...
利用乙個棧倒序另外乙個棧中的數
題目 有兩個相同的棧a和b,在棧a中存放著從大到小的數 1,2,3,4,5,棧頂為最小數1,另外乙個棧b為空的。現在要求不使用其他的資料結構,將棧a中的數字順序倒過來,使其棧頂的數為最大值5。include include include using namespace std template v...
演算法 13用乙個棧實現另乙個棧的排序
乙個棧中元素的型別為整型,現在想將該棧從頂到底按從大到小的順序排序,只許申請乙個棧。除此之外,可以申請新的變數,但不能申請額外的資料結構。如何完成排序?第一行輸入乙個n,表示棧中元素的個數 第二行輸入n個整數a iai 表示棧頂到棧底的各個元素 輸出一行表示排序後的棧中棧頂到棧底的各個元素。輸入 5...