五大經常使用算法 五大經常使用算法之二:動態規劃算法 五大經常使用算法之三:貪心算法 五大經常使用算法之四:回溯法 五大經常使用算法之五:分支限界法

http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741370.htmlhtml

分治算法

1、基本概念算法

   在計算機科學中,分治法是一種很重要的算法。字面上的解釋是「分而治之」,就是把一個複雜的問題分紅兩個或更多的相同或類似的子問題,再把子問題分紅更小的子問題……直到最後子問題能夠簡單的直接求解,原問題的解即子問題的解的合併。這個技巧是不少高效算法的基礎,如排序算法(快速排序,歸併排序),傅立葉變換(快速傅立葉變換)……設計模式

    任何一個能夠用計算機求解的問題所需的計算時間都與其規模有關。問題的規模越小,越容易直接求解,解題所需的計算時間也越少。例如,對於n個元素的排序問題,當n=1時,不需任何計算。n=2時,只要做一次比較便可排好序。n=3時只要做3次比較便可,…。而當n較大時,問題就不那麼容易處理了。要想直接解決一個規模較大的問題,有時是至關困難的。數組


2、基本思想及策略數據結構

   分治法的設計思想是:將一個難以直接解決的大問題,分割成一些規模較小的相同問題,以便各個擊破,分而治之。app

   分治策略是:對於一個規模爲n的問題,若該問題能夠容易地解決(好比說規模n較小)則直接解決,不然將其分解爲k個規模較小的子問題,這些子問題互相獨立且與原問題形式相同,遞歸地解這些子問題,而後將各子問題的解合併獲得原問題的解。這種算法設計策略叫作分治法。框架

   若是原問題可分割成k個子問題,1<k≤n,且這些子問題均可解並可利用這些子問題的解求出原問題的解,那麼這種分治法就是可行的。由分治法產生的子問題每每是原問題的較小模式,這就爲使用遞歸技術提供了方便。在這種狀況下,反覆應用分治手段,可使子問題與原問題類型一致而其規模卻不斷縮小,最終使子問題縮小到很容易直接求出其解。這天然致使遞歸過程的產生。分治與遞歸像一對孿生兄弟,常常同時應用在算法設計之中,並由此產生許多高效算法。ide


3、分治法適用的狀況函數

    分治法所能解決的問題通常具備如下幾個特徵:post

    1) 該問題的規模縮小到必定的程度就能夠容易地解決

    2) 該問題能夠分解爲若干個規模較小的相同問題,即該問題具備最優子結構性質。

    3) 利用該問題分解出的子問題的解能夠合併爲該問題的解;

    4) 該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子子問題。

第一條特徵是絕大多數問題均可以知足的,由於問題的計算複雜性通常是隨着問題規模的增長而增長;

第二條特徵是應用分治法的前提它也是大多數問題能夠知足的,此特徵反映了遞歸思想的應用;、

第三條特徵是關鍵,可否利用分治法徹底取決於問題是否具備第三條特徵,若是具有了第一條和第二條特徵,而不具有第三條特徵,則能夠考慮用貪心法或動態規劃法

第四條特徵涉及到分治法的效率,若是各子問題是不獨立的則分治法要作許多沒必要要的工做,重複地解公共的子問題,此時雖然可用分治法,但通常用動態規劃法較好


4、分治法的基本步驟

分治法在每一層遞歸上都有三個步驟:

    step1 分解:將原問題分解爲若干個規模較小,相互獨立,與原問題形式相同的子問題;

    step2 解決:若子問題規模較小而容易被解決則直接解,不然遞歸地解各個子問題

    step3 合併:將各個子問題的解合併爲原問題的解。

它的通常的算法設計模式以下:

    Divide-and-Conquer(P)

    1. if |P|≤n0

    2. then return(ADHOC(P))

    3. 將P分解爲較小的子問題 P1 ,P2 ,...,Pk

    4. for i←1 to k

    5. do yi ← Divide-and-Conquer(Pi) △ 遞歸解決Pi

    6. T ← MERGE(y1,y2,...,yk) △ 合併子問題

    7. return(T)

    其中|P|表示問題P的規模;n0爲一閾值,表示當問題P的規模不超過n0時,問題已容易直接解出,沒必要再繼續分解。ADHOC(P)是該分治法中的基本子算法,用於直接解小規模的問題P。所以,當P的規模不超過n0時直接用算法ADHOC(P)求解。算法MERGE(y1,y2,...,yk)是該分治法中的合併子算法,用於將P的子問題P1 ,P2 ,...,Pk的相應的解y1,y2,...,yk合併爲P的解。


5、分治法的複雜性分析

    一個分治法將規模爲n的問題分紅k個規模爲n/m的子問題去解。設分解閥值n0=1,且adhoc解規模爲1的問題耗費1個單位時間。再設將原問題分解爲k個子問題以及用merge將k個子問題的解合併爲原問題的解需用f(n)個單位時間。用T(n)表示該分治法解規模爲|P|=n的問題所需的計算時間,則有:

 T(n)= k T(n/m)+f(n)

    經過迭代法求得方程的解:

    遞歸方程及其解只給出n等於m的方冪時T(n)的值,可是若是認爲T(n)足夠平滑,那麼由n等於m的方冪時T(n)的值能夠估計T(n)的增加速度。一般假定T(n)是單調上升的,從而當                  mi≤n<mi+1時,T(mi)≤T(n)<T(mi+1)。 


6、可以使用分治法求解的一些經典問題
 
 (1)二分搜索
(2)大整數乘法
 (3)Strassen矩陣乘法
(4)棋盤覆蓋
(5)合併排序
(6)快速排序
(7)線性時間選擇

(8)最接近點對問題
(9)循環賽日程表
(10)漢諾塔

7、依據分治法設計程序時的思惟過程
 
    實際上就是相似於數學概括法,找到解決本問題的求解方程公式,而後根據方程公式設計遞歸程序。
一、必定是先找到最小問題規模時的求解方法
二、而後考慮隨着問題規模增大時的求解方法
三、找到求解的遞歸函數式後(各類規模或因子),設計遞歸程序便可。
 

五大經常使用算法之二:動態規劃算法

 

1、基本概念

    動態規劃過程是:每次決策依賴於當前狀態,又隨即引發狀態的轉移。一個決策序列就是在變化的狀態中產生出來的,因此,這種多階段最優化決策解決問題的過程就稱爲動態規劃。

2、基本思想與策略

    基本思想與分治法相似,也是將待求解的問題分解爲若干個子問題(階段),按順序求解子階段,前一子問題的解,爲後一子問題的求解提供了有用的信息。在求解任一子問題時,列出各類可能的局部解,經過決策保留那些有可能達到最優的局部解,丟棄其餘局部解。依次解決各子問題,最後一個子問題就是初始問題的解。

    因爲動態規劃解決的問題多數有重疊子問題這個特色,爲減小重複計算,對每個子問題只解一次,將其不一樣階段的不一樣狀態保存在一個二維數組中。

    與分治法最大的差異是:適合於用動態規劃法求解的問題,經分解後獲得的子問題每每不是互相獨立的(即下一個子階段的求解是創建在上一個子階段的解的基礎上,進行進一步的求解)。

 


 

3、適用的狀況

能採用動態規劃求解的問題的通常要具備3個性質:

    (1) 最優化原理:若是問題的最優解所包含的子問題的解也是最優的,就稱該問題具備最優子結構,即知足最優化原理。

    (2) 無後效性:即某階段狀態一旦肯定,就不受這個狀態之後決策的影響。也就是說,某狀態之後的過程不會影響之前的狀態,只與當前狀態有關。

   (3)有重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被屢次使用到。(該性質並非動態規劃適用的必要條件,可是若是沒有這條性質,動態規劃算法同其餘算法相比就不具有優點)

 


4、求解的基本步驟

     動態規劃所處理的問題是一個多階段決策問題,通常由初始狀態開始,經過對中間階段決策的選擇,達到結束狀態。這些決策造成了一個決策序列,同時肯定了完成整個過程的一條活動路線(一般是求最優的活動路線)。如圖所示。動態規劃的設計都有着必定的模式,通常要經歷如下幾個步驟。

    初始狀態→│決策1│→│決策2│→…→│決策n│→結束狀態

                      圖1 動態規劃決策過程示意圖

    (1)劃分階段:按照問題的時間或空間特徵,把問題分爲若干個階段。在劃分階段時,注意劃分後的階段必定要是有序的或者是可排序的,不然問題就沒法求解。

    (2)肯定狀態和狀態變量:將問題發展到各個階段時所處於的各類客觀狀況用不一樣的狀態表示出來。固然,狀態的選擇要知足無後效性。

    (3)肯定決策並寫出狀態轉移方程:由於決策和狀態轉移有着自然的聯繫,狀態轉移就是根據上一階段的狀態和決策來導出本階段的狀態。因此若是肯定了決策,狀態轉移方程也就可寫出。但事實上經常是反過來作,根據相鄰兩個階段的狀態之間的關係來肯定決策方法和狀態轉移方程。

    (4)尋找邊界條件:給出的狀態轉移方程是一個遞推式,須要一個遞推的終止條件或邊界條件。

    通常,只要解決問題的階段、狀態和狀態轉移決策肯定了,就能夠寫出狀態轉移方程(包括邊界條件)。

實際應用中能夠按如下幾個簡化的步驟進行設計:

    (1)分析最優解的性質,並刻畫其結構特徵。

    (2)遞歸的定義最優解。

    (3)以自底向上或自頂向下的記憶化方式(備忘錄法)計算出最優值

    (4)根據計算最優值時獲得的信息,構造問題的最優解

 


5、算法實現的說明

    動態規劃的主要難點在於理論上的設計,也就是上面4個步驟的肯定,一旦設計完成,實現部分就會很是簡單。

     使用動態規劃求解問題,最重要的就是肯定動態規劃三要素:

    (1)問題的階段 (2)每一個階段的狀態

    (3)從前一個階段轉化到後一個階段之間的遞推關係。

     遞推關係必須是從次小的問題開始到較大的問題之間的轉化,從這個角度來講,動態規劃每每能夠用遞歸程序來實現,不過由於遞推能夠充分利用前面保存的子問題的解來減小重複計算,因此對於大規模問題來講,有遞歸不可比擬的優點,這也是動態規劃算法的核心之處。

    肯定了動態規劃的這三要素,整個求解過程就能夠用一個最優決策表來描述,最優決策表是一個二維表,其中行表示決策的階段,列表示問題狀態,表格須要填寫的數據通常對應此問題的在某個階段某個狀態下的最優值(如最短路徑,最長公共子序列,最大價值等),填表的過程就是根據遞推關係,從1行1列開始,以行或者列優先的順序,依次填寫表格,最後根據整個表格的數據經過簡單的取捨或者運算求得問題的最優解。

          f(n,m)=max{f(n-1,m), f(n-1,m-w[n])+P(n,m)}

 

 


6、動態規劃算法基本框架

複製代碼
複製代碼

代碼

 1 for(j=1; j<=m; j=j+1) // 第一個階段
2   xn[j] = 初始值;
3
4  for(i=n-1; i>=1; i=i-1)// 其餘n-1個階段
5   for(j=1; j>=f(i); j=j+1)//f(i)與i有關的表達式
6 xi[j]=j=max(或min){g(xi-1[j1:j2]), ......, g(xi-1[jk:jk+1])};
8
9 t = g(x1[j1:j2]); // 由子問題的最優解求解整個問題的最優解的方案
10
11 print(x1[j1]);
12
13 for(i=2; i<=n-1; i=i+1)
15 {
17 t = t-xi-1[ji];
18
19 for(j=1; j>=f(i); j=j+1)
21 if(t=xi[ji])
23 break;
25 }
複製代碼
 

五大經常使用算法之三:貪心算法

貪心算法

1、基本概念:
 
      所謂貪心算法是指,在對問題求解時,老是作出在當前看來是最好的選擇。也就是說,不從總體最優上加以考慮,他所作出的僅是在某種意義上的局部最優解。
      貪心算法沒有固定的算法框架,算法設計的關鍵是貪心策略的選擇。必須注意的是,貪心算法不是對全部問題都能獲得總體最優解,選擇的貪心策略必須具有無後效性,即某個狀態之後的過程不會影響之前的狀態,只與當前狀態有關。
     因此對所採用的貪心策略必定要仔細分析其是否知足無後效性。
 
2、貪心算法的基本思路:
    1.創建數學模型來描述問題。
    2.把求解的問題分紅若干個子問題。
    3.對每一子問題求解,獲得子問題的局部最優解。
    4.把子問題的解局部最優解合成原來解問題的一個解。
 
3、貪心算法適用的問題
      貪心策略適用的前提是:局部最優策略能致使產生全局最優解。
    實際上,貪心算法適用的狀況不多。通常,對一個問題分析是否適用於貪心算法,能夠先選擇該問題下的幾個實際數據進行分析,就可作出判斷。
 
4、貪心算法的實現框架
    從問題的某一初始解出發;
    while (能朝給定總目標前進一步)
    { 
          利用可行的決策,求出可行解的一個解元素;
    }
    由全部解元素組合成問題的一個可行解;
  
5、貪心策略的選擇
     由於用貪心算法只能經過解局部最優解的策略來達到全局最優解,所以,必定要注意判斷問題是否適合採用貪心算法策略,找到的解是否必定是問題的最優解。
 
6、例題分析
    下面是一個能夠試用貪心算法解的題目,貪心解的確不錯,惋惜不是最優解。
    [揹包問題]有一個揹包,揹包容量是M=150。有7個物品,物品能夠分割成任意大小。
    要求儘量讓裝入揹包中的物品總價值最大,但不能超過總容量。
    物品 A B C D E F G
    重量 35 30 60 50 40 10 25
    價值 10 40 30 50 35 40 30
    分析:
    目標函數: ∑pi最大
    約束條件是裝入的物品總重量不超過揹包容量:∑wi<=M( M=150)
    (1)根據貪心的策略,每次挑選價值最大的物品裝入揹包,獲得的結果是否最優?
    (2)每次挑選所佔重量最小的物品裝入是否能獲得最優解?
    (3)每次選取單位重量價值最大的物品,成爲解本題的策略。
    值得注意的是,貪心算法並非徹底不可使用,貪心策略一旦通過證實成立後,它就是一種高效的算法。
    貪心算法仍是很常見的算法之一,這是因爲它簡單易行,構造貪心策略不是很困難。
    惋惜的是,它須要證實後才能真正運用到題目的算法中。
    通常來講,貪心算法的證實圍繞着:整個問題的最優解必定由在貪心策略中存在的子問題的最優解得來的。
    對於例題中的3種貪心策略,都是沒法成立(沒法被證實)的,解釋以下:
    (1)貪心策略:選取價值最大者。反例:
    W=30
    物品:A B C
    重量:28 12 12
    價值:30 20 20
    根據策略,首先選取物品A,接下來就沒法再選取了,但是,選取B、C則更好。
    (2)貪心策略:選取重量最小。它的反例與第一種策略的反例差很少。
    (3)貪心策略:選取單位重量價值最大的物品。反例:
    W=30
    物品:A B C
    重量:28 20 10
    價值:28 20 10
    根據策略,三種物品單位重量價值同樣,程序沒法依據現有策略做出判斷,若是選擇A,則答案錯誤。
 
 

五大經常使用算法之四:回溯法

一、概念

      回溯算法實際上一個相似枚舉的搜索嘗試過程,主要是在搜索嘗試過程當中尋找問題的解,當發現已不知足求解條件時,就「回溯」返回,嘗試別的路徑。

   回溯法是一種選優搜索法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步從新選擇,這種走不通就退回再走的技術爲回溯法,而知足回溯條件的某個狀態的點稱爲「回溯點」。

     許多複雜的,規模較大的問題均可以使用回溯法,有「通用解題方法」的美稱。

二、基本思想

   在包含問題的全部解的解空間樹中,按照深度優先搜索的策略,從根結點出發深度探索解空間樹。當探索到某一結點時,要先判斷該結點是否包含問題的解,若是包含,就從該結點出發繼續探索下去,若是該結點不包含問題的解,則逐層向其祖先結點回溯。(其實回溯法就是對隱式圖的深度優先搜索算法)。

       若用回溯法求問題的全部解時,要回溯到根,且根結點的全部可行的子樹都要已被搜索遍才結束。

       而若使用回溯法求任一個解時,只要搜索到問題的一個解就能夠結束。

三、用回溯法解題的通常步驟:

    (1)針對所給問題,肯定問題的解空間:

            首先應明肯定義問題的解空間,問題的解空間應至少包含問題的一個(最優)解。

    (2)肯定結點的擴展搜索規則

    (3)以深度優先方式搜索解空間,並在搜索過程當中用剪枝函數避免無效搜索。

四、算法框架

     (1)問題框架

      設問題的解是一個n維向量(a1,a2,………,an),約束條件是ai(i=1,2,3,…..,n)之間知足某種條件,記爲f(ai)。

     (2)非遞歸回溯框架

   1: int a[n],i;
   2: 初始化數組a[];
   3: i = 1;
   4: while (i>0(有路可走)   and  (未達到目標))  // 還未回溯到頭
   5: {
   6:     if(i > n)                                              // 搜索到葉結點
   7:     {   
   8:           搜索到一個解,輸出;
   9:     }
  10:     else                                                   // 處理第i個元素
  11:     { 
  12:           a[i]第一個可能的值;
  13:           while(a[i]在不知足約束條件且在搜索空間內)
  14:           {
  15:               a[i]下一個可能的值;
  16:           }
  17:           if(a[i]在搜索空間內)
  18:          {
  19:               標識佔用的資源;
  20:               i = i+1;                              // 擴展下一個結點
  21:          }
  22:          else 
  23:         {
  24:               清理所佔的狀態空間;            // 回溯
  25:               i = i –1; 
  26:          }
  27: }

 

        (3)遞歸的算法框架

         回溯法是對解空間的深度優先搜索,在通常狀況下使用遞歸函數來實現回溯法比較簡單,其中i爲搜索的深度,框架以下:

   1: int a[n];
   2: try(int i)
   3: {
   4:     if(i>n)
   5:        輸出結果;
   6:      else
   7:     {
   8:        for(j = 下界; j <= 上界; j=j+1)  // 枚舉i全部可能的路徑
   9:        {
  10:            if(fun(j))                 // 知足限界函數和約束條件
  11:              {
  12:                 a[i] = j;
  13:               ...                         // 其餘操做
  14:                 try(i+1);
  15:               回溯前的清理工做(如a[i]置空值等);
  16:               }
  17:          }
  18:      }
  19: }

五大經常使用算法之五:分支限界法

分支限界法

1、基本描述

    相似於回溯法,也是一種在問題的解空間樹T上搜索問題解的算法。但在通常狀況下,分支限界法與回溯法的求解目標不一樣。回溯法的求解目標是找出T中知足約束條件的全部解,而分支限界法的求解目標則是找出知足約束條件的一個解,或是在知足約束條件的解中找出使某一目標函數值達到極大或極小的解,即在某種意義下的最優解

   (1)分支搜索算法

    所謂「分支」就是採用廣度優先的策略,依次搜索E-結點的全部分支,也就是全部相鄰結點,拋棄不知足約束條件的結點,其他結點加入活結點表。而後從表中選擇一個結點做爲下一個E-結點,繼續搜索。

     選擇下一個E-結點的方式不一樣,則會有幾種不一樣的分支搜索方式。

   1)FIFO搜索

   2)LIFO搜索

   3)優先隊列式搜索

(2)分支限界搜索算法 

2、分支限界法的通常過程

    因爲求解目標不一樣,致使分支限界法與回溯法在解空間樹T上的搜索方式也不相同。回溯法以深度優先的方式搜索解空間樹T,而分支限界法則以廣度優先或以最小耗費優先的方式搜索解空間樹T

    分支限界法的搜索策略是:在擴展結點處,先生成其全部的兒子結點(分支),而後再從當前的活結點表中選擇下一個擴展對點。爲了有效地選擇下一擴展結點,以加速搜索的進程,在每一活結點處,計算一個函數值(限界),並根據這些已計算出的函數值,從當前活結點表中選擇一個最有利的結點做爲擴展結點,使搜索朝着解空間樹上有最優解的分支推動,以便儘快地找出一個最優解。

    分支限界法常以廣度優先或以最小耗費(最大效益)優先的方式搜索問題的解空間樹。問題的解空間樹是表示問題解空間的一棵有序樹,常見的有子集樹和排列樹。在搜索問題的解空間樹時,分支限界法與回溯法對當前擴展結點所使用的擴展方式不一樣。在分支限界法中,每個活結點只有一次機會成爲擴展結點。活結點一旦成爲擴展結點,就一次性產生其全部兒子結點。在這些兒子結點中,那些致使不可行解或致使非最優解的兒子結點被捨棄,其他兒子結點被子加入活結點表中。此後,從活結點表中取下一結點成爲當前擴展結點,並重覆上述結點擴展過程。這個過程一直持續到找到所求的解或活結點表爲空時爲止。

3、回溯法和分支限界法的一些區別

    有一些問題其實不管用回溯法仍是分支限界法均可以獲得很好的解決,可是另一些則否則。也許咱們須要具體一些的分析——到底什麼時候使用分支限界而什麼時候使用回溯呢?

回溯法和分支限界法的一些區別:

   方法對解空間樹的搜索方式       存儲結點的經常使用數據結構      結點存儲特性經常使用應用

  回溯法深度優先搜索堆棧活結點的全部可行子結點被遍歷後才被從棧中彈出找出知足約束條件的全部解

  分支限界法廣度優先或最小消耗優先搜索隊列、優先隊列每一個結點只有一次成爲活結點的機會找出知足約束條件的一個解或特定意義下的最優解

相關文章
相關標籤/搜索