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

分治法(一)

如何 處理過程 重新 代碼 字體 題目 -1 增加 帶來

泛泛而談晦澀難懂的概念非常的枯燥,接下來利用排序這樣一個問題引入今天的主題


給定7個數字的序列:6,202,100,301,38,8,1

要求對其按照升序進行排序。

首先使用冒泡的方法進行比較排序(每次紅色字體的兩個數字進行比較,較大者置後)

1 6,202,100,301,38,8,1

2 6,100,202,301,38,8,1

3 6,100,202,301,38,8,1

4 6,100,202,38,301,8,1

5 6,100,202,38,8,301,1

6 6,100,202,38,8,1,301

進行六次的比較和互換將最大的數字301放在了應該在的位置。

。。。

如此進行(7-1)*(7-1)=36次才能將整個序列排序完成。由此驗證了冒泡排序o(n*n)的時間復雜度。

接下來使用著名的歸並排序方法。歸並排序通常用遞歸實現,將待排序區間一分為二,對左右兩部分分別進行排序,最後將兩個排好序的部分進行合並。如此層層分割,直至區間長度為2,排序輕而易舉地實現,再層層合並。

對上述序列進行歸並排序如下

初始序列 :6,202,100,301,38,8,1

先將序列進行分割

6,202,100,301,|38,8,1

6,202,|100,301,|38,8,|1

第一次歸並:

(6,202),(100,301),(8,38),1 比較次數3次

第二次歸並:

(6,100,202, 301),(1,8,38)比較次數4次

第三次歸並:

(1,6,8,38,100,201,301)比較次數4次

綜上,共比較11次。在處理過程中對原序列進行分割求解這樣看似多此一舉的行為帶來的是整體時間復雜度的優化,有句俗語“磨刀不誤砍柴工”。在此問題中,將整個問題分割,形成的兩個子問題需要完成的工作和原問題性質是一樣的,方法也是類似,用同樣的方法處理子問題,最後將問題的解合並,這就是分治思想。


分治法

分:將一個大規模的問題化為若幹規模較小的子問題。

治:求出每個子問題的解並將這些解合並為原問題的解。

通常情況下,分解成的若幹個子問題與原問題具有相同的結構和性質,因此可以遞歸的求解。

有人會好奇,分治法的意義在哪裏,同樣是解決問題,多此一舉的將問題拆分,而後又合並,看似無形中增加了工作量。實際不然,采用分治法的有效保證是“當問題的規模足夠小的時候,可以非常輕松的求出問題的答案”,這樣將復雜的大問題轉換為無數個簡單的小問題的合並,在用的巧妙的場合,這種方法的效果是非常顯著的。

總結一下分治法的步驟就是:分割--求解--合並,分割雖然技術含量不是很高,但是合並可是一項技術活。不妨看看下面這個例子,對分治法加深一下印象。

請看例題:

技術分享

題目簡述:給定一個數字序列,其中的數字可正可負可為零,要求出一個子序列滿足該序列和最大。

題目要求簡潔明了,數據範圍和輸入輸出格式也都明確給出。不過別忘了題目中有一個提示“如果存在多個最大和序列,那麽給出第一個即可”。此處的第一個指的是始末位置下標較小的一個。

乍一看這道題,初學者可能沒有辦法將它和分治法聯系在一起,最可能想到的就是暴力又直接的方法:從首元素到末元素挨個假設作為該“最大和子序列”的初始元素,枚舉該子序列長度為1,2,3,。。。並計算序列元素和,不斷更新最大值,直至求出結果。這時候,你一定心裏暗暗發牢騷,為什麽求的是連續子序列和,如果是不連續序列和,直接將所有非負數相加不就大功告成了。我只能告訴你“想得美”。

雖然在題目上不能走捷徑,但是在解法上是可以優化的,那現在就勉為其難的將本題往分治法上貼唄。

分治法第一步要將區間分割。先假設將區間分為左右兩部分,這時候,就有兩種情況,一種是:要求的目標子序列處在左半部分的區間中,另一種情況:要求的目標子序列處在右半部分的區間中。我好像是發現了小秘密,似乎和分治法沾邊了,如果目標子序列在左半部分區間中,那麽將左半部分區間再一分為二。。。這麽分下去的話結果就出來了——那就是,整個區間被分成了一個一個的小碎片,哪裏來的目標子序列?情況似乎有些不妙,不過這才是第一步,接著往下。

分治法的第二步是將分割的小碎片進行求解,求解每個小碎片區間裏的最大和子序列,很簡單,分割結束時每個元素自成一區間,本身的值就是區間裏子序列的最大和。經過幾輪合並,每個區間包含的元素大於一個,這時候才談得上真正的求解,需要求解的就是在這些區間內部的最大和序列,經過前面的分析可知,任一待求序列就是構成該區間的兩個子區間包含的最大和序列之一。

分治法的第三步是合並,這是整個方法的核心,合並處理的就是兩個子區間合並起來究竟該選哪個子區間內的最大和序列作為合並後區間的最大和序列。至此,問題基本上已經解決完了。不知你是否能記起第一步的時候我提出的問題:整個序列已經被切割成小碎片了。整個求解的過程只是在子區間中選擇序列和較大的子序列,如何能將這些分散的序列連接起來?其次,將區間一分為二,最大和序列也有可能正好被切斷從而分布在兩個子區間中。因此在將兩個子區間合並的過程中,除了要考慮兩個子區間中的兩個最大和序列,還應該對被切斷這種情況進行特殊處理,即應該在切口兩側進行左右延伸,求得切口處的最大和序列,在左、右、中三者中選擇較大的一個,這樣就真正做到把分割開的序列重新連接為整體的合並處理。

主要思路如上,本題的測試樣例為:

技術分享

本題核心的代碼實現如下:

技術分享


想看本題完整解答過程的請打開網頁http://paste.ubuntu.com/25118866/。

分治法(一)