今天我们通过旅行商问题学习分支限界法的思想与一般流程,并将其与回溯法进行一定的对比。
题目描述
在如上图例中,共有四个节点代表四个城市,节点与节点之间有一条赋权的边,代表城市与城市之间的道路长度。
现在要求从一个城市开始,走遍所有城市并回到起点城市所走过的最小路径。
题目分析
首先我们知道,最终形成的路径是一个闭环,这也便意味着无论选择哪个起点,最终的最短路径是一样的。在这里,我们选择节点 作为起点进行求解。步骤如下:
1)对定义每个节点的结构体,其包括当前节点的编号 ,已走路程(实际位移长度),向下走完全程的下界位移长度(一般取不到)。已走过的路线数组 ,已走过的节点数组 ,并重载了大于号(后续优先队列的排序)。
2)计算每个节点的最小出边。
3)进行最优选择的广度搜素。首先建立根节点并将其置入优先队列。若队列不空,每次提取出 数值最小的节点,这个节点意味着在目前情况下能够达到的下界值中数值最小的节点,即当前来看期望最优的路线。然后以此为父节点,将其子节点更新数值并加入优先队列,依次递推。当走过全程时,更新答案,并每次操作新节点时将新节点的下界值与答案对比,若大于则跳出,若当前节点已走完全程且新答案小于目前答案,则更新答案。注意对路线数组 的更新。
Accept代码
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int g[N][N], mi[N];
int ans = 100;
vector<int> f;
int n = 4;
struct P
{
int idx;
int u, d;
vector<int> v;
bool st[N];
bool operator> (const P& f)const
{
return u + d > f.u + f.d;
}
} ;
int main()
{
g[1][2] = 30, g[1][3] = 6, g[1][4] = 4;
g[2][1] = 30, g[2][3] = 5, g[2][4] = 10;
g[3][1] = 6, g[3][2] = 5, g[3][4] = 20;
g[4][1] = 4, g[4][2] = 10, g[4][3] = 20;
priority_queue<P, vector<P>, greater<P>> pq;
int s = 0;
for (int i = 1; i <= n; i ++)
{
mi[i] = 100;
for (int j = 1; j <= n; j ++)
if (g[i][j]) mi[i] = min(mi[i], g[i][j]);
s += mi[i];
}
P root; root.idx = 1;
root.u = 0, root.d = s;
root.v.push_back(1);
memset(root.st, false, sizeof root.st); root.st[1] = true;
pq.push(root);
while (!pq.empty())
{
P t = pq.top(); pq.pop();
if (t.u + t.d >= ans) break;
if (t.v.size() == n)
{
P x = t; x.idx = 1;
x.u += g[t.idx][1], x.d -= mi[t.idx];
x.v.push_back(1);
pq.push(x);
continue;
}
else if (t.v.size() == n + 1)
{
if (ans > t.u) ans = t.u, f = t.v;
else break;
continue;
}
for (int i = 1; i <= n; i ++)
{
if (!t.st[i])
{
P x = t; x.idx = i;
x.u += g[t.idx][i], x.d -= mi[t.idx];
x.v.push_back(i); x.st[i] = true;
pq.push(x);
}
}
}
cout << ans << "\n";
for (auto i : f) cout << i << ' ';
return 0;
}