北航研究生算法分析课的作业之一
用分支定界算法求以下问题:
某公司于乙城市的销售点急需一批成品,该公司成品生产基地在甲城
市。甲城市与乙城市之间共有n 座城市,互相以公路连通。甲城市、乙城市以及其它各城市之间的公路连通情况及每段公路的长度由矩阵M1 给出。每段公路均由地方政府收取不同额度的养路费等费用,具体数额由矩阵M2 给出。请给出在需付养路费总额不超过1500 的情况下,该公司货车运送其产品从甲城市到乙城市的最短运送路线。
具体数据参见文件:
M1.txt: 各城市之间的公路连通情况及每段公路的长度矩阵(有向图); 甲城市为城市Num.1,乙城市为城市Num.50。M2.txt: 每段公路收取的费用矩阵(非对称)
过程分析:
分支定界法最重要的一部就是定出合理的更紧的界,可以快速剪枝,本题先用floyd算法,讲distance和cost矩阵求出所有城市之间的最短距离矩阵graph_1和最短花费矩阵graph _2
用深度优先搜索路径,得出的一条从0号到49号城市的完整路径的长度,即为定界法的上届,试验其他路线,在试验其他路线时如果当前距离curdist+graph[cur][49]已经超出了现存的界,则此条路径无意义,直接剪枝,或者当前花费curcost+graph_2[cur][49](到达终点所可能的最短花费)超过1500,也剪枝。
当路径终点为49号城市时,即找到了一条可行的路线,然后更新mindist,distbound。当找不到能到达49号城市的路径时就回溯。
程序流程如下
1. 将m1.txt,m2.txt的数据读入graph_1 and graph_2
2. 用floyid算法求出所有点对之间的最短路长,和最小费用.
3. 声明并初始化一些变量和数据结构
4. 建立一个堆栈,初始化该堆栈
5. 取出栈顶的结点,检查它的相邻结点(从上次考虑的那个结点的下一个结点开始考虑).确定下一个当前最优路径上的结点.被扩展的结点都被加入堆栈中.
6. 在检查的过程中,如果发现超出当前的路长界或超出费用的界,则进行”剪枝” ,然后回溯
7. 找到一个解后,保存即可,无需回溯,程序源文件中有详细说明原因
8. 重复上一步的过程,直到堆栈为空.当前保存的解即为最优解.
代码
// zy1606522 孙涛
#include<stdio.h>
#include<fstream>
#include<string>
#define MAX 10000000
#define N 50
void floyd(int n, int d[][N])//弗洛伊德算法
{
int i, j, k;
for (k = 0; k < n; ++k)
{
for (i = 0; i < n; ++i)
{
for (j = 0; j < n; ++j)
{
if (d[i][k] + d[k][j] < d[i][j])
{
d[i][j] = d[i][k] + d[k][j];
}
}
}
}
}
void matixcopy(int a[N][N], int b[N][N]) // 把矩阵b赋值给矩阵a 的函数
{
int i, j;
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
a[i][j] = b[i][j];
}
int main()
{
int stack[N]; int topstack;//栈顶指针
int visited[N];//visited函数记录当前已被访问城市
int i = 0, j = 0, k = 0;//定义循环变量
int graph_1[N][N], graph_2[N][N];
FILE*fp;
fp = fopen("m1.txt", "r");//读取m1 m2矩阵 ,注意m1.txt和m2.txt必须和cpp文件在同一个路径,否则出错
if (fp == NULL)
{
return -1;
}
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
fscanf(fp, "%d", &graph_1[i][j]);
fclose(fp);
fp = fopen("m2.txt", "r");
if (fp == NULL)
{
return -1;
}
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
fscanf(fp, "%d", &graph_2[i][j]);
fclose(fp);
int m1[N][N], m2[N][N];
matixcopy(m1, graph_1); /*copy graph_1和graph_2 因为graph_1,graph_2将被弗洛伊德算法函数操作 */
matixcopy(m2, graph_2);
floyd(N, graph_1);
floyd(N, graph_2);
int curdist = 0, curcost = 0,distbound=MAX,costbound=MAX;//分别为当前距离,当前花费,距离界限,花费界限
topstack = 0;
stack[topstack] = 0;
stack[topstack + 1] = 0;
visited[0] = 1;//visited数组记录当前被访问过的城市,避免再次访问
int bestpath[N];
int mindist=MAX, mincost=MAX;
while (topstack>=0) /*当全部指针出栈结束 ,整个while循环的第一遍会搜索五十层,找出第一条可行路径,此后的循环被第一次生成的界限限制,会越来越短,所以最后一次生成的就是结果*/
{
int cur ,next;
cur = stack[topstack];
next = stack[topstack + 1]; //next非常重要,每次搜索从next+1开始,有效避免重复搜索,造成死循环
for (i = next+ 1; i < 50; i++)//必须从next+1开始搜索否则回溯将造成死循环
{
if (m1[cur][i] == 9999)
continue;
if (visited[i] == 1)
continue;
if ((curdist + graph_1[cur][49] > distbound)||(curcost + graph_2[cur][49]>1500))//定界条件
{
continue;//不满足的直接剪枝
}
if (i < 50)
break;
}
if (i == 50)//最后一个城市不是49,回溯
{
topstack--;
curdist -= m1[stack[topstack]][ stack[topstack + 1]];
curcost -= m2[stack[topstack]][ stack[topstack + 1]];
visited[stack[topstack + 1]] = 0;
}
else//记录可选的城市
{
curdist += m1[stack[topstack]][i];
curcost += m2[stack[topstack]][i];
visited[i] = 1;
topstack++;
stack[topstack] = i;
stack[topstack + 1] = 0;//下一个城市置为0,下一层搜索依然从1号城市开始
if (i == 49)//最后一个城市是49
{
for(j=0;j<N;j++)//每次所得路径的城市个数不定,所以每次存储路径前要清洗数组
bestpath[j]=0;
for (j = 0; j <= topstack; j++)
bestpath[j] = stack[j];//存储路径
if(curcost<1500)//上面的定界不能保证每个curcost一定小于1500,最后一步可能超过,所以加限定条件
{
mindist = curdist;
mincost = curcost;
}
if(curdist<distbound)
distbound = curdist; //换成更紧的界,剪枝更快
/*此处无需增加回溯代码,因为搜索到49之后,下次无论如何都不能找到49,程序一定会执行第100行,第100行的程序会向上回溯一次,但下次搜索从i=next+1=stack[topstack+1]+1号城市开始,因此不会经过49号城市,因此不会死循环。在无法找到能达到49的路径后,继续回溯,直到所有元素出栈,不再满足while循环条件为止*/
}
}
}
printf("最短路程 %d\n最少花费 %d \n", mindist, mincost);
printf("依次经过城市序号\n ");
for(int k=0;k<N;k++)
{
printf("%d ",bestpath[k]+1);
if(bestpath[k]+1==N)//输出路径,到城市号为50停止
break;
}
printf("\n");
getchar();
return 0;
}