分支限界法与回溯法
(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 #include2 #include 3 #include 4 #include 5 #include 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 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