1-5、算法設計常用思想之窮舉法


文章內容來自王曉華老師

 

窮舉法又稱窮舉搜索法,是一種在問題域的解空間中對所有可能的解窮舉搜索,並根據條件選擇最優解的方法的總稱。數學上也把窮舉法稱為枚舉法,就是在一個由有限個元素構成的集合中,把所有元素一一枚舉研究的方法。

使用窮舉法解決問題,基本上就是以下兩個步驟:
  • 確定問題的解(或狀態)的定義、解空間的范圍以及正確解的判定條件;
  • 根據解空間的特點來選擇搜索策略,逐個檢驗解空間中的候選解是否正確;


解空間的定義
解空間就是全部可能的候選解的一個約束范圍,確定問題的解就在這個約束范圍內,將搜索策略應用到這個約束范圍就可以找到問題的解。要確定解空間,首先要定義問題的解並建立解的數據模型。如果解的數據模型選擇錯誤或不合適,則會導致解空間結構繁雜、范圍難以界定,甚至無法設計窮舉算法。
窮舉解空間的策略
窮舉解空間的策略就是搜索算法的設計策略,根據問題的類型,解空間的結構可能是線性表、集合、樹或者圖,對於不同類型的解空間,需要設計與之相適應的窮舉搜索算法。簡單的問題可以用通用的搜索算法,比如線性搜索算法用於對線性解空間的搜索,廣度優先和深度優先的遞歸搜索算法適用於樹型解空間或更復雜的圖型解空間。

盲目搜索和啟發式搜索
對於線性問題的盲目搜索,就是把線性表中的所有算法按照一定的順序遍歷一遍,對於復雜問題的盲目搜索,常用廣度優先搜索和深度優先搜索這兩種盲目搜索算法。
如果搜索能夠智能化一點,利用搜索過程中出現的額外信息直接跳過一些狀態,避免盲目的、機械式的搜索,就可以加快搜索算法的收斂,這就是啟發性搜索。啟發性搜索需要一些額外信息和操作來“啟發”搜索算法,根據這些信息的不同,啟發的方式也不同。


剪枝策略
對解空間窮舉搜索時,如果有一些狀態節點可以根據問題提供的信息明確地被判定為不可能演化出最優解,也就是說,從此節點開始遍歷得到的子樹,可能存在正確的解,但是肯定不是最優解,就可以跳過此狀態節點的遍歷,這將極大地提高算法的執行效率,這就是剪枝策略,應用剪枝策略的難點在於如何找到一個評價方法(估值函數)對狀態節點進行評估。特定的評價方法都附着在特定的搜索算法中,比如博弈樹算法中常用的極大極小值算法和“α-β”算法,都伴隨着相應的剪枝算法。

剪枝和啟發
剪枝不是啟發性搜索。剪枝的原理是在結果已經搜索出來或部分搜索出來(比如樹的根節點已經搜索出來了,但是葉子節點還沒有搜索出來)的情況下,根據最優解的判斷條件,確定這個方向上不可能存在最優解,從而放棄對這個方向的繼續搜索。而啟發性搜索通常是根據啟發函數給出的評估值,在結果出來之前就朝着最可能出現最優解的方向搜索。它們的差異點在於是根據結果進行判斷還是根據啟發函數的評估值進行判斷。

搜索算法的評估和收斂
收斂原則是只要能找到一個比較好的解就返回(不求最好),根據解的評估判斷是否需要繼續下一次搜索。大型棋類游戲通常面臨這種問題,比如國際象棋和圍棋的求解算法,想要搜索整個解空間得到最優解目前是不可能的,所以此類搜索算法通常都通過一個搜索深度參數來控制搜索算法的收斂,當搜索到指定的深度時(相當於走了若干步棋)就返回當前已經找到的最好的結果,這種退而求其次的策略也是不得已而為之

百錢買雞問題
一百個錢買一百只雞,是個典型的窮舉法應用。問題描述:每只大公雞值 5 個錢,每只母雞值 3 個錢,每 3 只小雞值 1 個錢,現在有 100 個錢,想買 100 只雞,問如何買?有多少種方法?

void Buy()
{
    int count = 0;

    for (int roosters = 0; roosters <= 20; roosters++)   //枚舉大公雞數量
    {
        for (int hens = 0; hens <= 33; hens++) //枚舉母雞數量
        {
            int chicks = 100 - roosters - hens;  //剩下的就是小雞數量
            if (((chicks % 3) == 0) //小雞個數應該是 3 的整數倍,算是個小小的剪枝
                && ((5 * roosters + 3 * hens + chicks / 3) == 100)) //是否湊夠 100 錢
            {
                count++;
                std::cout << "買法 " << count << ":公雞 " << roosters
                                              << ", 母雞 " << hens
                                              << ", 小雞 " << chicks << std::endl;
            }
        }
    }

    std::cout << "共有 " << count << " 種買法" << std::endl;
}
-- lua實現
function bug()
    local count = 0
    for roosters = 0, 20 do
        for hens = 0, 33 do
            local chicks = 100 - roosters - hens
             if (((chicks % 3) == 0) and ((5 * roosters + 3 * hens + chicks / 3) == 100)) then
                 count = count + 1
                 print("====買法", count, roosters, hens, chicks)
             end
        end
    end
    print("====共有買法", count)
end

 

雞兔同籠問題

有雞和兔在一個籠子中,數頭共 50 個頭,數腳共 120 只腳,問:雞和兔分別有多少只?

for ji = 1, 50 do
    local tu = 50 - ji
    if (tu * 4 + ji * 2) == 120 then
        print("===========雞 兔各", ji, tu)
        break
    end
end

 


免責聲明!

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



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