动态规划法回溯法分支限界法求解TSP问答实验报告.docx
《动态规划法回溯法分支限界法求解TSP问答实验报告.docx》由会员分享,可在线阅读,更多相关《动态规划法回溯法分支限界法求解TSP问答实验报告.docx(20页珍藏版)》请在冰豆网上搜索。
动态规划法回溯法分支限界法求解TSP问答实验报告
TSP问题算法实验报告
指导教师:
季晓慧
姓名:
辛瑞乾
学号:
1004131114
提交日期:
2015年11月
总述
TSP问题又称为旅行商问题,是指一个旅行商要历经所有城市一次最后又回到原来的城市,求最短路程或最小花费,解决TSP可以用好多算法,比如蛮力法,动态规划法…具体的时间复杂的也各有差异,本次实验报告包含动态规划法,回溯法以及分支限界法。
动态规划法
算法问题分析
假设n个顶点分别用0~n-1的数字编号,顶点之间的代价存放在数组mp[n][n]中,下面考虑从顶点0出发求解TSP问题的填表形式。
首先,按个数为1、2、…、n-1的顺序生成1~n-1个元素的子集存放在数组x[2^n-1]中,例如当n=4时,x[1]={1},x[2]={2},x[3]={3},x[4]={1,2},x[5]={1,3},x[6]={2,3},x[7]={1,2,3}。
设数组dp[n][2^n-1]存放迭代结果,其中dp[i][j]表示从顶点i经过子集x[j]中的顶点一次且一次,最后回到出发点0的最短路径长度,动态规划法求解TSP问题的算法如下。
算法设计
输入:
图的代价矩阵mp[n][n]
输出:
从顶点0出发经过所有顶点一次且仅一次再回到顶点0的最短路径长度
1.初始化第0列(动态规划的边界问题)
for(i=1;idp[i][0]=mp[i][0]
2.依次处理每个子集数组x[2^n-1]
for(i=1;iif(子集x[j]中不包含i)
对x[j]中的每个元素k,计算d[i][j]=min{mp[i][k]+dp[k][j-1]};
3.输出最短路径长度。
实现代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#definedebug"outputfordebug\n"
#definepi(acos(-1.0))
#defineeps(1e-8)
#defineinf0x3f3f3f3f
#definelllonglongint
#definelsonl,m,rt<<1
#definersonm+1,r,rt<<1|1
usingnamespacestd;
constintmod=1000000007;
constintMax=100005;
intn,mp[20][20],dp[20][40000];
intmain()
{
while(~scanf("%d",&n))
{
intans=inf;
memset(mp,0,sizeofmp);
for(inti=0;i{
for(intj=0;j{
if(i==j)
{
mp[i][j]=inf;
continue;
}
inttmp;
scanf("%d",&tmp);
mp[i][j]=tmp;
}
}
intmx=(1<<(n-1));
dp[0][0]=0;
for(inti=1;i{
dp[i][0]=mp[i][0];
}
dp[0][mx-1]=inf;
for(intj=1;j<(mx-1);j++)
{
for(inti=1;i{
if((j&(1<<(i-1)))==0)
{
intx,y=inf;
for(intk=1;k{
if((j&(1<<(k-1)))>0){
x=dp[k][(j-(1<<(k-1)))]+mp[i][k];
y=min(y,x);
}
}
dp[i][j]=y;
}
}
}
dp[0][mx-1]=inf;
for(inti=1;idp[0][mx-1]=min(dp[0][mx-1],mp[0][i]+dp[i][(mx-1)-(1<<(i-1))]);
printf("%d\n",dp[0][mx-1]);
}
return0;
}
输入输出截图
OJ提交截图
算法优化分析
该算法需要对顶点集合{1,2,…,n-1}的每一个子集进行操作,因此时间复杂度为O(2^n)。
和蛮力法相比,动态规划法求解TSP问题,把原来的时间复杂度是O(n!
)的排列问题,转化为组合问题,从而降低了算法的时间复杂度,但仍需要指数时间。
回溯法
算法问题分析
回溯法求解TSP问题,首先把所有的顶点的访问标志初始化为0,然后在解空间树中从根节点出发开始搜索,如果从根节点到当前结点对应一个部分解,即满足上述约束条件,则在当前结点处选择第一棵子树继续搜索,否则,对当前子树的兄弟结点进行搜索,如果当前结点的所有子树都已尝试过并且发生冲突,则回溯到当前结点的父节点。
采用邻接矩阵mp[n][n]存储顶点之间边的情况,为避免在函数间传递参数,将数组mp设置为全局变量,设数组x[n]表示哈密顿回路经过的顶点。
算法设计
输入:
无向图G=(V,E)
输出:
哈密顿回路
1、将顶点数组x[n]初始化为0,标志数组vis[n]初始化为0;
2、从顶点0出发构造哈密顿回路:
vis[0]=1;x[0]=1;k=1;
3、While(k>=1)
3.1、x[k]=x[k]+1,搜索下一个顶点。
3.2、若n个顶点没有被穷举完,则执行下列操作
3.2.1、若顶点x[k]不在湖密顿回路上并且(x[k-1],x[k])∈E,转步骤3.3;
3.2.2、否则,x[k]=x[k]+1,搜索下一个顶点。
3.3、若数组x[n]已经形成哈密顿路径,则输出数组x[n],算法结束;
3.4、若数组x[n]构成哈密顿路径的部分解,则k=k+1,转步骤3;
3.5、否则,取消顶点x[k]的访问标志,重置x[k],k=k-1,转步骤3。
实现代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#definedebug"outputfordebug\n"
#definepi(acos(-1.0))
#defineeps(1e-8)
#defineinf0x3f3f3f3f
#definelllonglongint
#definelsonl,m,rt<<1
#definersonm+1,r,rt<<1|1
usingnamespacestd;
intmp[20][20];
intx[30],vis[30];
intn,k,cur,ans;
voidinit()
{
for(inti=0;ifor(intj=0;jmp[i][j]=-1;
ans=-1;cur=0;
for(inti=1;i<=n;i++)x[i]=i;
}
voiddfs(intt)
{
if(t==n)
{
if(mp[x[n-1]][x[n]]!
=-1&&(mp[x[n]][1]!
=-1)&&(cur+mp[x[n-1]][x[n]]+mp[x[n]][1]{
for(inti=1;i<=n;i++)
vis[i]=x[i];
ans=cur+mp[x[n-1]][x[n]]+mp[x[n]][1];
}
}
else
{
for(inti=t;i<=n;i++)
{
if(mp[x[t-1]][x[i]]!
=-1&&(cur+mp[x[t-1]][x[i]]{
swap(x[t],x[i]);
cur+=mp[x[t-1]][x[t]];
dfs(t+1);
cur-=mp[x[t-1]][x[t]];
swap(x[t],x[i]);
}
}
}
}
intmain()
{
while(~scanf("%d",&n))
{
init();
for(inti=1;i<=n;i++)
{
for(intj=1;j<=n;j++)
{
if(i==j)
continue;
scanf("%d",&mp[i][j]);
}
}
///puts("YES");
dfs
(2);
cout<}
return0;
}
输入输出截图
OJ提交截图
算法优化分析
在哈密顿回路的可能解中,考虑到约束条件xi!
=xj(1<=I,j<=n,i!
=j),则可能解应该是(1,2,…,n)的一个排列,对应的解空间树种至少有n!
个叶子结点,每个叶子结点代表一种可能解。
当找到可行的最优解时,算法停止。
分支限界法
算法问题分析
分支限界法解决TSP问题首先确定目标函数的界[down,up],可以采用贪心法确定TSP问题的一个上界。
如何求得TSP问题的一个合理的下界呢?
对于无向图的代价矩阵,吧矩阵中每一行最小的元素想家,可以得到一个简单的下界,但是还有一个信息量更大的下界:
考虑一个TSP问题的完整解,在每条路径上,每个城市都有两条邻接边,一条是进入这个城市的,另一条是离开这个城市的,那么,如果把矩阵中每一行最小的两个元素相加在除以2,假设图中所有的代价都是整数,再对这个结果向上取整,就得到一个合理的下界。
需要强调的是,这个解可能不是一个可行解(可能没有构成哈密顿回路),但给出了一个参考下界。
一般情况下,假设当前已确定的路径为U=(r1,r2,…,rk),即路径上已确定了k个顶点,此时,该部分解的目标函数值的计算方法(限界函数)如下:
Lb=(2∑c[ri][r(i+1)]+∑ri行不在路径上的最小元素+∑ri行最小的两个元素)/2
算法设计
输入:
图G=(V,E)
输出:
最短哈密顿回路
1、根据限界函数计算目标函数的下界down;采用贪心法得到上界up;
2、计算根节点的目标函数值并加入待处理结点表PT;
3、循环知道某个叶子结点的目标函数值在表PT中取得极小值
3.1、i=表PT中具有最小值的结点;
3.2、对结点i的每个孩子几点x执行下列操作:
3.2.1估算结点x的目标函数值lb;
3.2.2若(lb<=up),则将结点x加入表PT中;否则丢弃该结点;
4、将叶子结点对应的最优解输出,回溯求得最优解的各个分量。
实现代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#definedebug"outputfordebug\n"
#definepi(acos(-1.0))
#defineeps(1e-8)
#defineinf0x3f3f3f3f
#definelllonglongint
#definelsonl,m,rt<<1
#definersonm+1,r,rt<<1|1
usingnamespacestd;
constintmod=1000000007;
constintMax=100005;
/***
***/
vectorMp[20];
vector:
:
iteratorit;
intmp[20][20];
intvis[20];
intup,low,n,ans;
structnode
{
intx[20],st,ed,dis,val,num;
booloperator<(constnode&p)const
{
returnval
}
};
priority_queueq;
intgetup(intu,intv,intw)
{
if(v==n)returnw+mp[u][1];
intsum=inf,p;
for(inti=1;i<=n;i++)
{
if(!
vis[i]&&sum>mp[u][i])
{
sum=mp[u][i];
p=i;
}
}
vis[p]=1;
returngetup(p,v+1,w+sum);
}
intgetlow()
{
intsum=0;
for(inti=1;i<=n;i++)
{
it=Mp[i].begin();
sum+=*it;
it++;
sum+=*it;
}
returnsum/2;
}
intgao(nodes)
{
intres=s.dis*2;
intt1=inf,t2=inf;
for(inti=1;i<=n;i++)
{
if(!
s.x[i])
{
t1=min(t1,mp[i][s.st]);
t2=min(t2,mp[s.ed][i]);
}
}
res+=t1;
res+=t2;
inttmp;
for(inti=1;i<=n;i++)
{
tmp=inf;
if(!
s.x[i])
{
it=Mp[i].begin();
res+=*it;
for(intj=1;j<=n;j++)
tmp=min(tmp,mp[j][i]);
res+=tmp;
}
}
return!
(res%2)?
(res/2):
(res/2+1);
}
voidbfs(nodes)
{
q.push(s);
while(!
q.empty())
{
nodehead=q.top();
q.pop();
if(head.num==n-1)
{
intp;
for(inti=1;i<=n;i++)
{
if(!
head.x[i])
{
p=i;
break;
}
}
intcnt=head.dis+mp[p][head.st]+mp[head.ed][p];
nodetmp=q.top();
if(cnt<=tmp.val)
{
ans=min(ans,cnt);
return;
}
else
{
up=min(up,cnt);
ans=min(ans,cnt);
continue;
}
}
nodetmp;
for(inti=1;i<=n;i++)
{
if(!
head.x[i])
{
tmp.st=head.st;
tmp.dis=head.dis+mp[head.ed][i];
tmp.ed=i;
tmp.num=head.num+1;
for(intj=1;j<=n;j++)tmp.x[j]=head.x[j];
tmp.x[i]=1;
tmp.val=gao(tmp);
if(tmp.val<=up)
q.push(tmp);
}
}
}
}
intmain()
{
while(~scanf("%d",&n))
{
for(inti=0;i<=n;i++)
Mp[i].clear();
for(inti=1;i<=n;i++)
{
for(intj=1;j<=n;j++)
{
if(i!
=j)
{
scanf("%d",&mp[i][j]);
Mp[i].push_back(mp[i][j]);
}
else
mp[i][j]=inf;
}
sort(Mp[i].begin(),Mp[i].end());
}
memset(vis,0,sizeofvis);
vis[1]=1;
up=0;
up+=getup(1,1,up);
low=getlow();
nodefir;
fir.st=1;
fir.ed=1;
fir.num=1;
for(inti=1;i<=n;i++)fir.x[i]=0;
fir.x[1]=1;
fir.dis=0;
fir.val=low;
ans=inf;
bfs(fir);
printf("%d\n",ans);
}
return0;
}
输入输出截图
OJ提交截图
算法优化分析
分支限界法的复杂度是根据数据的不同而不同,搜索的节点越少,复杂度越低,跟目标函数的选择有很大关系。
目标函数值的计算也会需要一定时间,比如此文章中的目标函数值求解的复杂度是O(n^2)。
当然此算法仍然有可以优化的地方,比如在记录该路径每个叶子结点的孩子结点时可以采用二进制的思想,从而使算法的时间复杂度在下降到当前T/n的数量级,解决COJ1814问题应该不成问题。
总结
TSP问题在很多地方都可以运用到,并且好多问题都是由TSP问题延伸和发展的,也可以称之为TSP问题,不过其思路大致相似,于是我们可以运用已学过的算法对其进行解决。
我在学习算法课以前的TSP问题大都用动态规划以及回溯法,究其时间复杂度以及代码的复杂度比较低,思路比较清晰,在解决此类延伸问题时容易调试和修改。
学完算法后最有感触的一点就是,算法的精髓并不在于其方式方法,而在于其思想思路。
有了算法的思想,那么潜移默化中问题就可以得到解决。