1. 程式人生 > >減治法(一)

減治法(一)

轉自:http://www.cnblogs.com/kkgreen/archive/2011/06/17/2083915.html

這篇文章將討論:

1) 減治法的思想和策略

2)  幾個資料結構裡面經典的使用減治策略的演算法:插入排序,深度和廣度優先查詢,拓撲排序(都是減一治的)

通過 1) 2)明白減治策略的基本思想和方法,也對經典資料結構做一番新的審視,從減治策略的角度來重新看待這些演算法。

而在後面,將繼續花幾篇文章討論減治策略的其他問題:排列問題,子集問題,減常因子演算法,減可變規模演算法。

----------------------------------------------------------------------------------------------------------------------------------------------------


減治技術利用了一個問題給定例項的解和同樣問題較小例項的解之間的某種關係

。一旦建立了這種關係,就可以從頂至下遞迴的來用該關係,也可以從底至上非遞迴的來運用該關係

1)減去一個常量

2)減去一個常量因子

3)減去的規模是可變的


1)   一般來說減去的一個常量是1,即如果不斷地解決n-1規模的問題就能解決n規模的問題,(偶而也有減2的,比較少)

比如求a^n的值,



既可以遞迴的從上到下求解,也可以非遞迴的從下往上構造(連續乘法,注意方法和蠻力一樣,但思考問題的角度不一樣)



2)  一般來說減去的一個常數因子是2(即將原問題規模分為2),其實減常因子的減治法可以看做是分治的變種,只不過它只對劃分子規模後的一個部分求解。

例如仍然是求a^n,我們可以這樣來思考:





3)對於減可變規模的例子,那就更少了,因為效率越高的演算法顯然越難找到。

一個例子是歐幾里得演算法,前面也寫過了:




總之,減治的3種方法,以及一個簡單的例子就像上面所述。


-------------------------------------------------------------------------------------------------------------------------------------------------


1,插入排序


1)  最簡單的排序方法,寫過,也很簡單。說下怎麼從減一治的策略來思考:

對A[0...n-1]排序,如果你已經對0到n-2排好了序,那麼將n-1號插入到合適的位置即可(注意後面的要移動)

這就遞迴的定義了插入排序的過程,當然,一般從底向上非遞迴的構造效率更高。遞迴和非遞迴的版本都可以寫,略過。


2)簡單的擴充套件下:折半插入排序
,在一個元素要求插入時,在尋找它的插入位置時,用折半查詢來尋找,複雜度是nlogn


3)shell排序:插入排序的改進,遞減插入步長,比如分別以13,4,1為步長來插入排序。

有機會寫一下。由於它的步長原因,可以看出shell排序是不穩定的。(插入排序是穩定的,它的步長是1)


-------------------------------------------------------------------------------------------------------------------------------------------------


2,深度優先查詢和廣度優先查詢



1)深度優先查詢

更正:前面寫的深度優先查詢是錯誤的,那本書上講的也是錯誤的。見前面的文章,我已經確認過了。

那本書上是這樣寫的:

初始化:第一個頂點入棧
while(棧不空)

{

  將當前棧頂元素出棧
  將出棧的這個頂點的所有未被訪問的鄰接點入棧

}

其結果是把出棧的順序作為深度優先遍歷的結果,錯錯錯!!!!!!!!!!!!!!!!!!!

這是形式上完全照抄廣度優先遍歷的寫法,廣度優先遍歷這樣是對的(把棧改成佇列,形式完全一樣)。它這樣做的核心思想似乎是每個頂點出棧時要把它未被訪問的鄰接點入棧,似乎這樣就能導致一個頂點擴展出去的頂點全部訪問完才能回到跟它同一批入棧的下一個頂點,事實上,大錯特錯!!!!!

錯誤:

a  不要把出棧順序當做訪問順序,而應該把進棧順序當做訪問順序,重寫一個程式(見下面)

b  不要冒然出棧,只有當一個頂點是死端(它沒有鄰接點或鄰接點已經都被訪問過了)了,才將它出棧。

c  出棧時不是將它所有的頂點都入棧,而是僅僅將它的一個未被訪問的頂點入棧。



其正確的演算法是這樣的:(用棧來實現)


初始化:第一個頂點入棧
while(棧不空)

{

  檢查當前棧頂元素是否為死端

  if(棧頂元素是死端)

    出棧;

  else(不是死端)

    把它的一個未被訪問的鄰接點入棧;

}


媽的,害我錯誤了這麼久。

當然,遞迴的來定義會簡單很多,不管什麼死端不死端,直接遞迴的定義深度優先搜尋,略。


另外一點就是這裡又擴充套件了一點,樹向邊和回邊:



進棧順序(第一個下標):  a  c  d  f  b  e      g  h  i  j  (深度遍歷被訪問順序)

出棧順序(第二個下標):  d  e  b  f  c  a  |  j  i  h  g

樹向邊:在檢查棧頂A時,若A不是死端,那麼將A的一個未被訪問的鄰接點B入棧,這就有了樹向邊A-B

回邊:若檢查棧頂A時,A是死端,則A到它的非父節點的鄰接點之間就存在一個回邊。

見上圖。

不含回邊----無迴路



2)廣度優先查詢

那本書上是對的,跟上述深度的錯誤程式碼一樣,只是將棧改成佇列。

也有樹向邊和回邊。

不再詳述。

BFS可以用來求2個頂點之間的最短距離,從其中一個開始遍歷,當到達另一個時就停止,此時BFS樹的根到第二個頂點之間的路徑具有最小邊數:




最後,一個總結:






-------------------------------------------------------------------------------------------------------------------------------------------------



3,拓撲排序

1)  從減一治的角度,其實就是傳統的拓撲演算法,就是每次從圖中刪除一個入度為0的頂點,見前面的一篇文章,很easy。


2)  來說一點有意思的,深度優先遍歷中出棧的順序反過來就是拓撲序列。請思考一下。







------------------------------------------------------------------------------------------------------------------------------------------------


總結:

減一治的思想和3種策略

3種經典的資料結構演算法,從減一治的角度思考。

特別是深度優先的更正!!!!!