演算法中的乙個模式 棧式遍歷

2021-05-21 17:49:41 字數 2515 閱讀 9664

說到演算法,我們都知道,它是乙個能夠有效解決問題的指令序列。

說到模式,我們都會想到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...