在无向带权图G=(V,E)中,结点代表景点,直接到达的景点有边相连,边上的数字代表景点之间的路径长度。找出从出发地开始的一个景点排列,按照这个顺序不重复地走遍所有景点回到出发地,所经过的路径长度最短。
(1)定义问题的解空间
解的形式为n元组:{x1,x2,…,xi,…,xn},分量xi表示第i个要去的旅游景点编号,景点的集合为S={1,2,…,n}。因为景点不可重复走,因此在确定xi时,前面走过的景点{x1,x2,…,xi-1}不可以再走,xi的取值为S−{x1,x2,…,xi-1},i=1,2,…,n。
(2)解空间的组织结构
问题解空间是一棵排列树,树的深度为n=4。
(3)搜索解空间
· 约束条件
用二维数组g[][]存储无向带权图的邻接矩阵,如果g[i][j]≠∞表示城市i和城市j有边相连,能走通。
· 限界条件
cl<bestl,cl的初始值为0,bestf的初始值为+∞。
cl:当前已走过的城市所用的路径长度。
bestl:表示当前找到的最短路径的路径长度。
· 搜索过程
如果采用普通队列式的分支限界法,除了最后一层外,所有的结点都会生成,普通队列式的分支限界法是不可行的。
可以使用优先队列式分支限界法,提高搜索速度。设置优先级:当前已走过的城市所用的路径长度cl。cl越小,优先级越高。
从根结点开始,以广度优先的方式进行搜索。根节点首先成为活结点,一次性生成所有孩子结点,判断孩子结点是否满足约束条件和限界条件,如果满足,则将其加入队列中;反之,舍弃。然后再从队列中取出一个元素,作为当前扩展结点,搜索过程队列为空时为止。
从1号景点出发,经过所有景点一次且只有一次,求最短路径。
(1)时间复杂度
最坏情况下,除了最后一层外,有1+n+n(n−1) +…+ (n−1)(n−2)…2≤n(n−1)!个结点需要判断约束函数和限界函数,判断两个函数需要O(1)的时间,因此耗时O(n!),时间复杂度为O(n!)。
(2)空间复杂度
程序中我们设置了每个结点都要记录当前的解向量x[]数组,占用空间为O(n),结点的个数最坏为O(n!),所以该算法的空间复杂度为O(n*n!)。
算法优化拓展
算法开始时创建一个用于表示活结点优先队列。每个结点的费用下界zl=cl+rl值作为优先级。cl表示已经走过的路径长度,rl表示剩余路径长度的下界,rl用剩余每个结点的最小出边之和来计算。初始时先计算图中每个顶点i的最小出边并用minout[i]数组记录,minsum记录所有结点的最小出边之和。如果所给的有向图中某个顶点没有出边,则该图不可能有回路,算法立即结束。
(1)约束条件
用二维数组g[][]存储无向带权图的邻接矩阵,如果g[i][j]≠∞表示城市i和城市j有边相连,能走通。
(2)限界条件
zl<bestl。
zl=cl+rl。
cl:当前已走过的城市所用的路径长度。
rl:当前剩余路径长度的下界。
bestl:当前找到的最短路径的路径长度。
(3)优先级
设置优先级:zl指已经走过的路径长度+剩余路径长度的下界。zl越小,优先级越高。
//program 6.3 旅行商问题 优先队列式分支限界法
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int inf=1e7;
const int N=105;
int g[N][N];
int bestx[N]; //当前最优路径
int bestl; //当前最短路径长度
int n,m; //结点数,边数
struct node{
int cl; //当前已走过的路径长度
int id; //当前序号
int x[N];//当前路径
node() {}
node(int _cl,int _id){
cl=_cl;
id=_id;
}
};
bool operator <(const node &a, const node &b){//优先队列的优先级,cl值越小越优先
return a.cl>b.cl;
}
void traveling_prioritybfs(){//优先队列式分支限界法
priority_queue<node> q; //创建一个优先队列
node newnode=node(0,2);//创建根节点
for(int i=1;i<=n;i++)
newnode.x[i]=i; //初时化新结点解向量
q.push(newnode);//根结点入队
while(!q.empty()){
node cur=q.top();//取队头
q.pop(); //队头出队
int t=cur.id;//当前处理的序号
if(t==n){//搜到倒数第2个结点时,判断是否更新最优解,
//例如当前找到一个路径(1243),到达4号结点时,立即判断g[4][3]和g[3][1]是否有边相连,
//如果cl+g[4][3]+g[3][1]<bestl,则更新最优值和最优解
if(cur.cl+g[cur.x[n-1]][cur.x[n]]+g[cur.x[n]][1]<bestl){
bestl=cur.cl+g[cur.x[n-1]][cur.x[n]]+g[cur.x[n]][1];
for(int i=1;i<=n;i++)
bestx[i]=cur.x[i];
}
continue;
}
if(cur.cl>=bestl)//判断当前结点是否满足限界条件,如果不满足不再扩展
continue;
for(int j=t;j<=n;j++){//排列树
if(g[cur.x[t-1]][cur.x[j]]!=inf){//如果x[t-1]与x[j]有边相连
int cl=cur.cl+g[cur.x[t-1]][cur.x[j]];
if(cl<bestl){//有可能得到更短的路线
newnode=node(cl,t+1);
for(int i=1;i<=n;i++)
newnode.x[i]=cur.x[i];//复制以前的解向量
swap(newnode.x[t],newnode.x[j]);//交换x[t]、x[j]两个元素的值
q.push(newnode);//新结点入队
}
}
}
}
}
void init(){//初始化
bestl=inf;
memset(g,0x3f,sizeof(g));
memset(bestx,0,sizeof(bestx));
}
void print(){//打印路径
cout<<"最短路径: ";
for(int i=1;i<=n;i++)
cout<<bestx[i]<<"-->";
cout<<"1"<<endl;
}
int main(){
int t;//测试用例数
int u,v,w;//u,v代表结点,w代表u和v之间路的长度;
cin>>t;
while(t--){
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
g[u][v]=g[v][u]=w;
}
traveling_prioritybfs();
cout<<bestl<<endl;
// print();
}
return 0;
}
/*测试数据
1
4 6
1 2 15
1 3 30
1 4 5
2 3 6
2 4 12
3 4 3
*/
//program 6.3 旅行商问题 回溯法
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=1e7;
const int N=105;
int g[N][N];
int x[N],bestx[N]; //记录当前路径,当前最优路径
int cl,bestl; //当前路径长度,当前最短路径长度
int n,m; //城市个数n,边数m
void traveling(int t){//回溯法
if(t>n){ //到达叶子结点
if(g[x[n]][1]!=inf&&(cl+g[x[n]][1]<bestl)){
for(int j=1;j<=n;j++)//记录最优解
bestx[j]=x[j];
bestl=cl+g[x[n]][1];//记录最优值
}
return;
}
for(int j=t;j<=n;j++){
if(g[x[t-1]][x[j]]!=inf&&(cl+g[x[t-1]][x[j]]<bestl)){
swap(x[t],x[j]);//交换两个元素的值
cl=cl+g[x[t-1]][x[t]];
traveling(t+1);
cl=cl-g[x[t-1]][x[t]];//还原现场
swap(x[t],x[j]);//复位
}
}
}
void init(){//初始化
bestl=inf;
cl=0;
memset(g,0x3f,sizeof(g));
memset(bestx,0,sizeof(bestx));
for(int i=0;i<=n;i++)
x[i]=i;
}
void print(){//打印路径
cout<<"最短路径: ";
for(int i=1;i<=n;i++)
cout<<bestx[i]<<"--->";
cout<<"1"<<endl;
}
int main(){
int t;//测试用例数
int u,v,w;//u,v代表结点,w代表u和v之间路的长度;
cin>>t;
while(t--){
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
g[u][v]=g[v][u]=w;
}
traveling(2);
cout<<bestl<<endl;
//print();
}
return 0;
}
/*测试数据
1
5 9
1 2 3
1 4 8
1 5 9
2 3 3
2 4 10
2 5 5
3 4 4
3 5 3
4 5 20
*/