分支限界法與回溯法
(1)求解目標:回溯法的求解目標是找出解空間樹中滿足約束條件的所有解,而分支限界法的求解目標則是找出滿足約束條件的一個解,或是在滿足約束條件的解中找出在某種意義下的最優解。
(2)搜索方式的不同:回溯法以深度優先的方式搜索解空間樹,而分支限界法則以廣度優先或以最小耗費優先的方式搜索解空間樹。
分支限界法的基本思想
分支限界法常以廣度優先或以最小耗費(最大效益)優先的方式搜索問題的解空間樹。
在分支限界法中,每一個活結點只有一次機會成為擴展結點。活結點一旦成為擴展結點,就一次性產生其所有兒子結點。在這些兒子結點中,導致不可行解或導致非最優解的兒子結點被舍棄,其余兒子結點被加入活結點表中。
此后,從活結點表中取下一結點成為當前擴展結點,並重復上述結點擴展過程。這個過程一直持續到找到所需的解或活結點表為空時為止。
常見的兩種分支限界法
(1)隊列式(FIFO)分支限界法
按照隊列先進先出(FIFO)原則選取下一個結點為擴展結點。
(2)優先隊列式分支限界法
按照優先隊列中規定的優先級選取優先級最高的結點成為當前擴展結點。
一、單源最短路徑問題
1、問題描述
在下圖所給的有向圖G中,每一邊都有一個非負邊權。要求圖G的從源頂點s到目標頂點t之間的最短路徑。
下圖是用優先隊列式分支限界法解有向圖G的單源最短路徑問題產生的解空間樹。其中,每一個結點旁邊的數字表示該結點所對應的當前路長。
找到一條路徑:
目前的最短路徑是8,一旦發現某個結點的下界不小於這個最短路進,則剪枝:
同一個結點選擇最短的到達路徑:
2.剪枝策略
在算法擴展結點的過程中,一旦發現一個結點的下界不小於當前找到的最短路長,則算法剪去以該結點為根的子樹。
在算法中,利用結點間的控制關系進行剪枝。從源頂點s出發,2條不同路徑到達圖G的同一頂點。由於兩條路徑的路長不同,因此可以將路長長的路徑所對應的樹中的結點為根的子樹剪去。
3.算法思想
解單源最短路徑問題的優先隊列式分支限界法用一極小堆來存儲活結點表。其優先級是結點所對應的當前路長。
算法從圖G的源頂點s和空優先隊列開始。結點s被擴展后,它的兒子結點被依次插入堆中。此后,算法從堆中取出具有最小當前路長的結點作為當前擴展結點,並依次檢查與當前擴展結點相鄰的所有頂點。如果從當前擴展結點i到頂點j有邊可達,且從源出發,途經頂點i再到頂點j的所相應的路徑的長度小於當前最優路徑長度,則將該頂點作為活結點插入到活結點優先隊列中。這個結點的擴展過程一直繼續到活結點優先隊列為空時為止。
代碼實現

1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 #include<queue> 5 #include<vector> 6 using namespace std; 7 #define MAXN 100 8 #define INF 0x3f3f3f3f 9 10 int a[MAXN][MAXN]; //存儲圖 11 int dist[MAXN], vis[MAXN]; //dist[]點到當前點的距離, vis[]用來判斷是否走過當前路徑 12 vector<int> pre[MAXN]; //存儲前驅結點 13 vector<int> ans; //用於存儲結果路徑 14 int n, m, s, t; //n個結點,m條邊,s是源點, t為目標點 15 16 struct NodeType{ 17 int vno; 18 int length; 19 bool operator<(const NodeType &e) const 20 { 21 return length > e.length; //length越小越優先出隊 22 } 23 }; 24 25 void Init() 26 { 27 int x, y, len; 28 cout << "請依次輸入結點個數、邊數、源點、目標點" << endl; 29 cin >> n >> m >> s >> t; 30 memset(dist, INF, sizeof(dist)); 31 memset(a, INF, sizeof(a)); 32 memset(vis, 0, sizeof(vis)); 33 for(int i = 0; i < m; i++) //建圖--有向圖 34 { 35 cin >> x >> y >> len; 36 a[x][y] = len; 37 } 38 } 39 40 void bfs() 41 { 42 NodeType e, e1; 43 queue<NodeType> pqu; 44 e.vno = s; 45 e.length = 0; 46 pqu.push(e); 47 dist[s] = 0; 48 49 while(!pqu.empty()) 50 { 51 e = pqu.front(); 52 pqu.pop(); 53 for(int j = 0; j < n; j++) 54 { 55 if(a[e.vno][j] < INF && e.length+a[e.vno][j] < dist[j] && !vis[j]) 56 { //剪枝:e.vno到頂點j有邊並且路徑長度更短 57 dist[j] = e.length + a[e.vno][j]; 58 pre[j].clear(); 59 pre[j].push_back(e.vno); 60 e1.vno = j; //建立相鄰點j的結點e1 61 e1.length = dist[j]; 62 pqu.push(e1); 63 } 64 else if(a[e.vno][j] < INF && e.length+a[e.vno][j] == dist[j] && !vis[j]) 65 { 66 pre[j].push_back(e.vno); 67 e1.vno = j; //建立相鄰點j的結點e1 68 e1.length = dist[j]; 69 pqu.push(e1); 70 } 71 } 72 73 } 74 75 } 76 77 void OutPutPath(int i) 78 { 79 for(int j = 0; j < pre[i].size(); j++) 80 { 81 ans.push_back(pre[i][j]); 82 if(pre[i][j] == s) 83 { 84 for(int k = ans.size()-1; k >= 0; k--) 85 { 86 cout << ans[k] <<" "; 87 } 88 cout <<"\n"; 89 } 90 OutPutPath(pre[i][j]); //往下一層 91 ans.pop_back(); //刪除上個元素 92 } 93 } 94 95 void OutPut() 96 { 97 cout << "點" << s << "到"<< t << "的最短距離是" << dist[t] << endl; 98 cout << "路徑為:"; 99 OutPutPath(t); 100 } 101 102 int main() 103 { 104 Init(); 105 bfs(); 106 ans.push_back(t); 107 OutPut(); 108 return 0; 109 } 110 /* 111 6 8 0 5 112 0 2 10 113 0 4 30 114 0 5 60 115 2 3 50 116 4 3 20 117 4 5 60 118 3 5 10 119 1 2 4 120 */ 121
測試數據的圖
測試結果
請依次輸入結點個數、邊數、源點、目標點 6 8 0 5 0 2 10 0 4 30 0 5 60 2 3 50 4 3 20 4 5 60 3 5 10 1 2 4 點0到5的最短距離是60 路徑為:0 5 0 4 3 5