1. 程式人生 > 實用技巧 >【演算法Part2】回溯演算法

【演算法Part2】回溯演算法

一.概念

  回溯演算法實際上一個類似列舉的搜尋嘗試過程,主要是在搜尋嘗試過程中尋找問題的解,當發現已不滿足求解條件時,就 “回溯” 返回,嘗試別的路徑。

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

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

二.基本思想

  回溯演算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。

  • 演算法在包含問題的所有解的解空間樹中,按照深度優先的策略,從根結點出發搜尋解空間樹。演算法搜尋至解空間樹的任一結點時,總是先判斷該結點是否肯定不包含問題的解。如果肯定不包含,則跳過對以該結點為根的子樹的系統搜尋,逐層向其祖先結點回溯。否則,進入該子樹,繼續按深度優先的策略進行搜尋。
  • 回溯法在用來求問題的所有解時,要回溯到根,且根結點的所有子樹都已被搜尋遍才結束。
  • 回溯法在用來求問題的任一解時,只要搜尋到問題的一個解就可以結束。(摘自回溯演算法

三.解題步驟

  1. 針對所給問題,確定問題的解空間:
    首先應明確定義問題的解空間,問題的解空間應至少包含問題的一個(最優)解
  2. 確定結點的擴充套件搜尋規則
  3. 以深度優先方式搜尋解空間,並在搜尋過程中用剪枝函式避免無效搜尋

四.演算法框架

bool finished = FALSE; /* 是否獲得全部解? */
backtrack(int a[], int k, data input)
{
    int c[MAXCANDIDATES]; /*這次搜尋的候選 */
    int ncandidates; /* 候選數目 */
    int i; /* counter */
    if (is_a_solution(a,k,input))
    process_solution(a,k,input);
    else {
        k = k+1;
        construct_candidates(a,k,input,c,&ncandidates);
        for (i=0; i<ncandidates; i++) {
            a[k] = c[i];
            make_move(a,k,input);
            backtrack(a,k,input);
            unmake_move(a,k,input);
            if (finished) return; /* 如果符合終止條件就提前退出 */
        }
    }
}

變數解釋:

  • a[]表示當前獲得的部分解;
  • k表示搜尋深度;
  • input表示用於傳遞的更多的引數;
  • is_a_solution(a,k,input)判斷當前的部分解向量a[1...k]是否是一個符合條件的解
  • construct_candidates(a,k,input,c,ncandidates)根據目前狀態,構造這一步可能的選擇,存入c[]陣列,其長度存入ncandidates
  • process_solution(a,k,input)對於符合條件的解進行處理,通常是輸出、計數等
  • make_move(a,k,input)unmake_move(a,k,input),前者將採取的選擇更新到原始資料結構上,後者把這一行為撤銷。

五.題型解答

1. 求一個集合的所有子集(leecode.78)

//給定一組不含重複元素的整數陣列 nums,返回該陣列所有可能的子集(冪集)
class Solution {
public:
    vector<vector<int>> res;
    vector<int> temp;
    
    void backtrace(vector<int>& nums,int start){
        res.push_back(temp);
        for(int i = start;i < nums.size();++i){
            temp.push_back(nums[i]);
            backtrace(nums,i+1);
            temp.pop_back();
        }
    }
    
    vector<vector<int>> subsets(vector<int>& nums) {
        if(!nums.size())
            return res;
        backtrace(nums,0);
        return res;
    }
};

2.電話號碼的字母組合(leecode.17)

/*
給定一個僅包含數字 2-9 的字串,返回所有它能表示的字母組合。
給出數字到字母的對映如下(與電話按鍵相同)。注意 1 不對應任何字母。
*/
class Solution {
public:
    map<char, string> mp = { { '2', "abc" }, { '3', "def" }, { '4', "ghi" }, { '5', "jkl" },
                             { '6', "mno" }, { '7', "pqrs" }, { '8', "tuv" }, { '9', "wxyz" } };
    vector<string> res;
    string temp;

    void backtrace(string digits, int start){
        if (!digits.size())
            res.push_back(temp);
        else{
            char num = digits[0];
            string letter = mp[num];
            for (int i = 0; i<letter.size(); i++){
                temp.push_back(letter[i]);
                backtrace(digits.substr(1), i + 1);
                temp.pop_back();
            }
        }
    }

    vector<string> letterCombinations(string digits) {
        if (!digits.size())
            return res;
        backtrace(digits, 0);
        return res;
    }
};