TSP问题的解决方案文档格式.docx
《TSP问题的解决方案文档格式.docx》由会员分享,可在线阅读,更多相关《TSP问题的解决方案文档格式.docx(16页珍藏版)》请在冰豆网上搜索。
![TSP问题的解决方案文档格式.docx](https://file1.bdocx.com/fileroot1/2022-12/10/031df689-60ea-4b2a-98d9-13579e25149d/031df689-60ea-4b2a-98d9-13579e25149d1.gif)
d(i,V’)=min{Cik+
d(k,V’-{k})}
注:
Cik表示你选择的城市和城市i的距离,d(k,V’-{k})是一个子问题。
综上所述,TSP问题的动态规划方程就出来了:
和蛮力法相比,动态规划求解tsp问题,把原来时间复杂性O(n!
)的排列转化为组合问题,从而降低了时间复杂度,但仍需要指数时间。
3、回溯法
确定了解空间的组织结构后,回溯法从开始结点(根结点)出发,以深度优先方式搜索整个解空间。
这个开始结点成为活结点,同时也成为当前的扩展结点处,搜索向纵深方向移至一个新结点。
这个新结点即成为新的活结点,并为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。
此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。
回溯法以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已无活结点时为止。
回溯法求解TSP问题,首先把所有的顶点的访问标志初始化为0,然后在解空间树中从根节点出发开始搜索,如果从根节点到当前结点对应一个部分解,即满足上述约束条件,则在当前结点处选择第一棵子树继续搜索,否则,对当前子树的兄弟结点进行搜索,如果当前结点的所有子树都已尝试过并且发生冲突,则回溯到当前结点的父节点。
采用邻接矩阵mp[n][n]存储顶点之间边的情况,为避免在函数间传递参数,将数组mp设置为全局变量,设数组x[n]表示哈密顿回路经过的顶点。
在哈密顿回路的可能解中,考虑到约束条件xi!
=xj(1<
=I,j<
=n,i!
=j),则可能解应该是(1,2,…,n)的一个排列,对应的解空间树种至少有n!
个叶子结点,每个叶子结点代表一种可能解。
当找到可行的最优解时,算法停止。
根据递归条件不同时间复杂度也会不同,这里为O(n!
)。
4、分支限界法
分支界限法以广度优先或以最小耗费(最大效益)优势的方式搜索问题的解空间树。
问题的解空间树是表示问题解空间的一棵有序树,常见的有子集树和排列树。
在搜索问题的解空间树时,分支界限法与回溯法的主要区别在于他们对当前扩展结点所采用的扩展方式不同。
在分支界限法中,每一个活结点只有一次机会成为扩展结点。
活结点一旦成为扩展结点,就一次性产生其所有儿子结点。
在这些儿子结点中,导致不可行解或导致非最优解得儿子结点被舍弃,其余儿子结点被加入活结点表中。
算法开始时创建一个最小堆,用于表示活结点优先队列。
堆中每个结点的子树费用的下界lcost值是优先队列的优先级。
接着算法计算出图中每个顶点的最小费用出边并用minout记录。
如果所给的有向图中某个顶点没有出边,则该图不可能有回路,算法即告结束。
如果每个顶点都有出边,则根据计算出的minout作算法初始化。
目标函数(限界函数),lb分为三部分,第一部分是经过路径的长度相加的2倍,加上第二部分离着路径首尾节点最近的距离相加(不在已知路径上的),加上第三部分除了路径上节点,矩阵中两个最短的距离相加,最后这三部分和相加,得到的结果除以2便是每个节点的限界值。
由于限界函数的不同,下界为O(n),上界为O(2^n),智力特定指出。
三、源程序及注释:
intmain()
{
inti,j,s=0;
int**a;
printf("
输入节点个数:
\n"
);
scanf("
%d"
&
n);
输入%d维对称矩阵:
n);
colable=(int*)malloc((sizeof(int))*n);
colable[0]=0;
//对各列允许矩阵进行赋值
for(i=1;
i<
n;
i++)
{
colable[i]=1;
}
row=(int*)malloc((sizeof(int))*n);
for(i=0;
row[i]=1;
a=(int**)malloc((sizeof(int*))*n);
a[i]=(int*)malloc((sizeof(int*))*n);
for(j=0;
j<
j++)
a[i][j])'
i=0;
while(row[i]==1)
j=min(a[i]);
row[i]=0;
colable[j]=0;
访问路径:
\t%d-->
%d\n"
i,j);
s=s+a[i][j];
i=j;
最短总距离为:
s);
}
intmin(int*a)
intj=0,m=a[0],k=0;
while(colable[j]==0||row[j]==0)
j++;
m=a[j];
}//求最短距离
for(;
if(colable[j]==1&
&
row[j]==1)//节点没有被访问
if(m>
=a[j])
//m始终保持最短距离
k=j;
returnk;
intinit()
inti;
intj;
intt;
if(scanf("
n)==EOF)
return-1;
i<
i++)
j<
j++)
if(i==j)
continue;
g[i][j]);
memset(con,-1,sizeof(con));
bit[i]=1<
<
i;
t=1;
con[t<
(i-1)][i]=g[0][i];
return1;
intgetcon(ints,intk)
intt,tt;
intmin=INF;
if(con[s][k]!
=-1)
returncon[s][k];
t=s&
(~bit[k-1]);
tt=t&
bit[i-1];
if(tt>
0)
if(getcon(t,i)+g[i][k]<
min)
min=getcon(t,i)+g[i][k];
con[s][k]=min;
3.回溯法
voidbacktrack(inti)
{
if(i>
n)
{
if(graph[road[n]][1]!
=INF&
(ans+graph[road[n]][1])<
bestans)
bestans=ans+graph[road[n]][1];
for(intj=1;
=n;
j++)bestroad[j]=road[j];
}
else
j++)
if(graph[road[i-1]][j]!
ans+graph[road[i-1]][j]<
bestans&
!
vis[j])
road[i]=j;
ans+=graph[road[i-1]][j];
vis[j]=1;
backtrack(i+1);
//改回辅助的全局变量
ans-=graph[road[i-1]][j];
vis[j]=0;
}
intmain()
memset(graph,INF,sizeof(graph));
cin>
>
n>
m;
for(inti=1;
=m;
i++)
inta,b;
a>
b;
graph[a][b];
graph[b][a]=graph[a][b];
vis[1]=1;
road[1]=1;
//假设是从1开始
backtrack
(2);
cout<
bestans<
endl;
i++)cout<
bestroad[i]<
"
"
;
1<
4.分支限界法
voidin()
mp[i][j]=INF;
mp[i][j]);
structnode
intvisp[22];
//标记哪些点走了
intst;
//起点
intst_p;
//起点的邻接点
inted;
//终点
inted_p;
//终点的邻接点
intk;
//走过的点数
intsumv;
//经过路径的距离
intlb;
//目标函数的值
booloperator<
(constnode&
p)const
returnlb>
p.lb;
};
priority_queue<
node>
q;
intlow,up;
intinq[22];
//确定上界
intdfs(intu,intk,intl)
if(k==n)returnl+mp[u][1];
intminlen=INF,p;
if(inq[i]==0&
minlen>
mp[u][i])/*取与所有点的连边中最小的边*/
minlen=mp[u][i];
p=i;
inq[p]=1;
returndfs(p,k+1,l+minlen);
intget_lb(nodep)
intret=p.sumv*2;
//路径上的点的距离
intmin1=INF,min2=INF;
//起点和终点连出来的边
if(p.visp[i]==0&
min1>
mp[i][p.st])
min1=mp[i][p.st];
ret+=min1;
min2>
mp[p.ed][i])
min2=mp[p.ed][i];
ret+=min2;
if(p.visp[i]==0)
min1=min2=INF;
if(min1>
mp[i][j])
min1=mp[i][j];
if(min2>
mp[j][i])
min2=mp[j][i];
ret+=min1+min2;
returnret%2==0?
(ret/2):
(ret/2+1);
voidget_up()
inq[1]=1;
up=dfs(1,1,0);
voidget_low()
low=0;
/*通过排序求两个最小值*/
inttmpA[22];
tmpA[j]=mp[i][j];
sort(tmpA+1,tmpA+1+n);
//对临时的数组进行排序
low+=tmpA[1];
intsolve()
/*贪心法确定上界*/
get_up();
/*取每行最小的边之和作为下界*/
get_low();
/*设置初始点,默认从1开始*/
nodestar;
star.st=1;
star.ed=1;
star.k=1;
i++)star.visp[i]=0;
star.visp[1]=1;
star.sumv=0;
star.lb=low;
/*ret为问题的解*/
intret=INF;
q.push(star);
while(!
q.empty())
nodetmp=q.top();
q.pop();
if(tmp.k==n-1)
/*找最后一个没有走的点*/
intp;
if(tmp.visp[i]==0)
break;
intans=tmp.sumv+mp[p][tmp.st]+mp[tmp.ed][p];
nodejudge=q.top();
/*如果当前的路径和比所有的目标函数值都小则跳出*/
if(ans<
=judge.lb)
ret=min(ans,ret);
/*否则继续求其他可能的路径和,并更新上界*/
else
up=min(up,ans);
ret=min(ret,ans);
/*当前点可以向下扩展的点入优先级队列*/
nodenext;
next.st=tmp.st;
/*更新路径和*/
next.sumv=tmp.sumv+mp[tmp.ed][i];
/*更新最后一个点*/
next.ed=i;
/*更新顶点数*/
next.k=tmp.k+1;
/*更新经过的顶点*/
j++)next.visp[j]=tmp.visp[j];
next.visp[i]=1;
/*求目标函数*/
next.lb=get_lb(next);
/*如果大于上界就不加入队列*/
if(next.lb>
up)continue;
q.push(next);
returnret;
四、运行输出结果:
(1)蛮力法
(2)动态规划法
(3)回朔法
(4)分支限界
五、调试和运行程序过程中产生的问题、采取的措施及获得的相关经验教训:
TSP问题在很多地方都可以运用到,并且好多问题都是由TSP问题延伸和发展的,也可以称之为TSP问题,不过其思路大致相似,于是我们可以运用已学过的算法对其进行解决。
我在学习算法课以前的TSP问题大都用动态规划以及回溯法,究其时间复杂度以及代码的复杂度比较低,思路比较清晰,在解决此类延伸问题时容易调试和修改。
学完算法后最有感触的一点就是,算法的精髓并不在于其方式方法,而在于其思想思路。
有了算法的思想,那么潜移默化中问题就可以得到解决