1. 程式人生 > >理解回溯法及例題分析

理解回溯法及例題分析

1、對回溯演算法的理解

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

(1)回溯法解題時通常包含3個步驟:

①針對所給問題,定義問題的解空間;

② 確定易於搜尋的解空間結構;

③以深度優先方式搜尋解空間,並在搜尋過程中用剪枝函式避免無效搜尋。

(2)回溯法的演算法框架

①解空間:問題的解空間至少包含問題的一個(最優)解。其表示形式一般是解空間樹:子集樹和排列樹

②遞歸回溯:

 1
void Backtrack(int t) 2 { 3 if(t>n) Output(x); 4 else { 5 for(int i=f(n,t);i<=g(n,t);i++) { 6 x[t]=h(i); 7 if(Constraint(t)&&Bound(t)) Backtrack(t+1); 8 } 9 } 10 }

③迭代回溯:

 1 void IterativeBacktrack(void)
 2 {
 3     int
t=1; 4 while(t>0) { 5 if(f(n,t)<=g(n,t)) { 6 for(int i=f(n,t);i<=g(n,t);i++) { 7 x[t]=h(i); 8 if(Constraint(t)&&Bound(t)) { 9 if(Solution(t)) Output(x); 10 else t++; 11 }
12 } 13 } 14 else t--; 15 } 16 }

2、例題:子集和問題

(1)子集和問題

設集合S={x1,x2,…,xn}是一個正整數集合,c是一個正整數,子集和問題判定是否存在S的一個子集S1,使S1中的元素之和為c。試設計一個解子集和問題的回溯法。

輸入格式:輸入資料第1行有2個正整數n和c,n表示S的大小,c是子集和的目標值。接下來的1行中,有n個正整數,表示集合S中的元素。

輸出格式:輸出子集和問題的解,以空格分隔,最後一個輸出的後面有空格。當問題無解時,輸出“No Solution!”。

輸入樣例:5 10

                  2 2 6 5 4

輸出樣例:2 2 6

(2)解空間

本題的解空間為:{x1,x2,x3,x4,···,xn},其中xi表示是否加上第i個數

(3)約束函式

本題的約束方式有兩部分,①isC + num[i] <= c,通過判斷當前子集和是否超出題目要求的子集和來剪枝

②isC+total >= c,與第一部分類似

(4)具體程式碼

 1 #include <iostream>
 2 using namespace std;
 3 #define N 1000
 4 int n,c,total=0;  // total表示所有整數之和
 5 int isSelect[N]={0};  // 表示整數n是否被選擇,1表示選擇
 6 int num[N];  // 表示整數集
 7 int isC=0;  // 表示當前子集和
 8 
 9 bool sum(int i)
10 {
11     if(isC == c) return true;  // 當子集和符合條件時返回
12     if(i > n) return false;
13     total-=num[i];
14     if(isC + num[i] <= c) {
15         isSelect[i]=1;
16         isC+=num[i];
17         if(sum(i+1)) return true;
18         isC-=num[i];  // 回溯法
19     }
20     if(isC+total >= c) {
21         isSelect[i]=0;  // 回溯法
22         if(sum(i+1)) return true;
23     }
24     total += num[i];  // 回溯法
25     return false;
26 }
27 
28 int main()
29 {
30     cin>>n>>c;
31     for(int i=1;i<=n;i++) {
32         cin>>num[i];
33         total+=num[i];
34     }
35     if(!sum(1)) cout<<"No Solution!";  // 無解時
36     else {
37         for(int i=1;i<=n;i++) {
38             if(isSelect[i]) cout<<num[i]<<" ";
39         }
40     }
41     return 0;
42 } 

3、在本章學習過程中遇到的問題及結對程式設計的情況

 對於回溯法的概念,結合解空間樹,理解起來不是很難,但是在程式碼實踐時,有時候會出現不是很明白為什麼要怎麼寫程式碼的情況,結對小夥伴也不是很明白,總體上還可以,但是還需要多實踐多理解。

參考資料:https://baike.so.com/doc/6735197-6949574.html