TSP问题描述
旅行商从驻地出发,经过每个需要访问的城市一次且只有一次,并最终返回出发点。如何安排路线,使旅行总路程最短?即求解最短哈密顿回路。
回溯法解tsp问题(深度优先)
以深度优先的方式,从根节点开始,依次扩展树节点,直到达到叶节点——搜索过程中动态产生解空间
代码
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define NUM 42
#define NoEdge 99999
struct MinHeapNode{//解空间树的节点
double lb,//子树费用的下界
cc;//当前费用
int s,
*x;
struct MinHeapNode *next;
};
int bestx[NUM+1];//最优路径
int x[NUM+1];
double cw=0.0,bestw=99999.0;//cw是当前路径值,bestw是最优路径值
double **w;//距离矩阵
int count=0;//计算节点数
MinHeapNode* head = 0; /*堆头*/
MinHeapNode* fq = 0; /*堆第一个元素*/
MinHeapNode* lq = 0; /*堆最后一个元素*/
void swap(int i,int j){//用于交换
int temp=x[i];
x[i]=x[j];
x[j]=temp;
}
void backtrackTSP(int i,int n){//回溯法求解
count++;
if(i==n){//到达叶节点
if(w[x[n-1]][x[n]]!=99999&&w[x[n]][x[1]]!=99999){//叶节点的父节点和叶节点是否有路径,叶节点和第一个节点是否有路径
if(cw+w[x[n-1]][x[n]]+w[x[n]][x[1]]<bestw){//当前路径值是否会优于当前最优值
bestw=cw+w[x[n-1]][x[n]]+w[x[n]][x[1]];
for(int j=1;j<=n;j++)
bestx[j]=x[j];
}
}
}
else{//非叶节点
for(int j=i;j<=n;j++){
if(w[x[i-1]][x[j]]!=99999&&cw+w[x[i-1]][x[j]]<bestw){//若当前节点到下一节点无路径或当前路径值已经大于当前最优值,进行剪枝
swap(i,j);
cw=cw+w[x[i-1]][x[i]];
backtrackTSP(i+1,n);
//回溯失败
cw=cw-w[x[i-1]][x[i]];
swap(i,j);
}
}
}
}
int main(){
FILE* fp1=fopen("22基站.txt","r");
FILE* fp2=fopen("15基站.txt","r");
FILE* fp3=fopen("20基站.txt","r");
FILE* fp4=fopen("30基站.txt","r");
w=(double**)malloc(sizeof(double*)*(NUM+1));
for(int i=0;i<=NUM;i++)
w[i]=(double*)malloc(sizeof(double)*(NUM+1));
for(int i=1;i<=22;i++)//读入22基站的连接矩阵
for(int j=1;j<=22;j++)
fscanf(fp1,"%lf",&w[i][j]);
for(int i=1;i<=15;i++)//读入15个基站的初始顺序
fscanf(fp2,"%d",&x[i]);
backtrackTSP(2,15);
printf("%lf\n",bestw);
for(int i=1;i<=15;i++)
printf("%d ",bestx[i]);
printf("\n%d\n",count);
count=0;
cw=0.0;
bestw=99999.0;
for(int i=1;i<=20;i++)//读入20个基站的初始顺序
fscanf(fp3,"%d",&x[i]);
backtrackTSP(2,20);
printf("%lf\n",bestw);
for(int i=1;i<=20;i++)
printf("%d ",bestx[i]);
printf("\n%d\n",count);
}
基站数据
运行结果
分支限界法解tsp问题(广度优先)
代码
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define NUM 42
#define NoEdge 99999
struct MinHeapNode{
double lb,//子树费用的下界
cc;//当前费用
int s,
*x;
struct MinHeapNode *next;
};
int bestx[NUM+1];
int x[NUM+1];
double cw=0.0,bestw=99999.0;
double **w;
double upcost=0.0;//问题的上界
MinHeapNode* head = 0; /*堆头*/
MinHeapNode* fq = 0; /*堆第一个元素*/
MinHeapNode* lq = 0; /*堆最后一个元素*/
void swap(int i,int j){
int temp=x[i];
x[i]=x[j];
x[j]=temp;
}
int DeleteMin(MinHeapNode*&E)//从堆中取出下一个节点
{
MinHeapNode* tmp = NULL;
tmp = fq;
// w = fq->weight ;
E = fq;
if(E == NULL)
return 0;
head->next = fq->next; /*一定不能丢了链表头*/
fq = fq->next;
//free(tmp) ;
return 0;
}
int Insert(MinHeapNode* hn)//在堆中插入节点
{
if(head->next == NULL)
{
head->next = hn; //将元素放入链表中
fq = lq = head->next; //一定要使元素放到链中
}else
{
MinHeapNode *tmp = NULL;
tmp = fq;
if(tmp->lb > hn->lb)
{
hn->next = tmp;
head->next = hn;
fq = head->next;
}else
{
for(; tmp != NULL;)
{
if(tmp->next != NULL && tmp->next->lb > hn->lb)
{
hn->next = tmp->next;
tmp->next = hn;
break;
}
tmp = tmp->next;
}
}
if(tmp == NULL)
{
lq->next = hn;
lq = lq->next;
}
}
return 0;
}
void up_cost(int k,int n){//用于求解问题上界
//贪心法+回溯法求解问题上界
//printf("beging:%d\n",k);
if(k==n){
if(w[x[k]][x[1]]!=NoEdge){
flag=1;
upcost=cw+w[x[k]][x[1]];
}
}
else{
for(int i=0;i<n-k-1;i++){//冒泡排序
for(int j=k+1;j<n-i;j++){
if(w[x[k]][x[j]]>w[x[k]][x[j+1]])
swap(j,j+1);
}
}
for(int i=k+1;i<=n;i++){
if(flag==1)
break;
if(w[x[k]][x[i]]!=NoEdge){
//printf("%d,%d,%lf\n",x[k],x[i],w[x[k]][x[i]]);
cw+=w[x[k]][x[i]];
swap(k+1,i);
up_cost(k+1,n);
cw-=w[x[k]][x[k+1]];//失败回溯
swap(k+1,i);
}
}
}
}
void low_cost(MinHeapNode *p,int n){//用于求解问题下界
p->lb=0.0;
p->lb+=2*p->cc;//已经经过的路径总长的两倍
double min=NoEdge;
int min1;
/*//从当前已经走过的第一个城市出发,走向最近的1个未遍历城市的距离和
for(int j=p->s+1;j<n;j++){
if(w[p->x[0]][p->x[j]]<min)
min=w[p->x[0]][p->x[j]];
}
p->lb+=min;
min=NoEdge;*/
for(int i=p->s+1;i<n;i++){//进入/离开未遍历城市时,各未遍历城市带来的最小路径成本
for(int j=0;j<n;j++){
if(w[p->x[i]][p->x[j]]<min){
min=w[p->x[i]][p->x[j]];
min1=j;
}
}
p->lb+=min;
min=NoEdge;
for(int j=0;j<n;j++){
if(w[p->x[i]][p->x[j]]<min&&j!=min1)
min=w[p->x[i]][p->x[j]];
}
p->lb+=min;
min=NoEdge;
}
p->lb/=2;
if(p->s==n-1)
p->lb+=w[p->x[n-1]][p->x[0]];
else{
//从当前已经走过的第一个城市出发,走向最近的1个未遍历城市的距离和
for(int j=p->s+1;j<n;j++){
if(w[p->x[0]][p->x[j]]<min)
min=w[p->x[0]][p->x[j]];
}
p->lb+=min;
min=NoEdge;
}
/*for(int i=0;i<n;i++)
printf("%d ",p->x[i]);
printf("\n%lf\n",p->lb);*/
}
void BBTSP(int v[],int n)//回溯法求解
{//解旅行售货员问题的优先队列式分支限界法
/*初始化最优队列的头结点*/
head = (MinHeapNode*)malloc(sizeof(MinHeapNode));
head->cc = 0;
head->x = 0;
head->lb = 0;
head->next = NULL;
head->s = 0;
MinHeapNode *E ;
E = (MinHeapNode*)malloc(sizeof(MinHeapNode));
E->x = new int[n];
for(int i = 0; i <n; i++)
E->x[i] = x[i+1];
E->s = 0;
low_cost(E,n);
E->cc = 0;
E->next = NULL; //初始化当前扩展节点
//搜索排列空间树
while(E->s >=0)
{//非叶结点
//printf("s:%d\n",E->s);
//printf("chuli:%d\n",E->x[E->s]);
//printlen();
//printf("-------s:%d---------\n",E->s);
if(E->s == n - 1)//到达叶节点
{
/*
如果该叶结点的路径值和所有节点的lb相比是最小的,输入最优解算法结束
否则更新上界
*/
/*printf("!!!!!!\n");
for(int k=0;k<n;k++)
printf("%d ",E->x[k]);
printf("\n");*/
if(w[E->x[E->s]][E->x[0]]==NoEdge){//无法形成回路
//printf("11111\n");
DeleteMin(E);
continue;
}
else{
E->cc+=w[E->x[E->s]][E->x[0]];
bestw=E->cc;
for(int i=0;i<n;i++)
bestx[i+1]=E->x[i];
break;
}
}else
{/*扩展子节点,将可行子节点加入优先队列中*/
//printf("2,upcost:%lf\n",upcost);
for(int i = E->s + 1; i < n; i++)
if(w[E->x[E->s]][E->x[i]] != NoEdge)
{ /*当前扩展节点到其他节点有边存在*/
//可行儿子结点
//printf("3\n");
double cc = E->cc + w[E->x[E->s]][E->x[i]]; /*加上节点i后当前节点路径*/
MinHeapNode* N;
N = (MinHeapNode*)malloc(sizeof(MinHeapNode));
N->x = new int[n];
for(int j = 0; j < n; j++)
N->x[j] = E->x[j];
N->x[E->s + 1] = E->x[i];
N->x[i] = E->x[E->s + 1];
N->cc = cc; /*更新当前路径距离*/
N->s = E->s + 1; /*更新当前节点*/
low_cost(N,n);
//printf("cc:%lf\n",N->cc);
//printf("lb:%lf\n",N->lb);
if(N->lb<=upcost)
{//子树可能含最优解,结点插入最小堆
/*添加当前路径*/
//printf("add:%d\n",N->x[E->s+1]);
//printf("4\n");
N->next = NULL;
Insert(N); /*将这个可行儿子结点插入到活结点优先队列中*/
}
}
free(E);
}//完成结点扩展
DeleteMin(E);//取下一扩展结点
if(E == NULL)
break; //堆已空
}
if(bestw == NoEdge)
return ;//无回路
while(true)
{//释放最小堆中所有结点
free(E->x);
DeleteMin(E);
if(E == NULL)
break;
}
}
int main(){
FILE* fp1=fopen("22基站.txt","r");
FILE* fp2=fopen("15基站.txt","r");
FILE* fp3=fopen("20基站.txt","r");
FILE* fp4=fopen("30基站.txt","r");
w=(double**)malloc(sizeof(double*)*(NUM+1));
for(int i=0;i<=NUM;i++)
w[i]=(double*)malloc(sizeof(double)*(NUM+1));
for(int i=1;i<=22;i++)//读入22基站的连接矩阵
for(int j=1;j<=22;j++)
fscanf(fp1,"%lf",&w[i][j]);
for(int i=1;i<=15;i++)//读入15个基站的初始顺序
fscanf(fp2,"%d",&x[i]);
up_cost(1,15);//求问题上界
cw=0.0;
printf("upcost:%lf\n",upcost);
BBTSP(bestx,15);
//backtrackTSP(2,15);
printf("最优值:%lf\n",bestw);
printf("最优路径:");
for(int i=1;i<=15;i++)
printf("%d ",bestx[i]);
cw=0.0;
bestw=99999.0;
for(int i=1;i<=20;i++)//读入20个基站的初始顺序
fscanf(fp3,"%d",&x[i]);
up_cost(1,20);//求问题上界
cw=0.0;
printf("\nupcost:%lf\n",upcost);
BBTSP(bestx,20);
printf("最优值:%lf\n",bestw);
printf("最优路径:");
for(int i=1;i<=20;i++)
printf("%d ",bestx[i]);
}
运行结果
结果分析
明显可以看出分支限界法求解所用的时间是少于回溯法的,毕竟前者设计上便是以空间换时间。回溯法的时间复杂度是,而分支限界法的时间复杂度是
来源:CSDN
作者:qq_虚无
链接:https://blog.csdn.net/qq_42295427/article/details/104106468