算法設計與分析(二)五大算法--分治法、貪心方法、動態規划、回溯法、分支界限法


參考

https://my.oschina.net/HuoQibin/blog/1632769

 

分治法

定義:

將原問題分解為幾個規模較小但類似於原問題的子問題,遞歸地求解這些子問題,然后再合並這些子問題的解來建立原問題的解。----《算法導論》

1.分治法基本策略

1)將問題分解為規模較小的子問題,子問題與原問題相互獨立且同質

2)迭代或者遞歸解決每個子問題

3)將子問題的解合並得到原問題解

 

2.分治算法特征分析

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

1) 該問題的規模縮小到一定程度就可以容易的解決;

2) 該問題可以分解為若干個規模較小的相同問題,即該問題具有最優子結構性質;

3) 利用分解出的子問題的解,可以合並為該問題的解;

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

分治算法大多采用遞歸實現,第二條特征就反應了遞歸思想的引用。

如果滿足了第一條特征和第二條特征,不滿足第三條特征,可以考慮用貪心法或動態規划法。

如果不滿足第四條特征,也可以用分治法,但是要做很多不必要的工作,重復的解公共的子問題,所以一般用動態規划法比較好。

3.實際應用場景

二分查找,階乘計算,歸並排序,堆排序、快速排序、傅里葉變換都用了分治法的思想。

4.依據分治法設計程序的思維過程

實際上類似數學歸納法,找到解決本問題的求解方程公式,然后根據方程公式設計遞歸程序。

1) 一定是先找到最小問題規模時的求解方法

2) 然后考慮隨着問題規模增大時的求解方法

3) 找到求解的遞歸函數后,設計遞歸程序即可。

算法框架:

SolutionType DandC(ProblemType P){
    ProblemType P1,P2,P3,...Pn;
    if(Small(P))    return S(P);    //子問題P足夠小,直接求解
    else{
        Divide(P1,P2,P3,...Pn);    //將問題P分解為子問題P1,P2,...Pn
        Return Combine(DandC(P1),DandC(P2),...,DandC(Pk));    //求解子問題,並合並解
    }
} 

相關算法:

貪心法

貪心法是求解最優化問題的一種設計策略。貪心法通過分步決策來求解問題。

定義:

貪心算法就是,在對問題求解時,總是做出在當前這一步看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的是在某種意義上的局部最優解。

最優化問題:問題給出某些約束條件,滿足這些約束條件的解稱為可行解;為了衡量可行解的好壞,問題還給出了目標函數,使目標函數取最大(小)值的可行解稱為最優解。

  • 注:貪心法在每一步上用作決策依據的選擇准則被稱為最優量度標准 或 貪心准則,這種量度標准通常只考慮局部最優性。
  • 注:貪心算法沒有固定的算法框架,算法設計的關鍵是貪心策略的選擇。必須注意的是,貪心算法不是對所有問題都能得到整體最優解,選擇的貪心策略必須具備無后效性,即某個狀態以后的過程不會影響以前的狀態,只與當前狀態有關。所以對所采用的貪心策略一定要仔細分析其是否滿足無后效性。

貪心法基本要素:

貪心選擇性質最優度量標准所謂貪心選擇性質是指所求問題的整體最優解可以通過一系列局部最優的選擇(通過最優度量標准來判斷),即貪心選擇來達到。這是貪心算法可行的第一個基本要素,也是貪心算法與動態規划算法的主要區別。選擇最優度量標准是使用貪心法求解問題的核心問題。貪心算法每步做出的選擇可以依賴以前做出的選擇,但絕不依賴將來的選擇(也稱無后效性),也不依賴子問題的解。也正是如此,貪心法的特征是自頂向下(與動態規划相反),一步一步地做出貪心決策。

最優子結構(最優化原理)當一個問題的最優解中包含了子問題的最優解時,則稱該問題具有最優子結構特性。一個可用貪心法求解的問題往往呈現最優子結構性質。

     

貪心策略適用的前提是:局部最優策略能導致產生全局最優解。
實際上,貪心算法適用的情況很少。一般,對一個問題分析是否適用於貪心算法,可以先選擇該問題下的幾個實際數據進行分析,就可做出判斷。

 

注:並不是所有具有最優子結構特性的問題,都可以有幸的找到最優量度標准,此時可以考慮采用動態規划法求解。

 

貪心算法與動態規划的區別

  • 貪心算法和動態規划都具備最優子結構(滿足最優化原理即是最優子結構問題),以及都滿足無后效性,但動態規划不具備貪心選擇性質
  • 動態規划算法和貪心算法有一個顯著區別:

1)在動態規划算法中,以自底向上的方式來利用最優子結構,也就是說,首先找到子問題的最優解,解決子問題,然后找到問題的一個最優解。
2)在貪心算法中,以自頂向下的方式使用最優子結構,也就是說,貪心算法會先做出選擇,在當時看起來是最優的選擇,然后再求解一個結果子問題,而不是先求解子問題的最優解,然后再做出選擇

  • 貪心算法作出的每步貪心決策都無法改變,因為貪心策略是由上一步的最優解推導下一步的最優解,而上一部之前的最優解則不作保留。 

動態規划算法的全局最優解中一定包含某個局部最優解,但不一定包含前一個局部最優解,因此需要記錄之前的所有局部最優解;

利用貪心算法求解最優問題的步驟:

(1)選定合適的貪心選擇的標准;(2)證明在此標准下該問題具有貪心選擇性質;

(3)證明該問題具有最優子結構性質;(4)根據貪心選擇的標准,寫出貪心選擇的算法,求得最優解

算法模板:

SolutionType Greedy(SType a[],int n){
    SolutionType solution = null;    //初始解向量不含任何分量
    for(int i = 0;i<n;i++){    //多步決策,每步選擇一個分量
        SType x = Select(a);    //遵循最優度量標准選擇一個分量
        if(Feasible(solution,x)    //判斷新分量x加入后是否可行
            solution = Union(solution,x);    //形成新的最優解
    }
    return solution;    //返回生成的最優解
}

相關算法:

動態規划

 定義

按照多步決策方法(和貪心一樣也是分步決策),一個問題的活動過程可以分成若干階段,每個階段可能包含一個或多個狀態。多部決策求解方法就是從初始狀態開始做出每個階段的決策,形成一個決策序列,該決策序列也成為策略。對於每一個決策序列,可以用一個數值函數(目標函數)衡量該策略的優劣。問題求解的目標是獲取最優決策序列

動態規划法基本要素:

最優性原理(最優子結構):和貪心法相同。最優性原理是指“多階段決策過程的最優決策序列具有這樣的性質:不論初始狀態和初始決策如何,對於前面決策所造成的某一狀態而言,其后各階段的決策序列必須構成最優策略”這個最優性原理是動態規划的基礎。tip:而如果問題的最優解所包含的子問題的解也是最優的,就稱該問題具有最優子結構,即滿足最優性原理。

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

重疊子問題:動態規划法的子問題往往是重疊的,如果采用與分治法類似的直接遞歸會非常費時。為了避免重復計算,動態規划法一般采用自底向上的方式進行計算,並保存已經求解的子問題的值。當這些子最優解值被重復引用時,無需重新計算。

設計動態規划法步驟:

  1. 刻畫最優解的結構特性;
  2. 遞歸定義最優解值;
  3. 自底向上方式計算最優解值;
  4. 根據計算得到的信息構造一個最優解。

算法實現的說明

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

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

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

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

遞推關系必須是從次小的問題開始到較大的問題之間的轉化,從這個角度來說,動態規划往往可以用遞歸程序來實現,不過因為遞推可以充分利用前面保存的子問題的解來減少重復計算,所以對於大規模問題來說,有遞歸不可比擬的優勢,這也是動態規划算法的核心之處。

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

相關算法:

回溯法

回溯法思路的簡單描述是:把問題的解空間轉化成了圖或者樹的結構表示,然后使用深度優先搜索策略進行遍歷,遍歷的過程中記錄和尋找所有可行解或者最優解。

在許多遞歸問題當中,我們采取的方法都是窮盡所有的可能,從而找出合法的解。但是在某些情況下,當遞歸到某一層的時候,根據設置的判斷條件,可以 judge 此解是不合法的。在這種情況下,我們就沒必要再進行深層次的遞歸,從而可以提高算法效率。這一類算法我們稱為“回溯法”,設置的判斷條件稱為“剪枝函數”。

使用剪枝函數深度優先生成狀態空間樹中的節點的求解方法稱為回溯法廣度優先生成結點,並使用剪枝函數的方法稱為分枝限界法

基本思想類同於:

  • 圖的深度優先搜索
  • 二叉樹的后序遍歷

為了生成問題狀態,采用兩種根本不同的方法。雖然, 這兩種方法都是從根結點 開始然后生成其它結點。如果已生成一個結點而它的所有兒子結點還沒有全部生成, 則這 個結點叫做活結點。當前正在生成其兒子結點的活結點叫 E-結點(正在擴展的結點 )。不再 進一步擴展或者其兒子結點已全部生成的結點就是死結點。在生成問題狀態的兩種方法 中,都要有一張活結點表。在第一種方法中, 當前的 E-結點 R 一旦生成一個新的兒子 C, 這 個兒子結點就變成一個新的 E-結點, 當完全檢測了子樹 C 之后, R 結點就再次成為 E-結點。 這相當於問題狀態的深度優先生成。在第二種狀態生成方法中, 一個 E-結點一直保持到變 成死結點為止。在這兩種方法中, 將用限界函數去殺死還沒有全部生成其兒子結點的那些 活結點。這樣做要非常小心,以使得在處理結束時至少能生成一個答案結點; 如果這個問題 要求找出全部解,則要能生成所有的答案結點。使用限界函數的深度優先結點生成方法稱 為回溯法( backtr acking) 。

E-結點一直保持到死為止的狀態生成方法導致分枝-限界方法 ( br anch-and-bound) , 分枝-限界法在第 7 章討論。在寬度優先生成中, 每個新結點都被放 入一個隊列。當這個當前 E-結點已生成了它的所有兒子時, 在隊列前面的下一個結點就變 成新的 E-結點。在深度優先生成中, 那些新結點被放入棧而不是放入隊列。當涉及這兩種方案之 一時,當前的術語就不能保持一致了。隊列方法一般叫做寬度優先生成, 而棧方法則稱為 D-檢索( 深度檢索)。

 

 

定義(太長不記)

回溯算法實際上一個類似枚舉的搜索嘗試過程,主要是在搜索嘗試過程中尋找問題的解,當發現已不滿足求解條件時,就“回溯”返回,嘗試別的路徑。回溯法是一種選優搜索法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術為回溯法,而滿足回溯條件的某個狀態的點稱為“回溯點”。

基本思想  

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

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

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

用回溯法一般步驟:   

(1)針對所給問題,確定問題的解空間: 首先應明確定義問題的解空間,問題的解空間應至少包含問題的一個(最優)解。

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

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

條件:

  1. 它的解具有n-y元組的形式
  2. 問題提供顯式約束來確定狀態空間樹,並提供隱式約束來判定可行解
  3. 能設計有效的約束函數縮小檢索空間

也有說法是 多米諾性質 :

    • 如果當前結點不滿足約束條件,能夠推導出它的子結點也不滿足約束條件.
    • 如果子結點滿足約束條件能夠推導出其父結點滿足約束條件.

 

  • 顯示約束解空間:規定每個分量xi取值的約束條件稱為顯式約束。對給定的一個問題,顯示約束規定了所有可能的元組,他們組成問題的候選解集,被稱為該問題實例的解空間。
  • 隱式約束判定函數:隱式約束給出了判定一個候選解是否為可行解的條件。一般需要從問題描述的隱式約束出發,設計一個判定函數(有時候是使該判定函數取極值來得到最優解),程序根據判定函數判斷一個解是否為可行解。
  • 最優解目標函數:目標函數,也稱代價函數,用來衡量每個可行解的優劣。使目標函數取得最大(小)值的可行解為問題的最優解。
  • 剪枝函數:為了提高搜索效率,在搜索過程中使用約束函數,可以避免無謂地搜索那些已知不含答案狀態的子樹。如果是最優化問題,還可以使用限界函數剪去那些不可能含有最優解的子樹。約束函數和限界函數目的相同,都是為了剪去不必要搜索的子樹,減少問題求解所需實際生成的狀態結點數,他們統稱為剪枝函數。

回溯法算法框架:

Void RBacktrack(int k){
    for(每個x[k],使得x[k]∈T[x[0],...,x[k-1]&&B(x[0],...x[k]){ //T[x[0],...,x[k-1]]是所有結點x[i]的集合,B(x[0],...x[k]就是顯式約束規定x值的范圍
        if(x[o],x[1],...,x[k]是一個可行解)//通過隱式約束得到的判定函數判斷是否可行
            輸出(x[o],x[1],...,x[k]);
        RBacktrack(k+1);
    }
}

回溯法相關算法:

分支限界法

 

在圖的檢索方法中, BFS 和 D-檢索這兩種方法都是在對當前 E-結點檢測完畢之后才檢 測以隊或棧的形式存放在活結點表中的其它結點。

  • FIFO分枝限界法的活結點表是先進先出隊列,按隊列的FIFO原則選取下一個結點為當前擴展結點;
  • LIFO分枝限界法的活結點表是堆棧(即D-搜索),按棧的LIFO原則選取下一個結點為當前擴展結點,所以一個E結點死后D-會從最后一個活節點處開始;
  • LC(least cost)分枝限界法的活結點表是優先權隊列,按優先權隊列中規定的結點優先級選取優先級最高的下一個結點為當前擴展結點。

將這兩種方法一般化就成為分枝-限界 策略。分枝-限界法是在生成當前 E-結點全部兒子之后再生成其它活結點的兒子且用限界函數幫助避免生成不包含答案結點子樹的狀態空間的檢索方法。在這總的原則下, 根據對 狀態空間樹中結點檢索次序的不同又可將分枝-限界設計策略分為數種不同的檢索方法, 其 中與 BFS 類似的狀態空間檢索稱為 FIFO( first in first out)檢索,它的活結點表采用一張先 進先出表(即隊 ) ;類似於 D-檢索的狀態空間檢索稱為 LIFO(last in first out)檢索, 它的活結點表是一張后進先出表(即棧) 。

類似於回溯法,也是一種在問題的解空間樹T上搜索問題解的算法。但在一般情況下,分支限界法與回溯法的求解目標不同

回溯法的求解目標是找出T中滿足約束條件的所有解,而分支限界法的求解目標則是找出滿足約束條件的一個解,或是在滿足約束條件的解找出使某一目標函數值達到極大或極小的解,即在某種意義下的最優解。

分支限界法常以 廣度優先或以 最小耗費(最大效益)優先的方式搜索問題的解空間樹。
在分支限界法中,每一個活結點只有一次機會成為擴展結點。活結點一旦成為擴展結點,就一次性產生其所有兒子結點。在這些兒子結點中,導致不可行解或導致非最優解的兒子結點被舍棄,其余兒子結點被加入活結點表中。
此后,從活結點表中取下一結點成為當前擴展結點,並重復上述結點擴展過程。這個過程一直持續到找到所需的解或活結點表為空時為止。
 

實現方法

(1)隊列式(FIFO)分支限界法
按照隊列先進先出(FIFO)原則選取下一個節點為擴展節點。
(2)優先隊列式分支限界法
按照優先隊列中規定的優先級選取優先級最高的節點成為當前擴展節點。
 


免責聲明!

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



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