算法設計與分析報告


算法分析與設計論文

 

以大學生程序設計競賽為例

 

 

 

 

姓名:於港添

學    號:2015338

專    業:信息與計算科學

學    校:山東農業大學

授課老師:費玉奎

 

 

 

 

 

前言:

這門課程主要講了貪心、遞歸、回溯、分支定界、動態規划等幾種算法。

在進行學習之前有做過相關題目,所以在聽課的時候感覺好理解了許多。沒學這門課的時候總是想因為沒學ACM課感到惋惜。

  1.貪心算法

    貪心算法算是DP問題的一個分支了。確定貪心思路,確定貪心標准是其核心。貪心算法的特點在於通過局部最優達到總體最優。也可以理解貪心算法並不是從總體上考慮,它所做出的選擇只是在某種意義上的局部最優解算法。

    從全局來看,貪心算法只是每一步都確定當前最優的選擇然后進行下一步的選擇,並沒有回溯過程。所以后面的每一步都是當前看似最佳的選擇並不一定能產生整體的最優,這就限制了它只能適用於部分問題的解決。

    貪心算法雖然局限性比較大,但是思慮清晰,運算容易。貪心類的題目,按照一定的貪心標准求解時出現問題則要考慮是否是貪心標准有誤。考慮是否缺少某種情況和是否需要更換貪心標准。

    貪心算法可適用於以下幾種問題:

      1.0/1背包問題:給定背包容量M,n件物品,物品有屬性體積Wi,價值Vi,使得背包所裝價值最大。以體積W或者以價值V作為貪心的標准均有缺陷。那么性價比則是最合適的貪心標准。

      2.最優裝載問題:  有一批集裝箱要裝到一艘重量為c的輪船,其中集裝箱i的重量為wi,最優裝載問題要求確定在裝載體積不受限制的情況下,盡可能多的集裝箱裝上輪船。

      3.連續最值子和問題:(大)給定數組A[ ],求該數組的連續子和的最大值?

      4.單源最短路徑問題

      .......

  2.遞歸算法

    說實話,我感覺遞歸算法是最高深最有操作的一種算法。為什么這么說?DFS算法也可以看成一種遞歸。而且遞歸也可以用來DP,記得Project Euler做過一道題,題意大體是:給定一個整數n,n可以被表示為下述多種情況

        1+1+1+1+.....+1(n個1相加)

        2+1+1+.....+1

        2+2+.....+1

        .......

        (n-1)+1

        n

    也就是說n可以被表示成任意個正整數的和,求可以被表示的情況數。每個階段都是在拆分n,  n-1和1,n-2和2........n-x和x。拆成n-x和x的時候,計算n-x的情況數和x的情況數。很討厭的一道題。可不得不說,遞歸就是牛。還有一道數乘積的問題,也是用遞歸求解,像這種題目做起來應該會不好受。

    話說回來,遞歸算法就是在函數體內重復調用該函數。所以一定要先想好思路,遞歸的出口、滿足各種條件該進行的操作。

    遞歸操作有不少,有許多普通操作和“騷操作“,像一些普通的數學公式、斐波那契數列、階乘或回溯。這類一眼大體能看出來的就比較容易想,不太會迷糊。確定出口就可以了。所謂的“騷操作”就比如上述的例子。情況多變,求解特別復雜的那種。

    設計思路:

      遞歸算法的設計思路就是要解決一個規模為n的問題,先看規模為n-1(或者n-k或者n/2,總之是比原問題規模小)的問題是否和原問題有同樣的性質(縮小問題規模),如果性質相同,那這個問題應該可以用遞歸算法解決。

    遞歸算法可適用以下幾種問題:

      1.數學公式(例:階乘n!)

        int fun(int n)

        {    if(n==1) return 1;

          return n*fun(n-1);

        }

      2.漢諾塔

        漢諾塔問題比較常見,有n個大小不等的盤子放在一個塔A上面,自上而下按照從小到大的順序排列。要求將所有n個盤子搬到另一個塔C上面,可以借助一個塔B中轉,但是要滿足任何時刻大盤子不能放在小盤子上面。

        基本思想,當前步未解決的小盤子的塔,中轉塔,已解決的塔。先把上面的N-1個盤子經C移到B,然后將最底下的盤子移到C,再講B上面的N-1個盤子經A移動到C。可以看作每次只考慮當前的未解決的最大盤子。

      3.樹形數據結構

        二叉樹、線段樹、樹狀數組建樹。樹的一系列算法這里就不詳舉了。

      4.“騷操作”系列

    3.回溯算法

      回溯法一直被我理解為深度優先遍歷(DFS),也是一種剪枝枚舉法,歸為暴力。回溯算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。用俗語來說就是不到黃河不死心,再加上限界函數讓它早點見到黃河。回溯法的靈魂:恢復現場!!!

      回溯法分為遞歸型和非遞歸型,但不論哪一種都要在搜索中恢復現場。說到遞歸型那就很典型了,各種走迷宮。這種題型非常好理解,就是上述說的找黃河嘛。非遞歸型的例如:給你一串數字123,你要將它分開,分成至多有兩個數相連 [["1","2","3"],["12","3"],["1","23"]]。貼上代碼:

      

class Solution {

public:

    vector <string> a;

    vector<vector<string> > ans;

    void back(int t,int n,string &s)

    {

        if(!s.size()){

          ans.push_back(a);

          return ;

        }

        if(t>=s.size())    

          return ;

        else{

          if(n==1){

            string c="";

            c=c+s[t];

            a.push_back(c);

            if(t+1==s.size()){

              ans.push_back(a);

            }

            t++;

            back(t,1,s);

            t++;

            back(t,2,s);

            t-=2;

            a.erase(a.end()-1);

        }

          else{

            string c="";

            c=c+s[t-1]+s[t];

            a.push_back(c);

            if(t+1==s.size()){

               ans.push_back(a);

            }

            t++;

            back(t,1,s);

            t++;

            back(t,2,s);

            t-=2;

            a.erase(a.end()-1);

            }

        }

    return ;   

    }

    vector<vector<string> > splitString(string& s) {

        back(0,1,s);

        if(s.size()>1)

            back(1,2,s);

        return ans;

    }

};

      回溯法無疑可以得到正確答案,可其缺點就是復雜度太高,很多題型數據一大就不能用了。  //注意爆棧

  4.分支定界法

    分支定界 (branch and bound) 算法是一種在問題的解空間樹上搜索問題的解的方法。但與回溯算法不同,分支定界算法采用廣度優先或最小耗費優先的方法搜索解空間樹,並且,在分支定界算法中,每一個活結點只有一次機會成為擴展結點。

利用分支定界算法對問題的解空間樹進行搜索,它的搜索策略是:

      1 .產生當前擴展結點的所有孩子結點;

      2 .在產生的孩子結點中,拋棄那些不可能產生可行解(或最優解)的結點;

      3 .將其余的孩子結點加入活結點表;

      4 .從活結點表中選擇下一個活結點作為新的擴展結點。

    如此循環,直到找到問題的可行解(最優解)或活結點表為空。

    從活結點表中選擇下一個活結點作為新的擴展結點,根據選擇方式的不同,分支定界算法通常可以分為兩種形式:

      1 . FIFO(First In First Out) 分支定界算法:按照先進先出原則選擇下一個活結點作為擴展結點,即從活結點表中取出結點的順序與加入結點的順序相同。

      2 .最小耗費或最大收益分支定界算法:在這種情況下,每個結點都有一個耗費或收益。如果要查找一個具有最小耗費的解,那么要選擇的下一個擴展結點就是活結點表中具有最小耗費的活結點;如果要查找一個具有最大收益的解,那么要選擇的下一個擴展結點就是活結點表中具有最大收益的活結點。

     分支定界法的典型問題也是迷宮求最小步數等等,有時也可以用來對數據進行預處理。

    相信剛開始接觸搜索算法的人,都做過類似迷宮這樣的題目吧。我們在“走迷宮”的時候,一般回溯法思路是這樣的:   

      1、這個方向有路可走,我沒走過  
      2、往這個方向前進  
      3、是死胡同,往回走,回到上一個路口  
      4、重復第一步,直到找着出口  
    這樣的思路很好理解,編程起來也比較容易。但是當迷宮的規模很大時,回溯法的缺點便暴露無遺:搜索耗時極巨,無法忍受。  

    回溯法與分支定界法都屬搜索算法,也都經常用到剪枝函數,剪枝函數用來減少非最優或者不正確結果的搜索來提高效率。剪枝的原則: 
      1、正確性  
      正如上文所述,枝條不是愛剪就能剪的。如果隨便剪枝,把帶有最優解的那一分支也剪掉了的話,剪枝也就失去了意義。所以,剪枝的前提是一定要保證不丟失正確的結果。  
      2、准確性  
      在保證了正確性的基礎上,我們應該根據具體問題具體分析,采用合適的判斷手段,使不包含最優解的枝條盡可能多的被剪去,以達到程序最優化的目的。可以說,剪枝的准確性,是衡量一個優化算法好壞的標准。  
      3、高效性   設計優化程序的根本目的,是要減少搜索的次數,使程序運行的時間減少。但為了使搜索次數盡可能的減少,我們又必須花工夫設計出一個准確性較高的優化算法,而當算法的准確性升高,其判斷的次數必定增多,從而又導致耗時的增多,這便引出了矛盾。  
    因此,如何在優化與效率之間尋找一個平衡點,使得程序的時間復雜度盡可能降低,同樣是非常重要的。倘若一個剪枝的判斷效果非常好,但是它卻需要耗費大量的時間來判斷、比較,結果整個程序運行起來也跟沒有優化過的沒什么區別,這樣就太得不償失了。   
    綜上所述,我們可以把剪枝優化的主要原則歸結為六個字:正確、准確、高效。   
    5.動態規划

    動態規划算法的基本思想是:自頂向下(多階段決策)將待求解的問題分解成若干個相互聯系的子問題,先求解子問題,然后從這些子問題的解得到原問題的解;對於重復出現的子問題,只在第一次遇到的時候對它進行求解,並把答案保存起來,讓以后再次遇到時直接引用答案,不必重新求解。動態規划算法將問題的解決方案視為一系列決策的結果,與貪心算法不同的是,在貪心算法中,每采用一次貪心標准,便做出一個不可撤回的決策;而在動態規划算法中,還要考察每個最優決策序列中是否包含一個最優決策子序列,即問題是否具有最優子結構性質。 

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

    在我的理解中,其實就是有動態判斷條件(而不是只有終止條件)的遞歸或循環。就比如典型的N皇后問題,在N*N的棋盤上,放置N個皇后,使其不同行不同列,不同對角。他的思想是每擴展棋盤一圈(N增大1)都根據前一狀態N來計算,所以當然也是可以用DP的,但是會有很多重復的情況,所以更適合於用回溯,把不合格的情況提前掐掉。

    另外一個典型的問題就是上樓梯,有N級台階,每次只能上一步或者兩步,求到第N階台階有多少種策略。當前解取決於上一步和上兩步的解之和。

    其實根據這兩道題目就可以發現,動態規划是一種解決問題的思想,而不是像搜索等算法有固定解題方法。

 

 


免責聲明!

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



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