这篇文章是一个研究生数模的作业,研究了一下感觉挺有意思的,所以准备拿来好好研究一下,借此写几篇博文,记载。
----20140605
首先,这里第一篇我会基于该题目本身来进行基本的设计和研究。参考过一两篇所谓答案,感觉看上去不知所云,希望我能用最简单的语言成功描述这个问题。下面是这个题目:
某物流中心拥有一支货运车队,每台货运车辆的载重量(吨)相同、平均速度(千米/小时)相同,该物流中心用这样的车为若干个客户配送物资,物流中心与客户以及客户与客户之间的公路里程(千米)为已知。每天,各客户所需物资的重量(吨)均已知,并且每个客户所需物资的重量都小于一台货运车辆的载重量,所有送货车辆都从物流中心出发,最后回到物流中心。物流中心每天的配送方案应当包括:当天出动多少台车?行驶路径如何?由此形成的当天总运行里程是多少?一个合格的配送方案要求送货车辆必须在一定的时间范围内到达客户处,早到达将产生等待损失,迟到达将予以一定的惩罚;而一个好的配送方案还应该给出使配送费用最小或总运行里程最短的车辆调度方案。
〔算例〕载重量为 8 吨、平均速度为 50千米/小时 的送货车辆从物流中心(0)出发,为编号是 1,2,…,8 的8个客户配送物资。某日,第个客户所需物资的重量为吨(),在第个客户处卸货时间为小时,第个客户要求送货车辆到达的时间范围 由表1给出。物流中心与各客户以及各客户间的公路里程(单位:千米)由表2给出。问当日如何安排送货车辆(包括出动车辆的台数以及每一台车辆的具体行驶路径)才能使总运行里程最短。
表1 物资配送任务及其要求
客户 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
(吨) |
2 |
1.5 |
4.5 |
3 |
1.5 |
4 |
2.5 |
3 |
(小时) |
1 |
2 |
1 |
3 |
2 |
2.5 |
3 |
0.8 |
[1, 4] |
[4, 6] |
[1, 2] |
[4, 7] |
[3, 5.5] |
[2, 5] |
[5, 8] |
[1.5, 4] |
表2 点对之间的公路里程(千米)
|
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
0 |
0 |
40 |
60 |
75 |
90 |
200 |
100 |
160 |
80 |
1 |
40 |
0 |
65 |
40 |
100 |
50 |
75 |
110 |
100 |
2 |
60 |
65 |
0 |
75 |
100 |
100 |
75 |
75 |
75 |
3 |
75 |
40 |
75 |
0 |
100 |
50 |
90 |
90 |
150 |
4 |
90 |
100 |
100 |
100 |
0 |
100 |
75 |
75 |
100 |
5 |
200 |
50 |
100 |
50 |
100 |
0 |
70 |
90 |
75 |
6 |
100 |
75 |
75 |
90 |
75 |
70 |
0 |
70 |
100 |
7 |
160 |
110 |
75 |
90 |
75 |
90 |
70 |
0 |
100 |
8 |
80 |
100 |
75 |
150 |
100 |
75 |
100 |
100 |
0 |
很显然这是一个基于最短路径的问题。
在第一步,显然应该获得关于所有节点之间的最短路径矩阵。最初我根据网上的若干文章选择使用了Dijkstra算法以获得关于物流中心,也就是0节点的最短路径数组。当然针对算例也能得到不错的配送方案,但是对于比较复杂的路线肯定无法满足需要,因此我还是重选了Floyd算法来处理这个问题(另一个原因是因为这个算法更好写)。这里我把两个算法写成的java函数放到下面
//Floyd算法,返回一个类,这个类包含两个二维数组
public Result floyd(int[][] g) {
int n = g.length;
int[][] dis = new int[n][n];//距离矩阵
int[][] path = new int[n][n];//path矩阵
for (int q = 0; q < n; q++) {
for (int w = 0; w < n; w++) {
dis[q][w] = g[q][w];
path[q][w] = -1;
}
}//数组初始化完成
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dis[i][j] > dis[i][k] + dis[k][j]) {
dis[i][j] = dis[i][k] + dis[k][j];
path[i][j] = k;
}
}
}
}
Result rs = new Result();
rs.dis = dis;
rs.path = path;
return rs;
}
//每次只会计算start这一行,结果包含两个二维数组和一个数组
public Result dijsktra(int[][] weight, int start) {
// 接受一个有向图的权重矩阵,和一个起点编号start(从0编号,顶点存在数组中)
// 返回一个int[] 数组,表示从start到它的最短路径长度
int n = weight.length; // 顶点个数
int[] shortPath = new int[n]; // 存放从start到其他各点的最短路径
int[] visited = new int[n]; // 标记当前该顶点的最短路径是否已经求出,1表示已求出
int[] pathM = new int[n];
for (int i = 0; i < pathM.length; i++) {
pathM[i]= -1;
}
Result rs = new Result();
// 初始化,第一个顶点求出
shortPath[start] = 0;
visited[start] = 1;
for (int count = 1; count <= n - 1; count++) // 要加入n-1个顶点
{
int k = -1; // 选出一个距离初始顶点start最近的未标记顶点
int dmin = 1000;
for (int i = 0; i < n; i++) {
if (visited[i] == 0 && weight[start][i] < dmin) {
dmin = weight[start][i];
k = i;
}
}
// 将新选出的顶点标记为已求出最短路径,且到start的最短路径就是dmin
shortPath[k] = dmin;
visited[k] = 1;
// 以k为中间点想,修正从start到未访问各点的距离
for (int i = 0; i < n; i++) {
if (visited[i] == 0
&& weight[start][k] + weight[k][i] < weight[start][i]) {
weight[start][i] = weight[start][k] + weight[k][i];
pathM[i] = k;
}
}
}
rs.pathM = pathM;//path矩阵
rs.dist = shortPath;//改行的dist数组
rs.resultM = weight;//修改过start行的距离矩阵
return rs;
}
得到基础的两个矩阵之后。第一步就算是结束了。
但是接下来选择多少运送车辆和每辆车的运送路径就成了关键的问题。
1.根据车辆载重构造可能的运送线路集合;
2.根据最短线路规则制定行车线路;
3.根据时间规则,构造可能的节点顺序。
想到这里,问题似乎就陷入了死胡同,首先,三个条件的交叉点不一定存在。第二,没有一个条件可以作为评分标准。第三,用程序写这个就更不好写了。
既然一次性搞定有问题,那么就一步步来把。