分治算法(一)


當我們求解某些問題時,由於這些問題要處理的數據相當多,或求解過程相當復雜,使得直接求解法在時間上相當長,或者根本無法直接求出。對於這類問題,我們往往先把它分解成幾個子問題,找到求出這幾個子問題的解法后,再找到合適的方法,把它們組合成求整個問題的解法。如果這些子問題還較大,難以解決,可以再把它們分成幾個更小的子問題,以此類推,直至可以直接求出解為止。這就是分治策略的基本思想。

1、引例:

    如果給你一個裝有16枚硬幣的袋子,其中有一枚是偽造的,並且那枚偽造硬幣的重量和真硬幣的重量不同。你能不能用最少的比較次數找出這個偽造的硬幣?為了幫助你完成這一任務,將提供一台可用來比較兩組硬幣重量的儀器,利用這台儀器,可以知道兩組硬幣的重量是否相同。

    常規的解決方法是先將這些硬幣分成兩枚一組,每一次只稱一組硬幣,如果運氣好的話只要稱1次就可以找到,最壞最多稱8次才可以找出那枚硬幣,這種直接尋找的方法存在着相當大的投機性,適用於硬幣數量少的情況,在硬幣數量多的情況下就成為一件費時費力又需要運氣的事。

    試着改變一下方法:如果我們將全部硬幣分成兩組,將原來設計的一次比較兩枚硬幣變為一次比較兩組硬幣,我們會發現通過一次比較后.完全可以舍棄全部是真幣的一組硬幣,選取與原有問題一致的另一半進行下一步的比較,這樣問題的規模就明顯縮小,而且每一次比較的規模都是成倍減少(如圖4-1所示)。

 

    根據以上分析。我們可以得到以下的結論:

    (I)參與比較的硬幣數量越多,使用該方法來實現就越快.而且投機性大大減少;

    (2)解決方法關鍵在於能將大問題分割成若干小問題;

    (3)小問題與原有問題是完全類似的。

通常我們將這種大化小的設計策略稱之為分治法.即“分而治之”的意思。

2、分治法的基本思想和解題的一般步驟:

分治算法的基本思想是將一個規模為N的問題分解為K個規模較小的子問題,這些子問題相互獨立且與原問題性質相同。求出子問題的解,就可得到原問題的解。側重點在於能各個擊破。分治法在設計檢索、分類算法或某些速算算法中很有效。最常用的分治法是二分法、歸並法、快速分類法等。

分治法解題的一般步驟:

(1)分解,將要解決的問題划分成若干規模較小的同類問題;

(2)求解,當子問題划分得足夠小時,用較簡單的方法解決;

(3)合並,按原問題的要求,將子問題的解逐層合並構成原問題的解。

1、解決算法實現的同時,需要估算算法實現所需時間。分治算法時間是這樣確定的:     解決子問題所需的工作總量(由 子問題的個數、解決每個子問題的工作量 決定);合並所有子問題所需的工作量

2、分治法是把任意大小問題盡可能地等分成兩個子問題的遞歸算法

3、分治的具體過程大致如下: 

   begin  {分治過程開始}
    if ①問題不可分 then ②返回問題解  
     else begin
          ③從原問題中划出含一半運算對象的子問題1;
          ④遞歸調用分治法過程,求出解1;
          ⑤從原問題中划出含另一半運算對象的子問題2;
          ⑥遞歸調用分治法過程,求出解2; 
          ⑦將解1、解2組合成整修問題的解;  
        end;
   end; {結束}

3、典型例題:

1】用遞歸算法和非遞歸算法實現二分查找即:有 n個已經從小到大排序好的數據,從鍵盤輸入一個數m,用二分查找方法,判斷它是否在這個數中。

 1 var a:array[1..20]of integer;
 2    n,i,m,x,y:integer;
 3 procedure  jc(x,y:integer);                    //遞歸過程
 4   var  k:integer;
 5   begin
 6     k:=(x+y)div 2;                            //取中間位置點
 7     if a[k]=m then writeln('the num in ',k);        //找到查找的數,輸出結果
 8     if x>y then writeln('no find')                //找不到該數
 9      else begin
10            if a[k]<m then jc(k+1,y);            //在后半中查找
11            if a[k]>m then jc(x,k-1);            //在前半中查找
12          end;
13   end;
14 begin
15   readln(n);
16   x:=1;y:=n;
17   for i:=1 to n do readln(a[i]);            //輸入排序好的數
18   readln(m);                            //輸入要查找的數
19   jc(x,y);                                //遞歸查找
20 end.
遞歸法
 1 var a:array[1..20]of integer;
 2    n,i,m,x,y,k:integer;
 3 begin
 4   readln(n);
 5   x:=1;y:=n;
 6   for i:=1 to n do readln(a[i]);
 7   readln(m);
 8   repeat
 9     k:=(x+y)div 2;
10     if a[k]=m then begin writeln('the num in ',k);halt;end
11     else begin
12            if a[k]<m then x:=k+1;
13            if a[k]>m then y:=k-1;
14          end;
15   until x>y;
16   writeln('No find');
17 end.
非遞歸法

2一元三次方程求解 

有形如:ax3 +bx2 +cx+d0這樣的一個一元三次方程。給出該方程中各項的系數(abc均為實數),並約定該方程存在三個不同實根(根的范圍在-100 至 100 之間),且根與根之差的絕對值≥1。 要求由小到大依次在同一行輸出這三個實根(根與根之間留有空格),並精確到小數點后 位。 

提示:記方程 fx=0,若存在 個數 x1 和 x2,且 x1<x2fx1*fx2)<0

        則在(x1x2)之間一定有一個根。

輸入: 

abc

輸出: 

三個實根(根與根之間留有空格) 

輸入輸出樣例 

輸入:  1 -5 -4 20 

輸出:  -2.00 2.00 5.00 

 

 

【例3】循環比賽

【問題描述】 設有N個選手的循環比賽。其中N=2M,要求每名選手要與其他N-1名選手都賽一次。每名選手每天比賽一次,循環賽共進行N-1天.要求每天沒有選手輪空。

輸入:M

輸出:表格形式的比賽安排表

【樣例輸入】

  3

【樣例輸出】

  1 2 3 4 5 6 7 8

  2 1 4 3 6 5 8 7

  3 4 1 2 7 8 5 6

  4 3 2 1 8 7 6 5

  5 6 7 8 1 2 3 4

  6 5 8 7 1 1 4 3

  7 8 5 6 3 1 1 2

  8 7 6 5 d 3 2 1

【問題分析】:此題很難直接給出結果,我們先將問題進行分解,n=2^m,將規模減半,如果m=3(即n=8),8名選手的比賽,減半后變成4名選手的比賽(n=4),4個選手的比賽的安排方式還不是很明顯,再減半到兩名選手隊的比賽(n=2),兩名選手的比賽安排方式很簡單,只要讓兩名選手直接進行一場比賽即可:

 

1

2

2

1

分析兩個球隊的比賽的情況不難發現,這是一個對稱的方陣,我們把這個方陣分成4部分(即左上,右上,左下,右下),右上部分可由左上部分加1(即加n/2)得到,而右上與左下部分、左上與右下部分別相等。因此我們也可以把這個方陣看作是由n=1的方陣所成生的,同理可得n=4的方陣:

1

2

3

4

2

1

4

3

3

4

1

2

4

3

2

1

同理可由n=4方陣生成n=8的方陣:

1

2

3

4

5

6

7

8

2

1

4

3

6

5

8

7

3

4

1

2

7

8

5

6

4

3

2

1

8

7

6

5

5

6

7

8

1

2

3

4

6

5

8

7

2

1

4

3

7

8

5

6

3

4

1

2

8

7

6

5

4

3

2

1

 

這樣就構成了整個比賽的安排表。

 

在設計程序時,我們采用由小到大的方法進行擴展,而數組下標的處理是解決該問題的關鍵。用數組a記錄2^m名選手的循環比賽表,整個循環比賽表從最初的1*1方陣按上述規則生成2*2的方陣,再生成4*4的方陣,……直到生成出整個循環比賽表為止。變量h表示當前方陣的大小,也就是要生成的下一個方陣的一半。

【參考程序】

 1 program p4_1;  2 var 
 3  i,j,h,m,n:integer;  4   a:array[1..100,1..100]of integer;  5 begin
 6   assign(input,'word.in'); reset(input);  7   assign(output,'word.out'); rewrite(output);  8  readln(m);  9   n:=1;a[1,1]:=1;h:=1; 10   for i:=1 to m do n:=n*2; 11   repeat
12     for i:=1 to h do
13       for j:=1 to h do begin
14         a[i,j+h]:=a[i,j]+h;{構造右上角方陣}
15         a[i+h,j]:=a[i,j+h];{構造左下角方陣}
16         a[i+h,j+h]:=a[i,j];{構造右下角方陣}
17       end; 18     h:=h*2; 19   until h=n; 20   for i:=1 to n do
21   begin
22     for j:=1 to n do write(a[i,j]:4); writeln; 23   end; 24  close(input); close(output); 25 end.

[例4] 求方程的根

【問題描述】

    輸入mnpab,求方程f(x)=mx+nx-px0[a,b]內的根。mnpab均為整數,且a<bmnp都大於等於1。如果有根,則輸出,精確到1E-11;如果無方程根,則輸出“NO”。

【樣例】

equation.in

2 3 4 1 2

equation.out

1.5071265916E+00

2.9103830457E-11

【算法分析】

首先這是一個單調遞增函數,對於一個單調遞增(或遞減)函數,如圖4-7所示,判斷在[a, b]范圍內是否有解,解是多少。方法有多種,常用的一種方法叫“迭代法”,也就是“二分法”。先判斷f(a)·f(b)≤0,如果滿足則說明在[a, b]范圍內有解,否則無解。如果有解再判斷x(a+b)/2是不是解,如果是則輸出解結束程序,否則我們采用二分法,將范圍縮小到[a, x)或(x, b],究竟在哪一半區間里有解,則要看是f(a)·f(x)<0還是f(x)·f(b)<0

 

當然對於yx,我們需要用換底公式把它換成exp(x*ln(y))。

4、小結:

分治法所能解決的問題一般具有以下幾個特征:

  1. 該問題的規模縮小到一定的程度就可以容易地解決;
  2. 該問題可以分解為若干個規模較小的相同問題,即該問題具有子結構性質
  3. 利用該問題分解出的子問題的解可以合並為該問題的解;
  4. 該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子問題。

第4條特征涉及到分治法的效率,如果各子問題是不獨立的,則分治法要做許多不必要的工作,重復地解公共的子問題,此時雖然也可用分治法,但一般用動態規划較好。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM