1.题目分析
1.1题目简介
假设你为快递公司设计快递投递路线优化程序。(1)每个市有个中转分发点,有些城市之间有直通路线,有些城市之间没有直通路线;(2)城市与城市之间的运费计算公式为:距离*1;(3)设投递包裹的尺寸、重量都一样,每条运输线路有个运力上限(即只能运输多少个包裹)。
要求:(1)随机产生当天的包裹投递需求;(2)对所产生的包裹需求,生成运输路线策略。(3)(拓展要求):根据随机测试结果,利用程序优化城市间包裹线路。
1.2分析思路
由上述的题目简介可知,在本次课程设计中,我们主要解决的有以下几个问题。第一个为城市矩阵的随机生成。第二个问题为最短运费的路径选择问题。第三个问题为程序优化问题。
针对于第一个问题,采用rand()随机函数调用vs中的随机矩阵来生成所需要的距离矩阵,包裹矩阵和运力矩阵。虽然rand()函数并不能实现真正意义上的随机,但为了方便后续的调试和优化,所以没有选择加入时间因素的srand()函数。针对第二个问题,由于是选择最短运费,理论上是与Dijkstra算法的最短路径求解的原理是一样的。我们知道,Dijkstra算法是把点与点之间的最短路径作为迭代的初始数据的,而我们要求解整体的最小运费,可以将点于点之间的最小运费作为迭代的初始数据,而局部最小运费的总和就是整体路径最小运费的结果。针对于第三个问题,由于第一步采用rand()函数,所以每一次产生的矩阵都是一样的,所以在后期的调试优化中,我们通过对于同一个点到不同点的运费路径选择以及不同点到同一个点的运费路径选择,实时调整优化程序,使得程序达到最优。
2.总体设计
基于上面的题目分析,所以在本次设计中,总结系统框架如下图所示。
在图形用户界面通过制表符等是的界面美观简洁。在输入输出方面,采用cin,cout等来实现人机交互。通过do while()等来实现循环输入和反复查询。通过对于原本的Dijkstra算法进行调整修改来实现其对于最小运费的求解。
3.数据结构设计
针对这一模拟系统,需要管理的数据主要有城市集合,城市间距离矩阵,城市间运力矩阵,城市间包裹矩阵等数据结构。现就每种数据给出详细的分析。
3.1城市集合
各个城市之间处于对等状态,所以这里采用顺序结构一维数组来存储城市的名称信息。CityName定义如下。
char CityName[N] = { 'a','b','c','d','e','f','g','h','i','j' };
3.2城市间矩阵集合
各个城市间的距离,运力,包裹运输处于一种多对多的模式,所以这里采用图的形式来存储城市间的运输信息。通过邻接矩阵来存储不同路径上的信息。由于a到b之间的距离,运力与b到a之间的距离运力完全相同。所以该图为无向图。Graph定义如下。
typedef struct graph {
int vCnt;//顶点个数
int adjMatrix[N][N];
int CPower[N][N];//运力上限
int CPackage[N][N];//包裹矩阵
}Graph;
4.算法设计
整体算法的基本工作流程如下图所示。
在重复查询方面,采用了do while()循环特有的性质,通过设置标志位来实现反复查询,具体代码如下所示。
do
{
CityFirst = getchar();
if (CityFirst >= 'a'&&CityFirst <= cc)
{
cout << "第一个城市输入正确!\n";
getchar();
flag = 0;
}
else
{
cout << "输入有误!请重新输入!\n";
getchar();
flag = 1;
}
} while (flag);
而在Dijkstra算法方面,把算法中迭代的距离值替换成运费。而运费的计算公式如下。
其中m是运费,t是趟数,即当前路径上的运力与包裹的一个比值,s是两个城市之间的距离,趟数的计算公式如下所示。
其中p是从起始城市到目标城市的包裹值,x是当前路径上的运力上限。由于在程序中t、p和x都是整型变量,所以在算式后加1来拟合结果。而0.0001是消除由于拟合导致的p与x相等所带来的误差。经最大差值计算,0.0001对于本算法产生的影响在误差允许的范围内。具体代码如下。
dist[i] = g->adjMatrix[v][i]*(int)(g->CPackage[v][l]/(g->CPower[v][i]+0.0001)+1);
5.结果分析
由于在本次的题目中,矩阵的运力上限与包裹对于算法的优劣性影响很大。
所以我们定义一个数值为包裹数比运力上限大的概率。设这个数值为包运比,在本次算法中,包裹和运力都是随机生成的数据,通过运用概率论的知识,我们可以方便的求出包运比。
在五维矩阵,且包运比为75%时。由图三可以得到a城市到e城市要运输的包裹为8。而算法最终选择的路径为由a城市到e城市,距离为8公里,只走一趟,运费为8元。而在a城市经由b城市中转在到达c城市虽然只需要6公里,但由于运力上限的原因,需要两趟才能完成运输任务,这样运费需要12元。由此可知,该算法符合题目要求。
在十维矩阵,且包运比为100%时。由图四可以得到a城市到e城市要运输的包裹为14。而算法选择的路径为b-a-i-e。该路径的距离为8公里,需要趟数为5,所需运费为40元。经分析该算法符合题目要求。
6.结论
从模拟的结果来看,本课程设计的算法完全符合题目要求。在多次的测试中由该算法输出的最优路径都是当前情况下的最小运费。平且在包运比不同的情况下,该算法总是可以选择出当前情况下的最优路径。而在用户界面方面,设置了循环输入来检验输入的城市是否满足条件。并且设置了反复查询机制,可以对于同一个城市矩阵实现对此查询,具有良好的人机交互性。
但本算法在运输过程中认为同一次运输任务的包裹都必须要经过同一最优路径运输。在某种情况下,限制了运输的机制,后续仍有待完善。并且在城市矩阵设置方面,默认为由a城市到b城市的距离与b城市到a城市的距离相等,即为无向图,而现实生活中情况可能更为复杂,后续可以根据具体需要做出具体调整。
7.附录
整体代码
#include <iostream>
#include <stdio.h>
using namespace std;
//初始化认为10个城市为最大值
#define N 10
#define maxValue 10
int CityPath[N][N]; //定义最大为10×10的矩阵
int CityPower[N][N]; //定义运力上限矩阵
int CityPackage[N][N]; //定义为包裹运输矩阵
char CityName[N] = { 'a','b','c','d','e','f','g','h','i','j' };
typedef struct graph {
int vCnt;//顶点个数
int adjMatrix[N][N];
int CPower[N][N];//运力上限
int CPackage[N][N];//包裹矩阵
}Graph;
//Dijkstra算法
void Dijkstra(Graph *g, char first,char last) //v为起点 k为终点;
{
int v,l;
switch (first)
{
case 'a':v = 0; break;
case 'b':v = 1; break;
case 'c':v = 2; break;
case 'd':v = 3; break;
case 'e':v = 4; break;
case 'f':v = 5; break;
case 'g':v = 6; break;
case 'h':v = 7; break;
case 'i':v = 8; break;
case 'j':v = 9; break;
}
switch (last)
{
case 'a':l = 0; break;
case 'b':l = 1; break;
case 'c':l = 2; break;
case 'd':l = 3; break;
case 'e':l = 4; break;
case 'f':l = 5; break;
case 'g':l = 6; break;
case 'h':l = 7; break;
case 'i':l = 8; break;
case 'j':l = 9; break;
}
int *dist, *s, **path, *plen;
int i, j, w, m, p;
//path用来记录从v到各点的最短路径
path = (int**)malloc(sizeof(int*) *g->vCnt);
for (i = 0; i < g->vCnt; i++)
{
path[i] = (int*)malloc(sizeof(int)*g->vCnt);
}
//s,dist,plen都为一维辅助数组 大小为顶点个数大小
//s[i]=1表示第i个顶点已经求出最短路径
s = (int*)malloc(sizeof(int)*g->vCnt);
//dist[i]表示从v到i个顶点的距离
dist = (int*)malloc(sizeof(int)*g->vCnt);
//plen[i]表示从v到第i个顶点的顶点数
plen = (int*)malloc(sizeof(int)*g->vCnt);
for (i = 0; i < g->vCnt; i++)
{
if (i == v)
{
s[i] = 1;
}
else
{
s[i] = 0;
}
plen[i] = 0;
dist[i] = g->adjMatrix[v][i]; //对dist数组初始化
if (dist[i] < maxValue&&i != v)
{
path[i][0] = v;
path[i][1] = i;
plen[i] = 2;
}
}
//逐次求出v到其余顶点的最短路径
for (i = 1; i < g->vCnt - 1; i++)
{
m = v;
w = maxValue;
for (j = 0; j < g->vCnt; j++)
{
//s[j]=0表示第j个点的最短路径还没有求出并且dist[j]小于最大值
//遍历与v邻接的所有顶点 选出与v距离最短的顶点m
if (s[j] == 0 && dist[j] < w)
{
//修改最大值
w = dist[j];
//修改顶点
m = j;
}
}
//将找出的第一个最短路径m的s[m]置为1
if (m != v)
{
s[m] = 1;
}
else
{
break;
}
for (j = 0; j < g->vCnt; j++)
{
//j对应的顶点的最短路径没有被找到
//并且从v到m的距离dist[m]+m到其他点的距离小于v到其他点的距离dist[j]
//此时进入循环查找最短路径
if (s[j] == 0 && dist[m] + g->adjMatrix[m][j] < dist[j])
{
dist[j] = dist[m] + g->adjMatrix[m][j];
for (p = 0; p < plen[m]; p++)
{
path[j][p] = path[m][p];
}
path[j][p] = j;
plen[j] = plen[m] + 1;
}
}
}
输出v到所有顶点的最短路径
//for (i = 0; i < g->vCnt; i++)
//{
// for (j = 0; j < plen[i]; j++)
// {
// printf("%d", path[i][j]);
// }
// cout << "\n";
//}
//输出v到指定顶点的路径
printf("从%c城市到%c城市需要运输的包裹数目为:%d\n", v + 97, l + 97, g->CPackage[v][l]);
printf("从%c城市到%c城市的最优路径为:\n", v+97, l+97);
for (j = 0; j < plen[l]; j++)
{
if (j == plen[l] - 1)
{
printf("%c城市", path[l][j] + 97);
}
else
{
printf("%c城市->", path[l][j] + 97);
}
}
printf("\n从%c城市到%c城市的距离为:%d公里\n", v + 97, l + 97, dist[l]);
printf("该路径的运费为:\n城市间距离*包裹数=总运费\n%d*%d=%d\n", dist[l], g->CPackage[v][l], dist[l] * g->CPackage[v][l]);
printf("总运费为%d元!", dist[l] * g->CPackage[v][l]);
}
int main()
{
int city;
cout << "\n\t\t\t\t******************************************\n";
cout << "\t\t\t\t\t基于无向图的城市间快递派送算法\n\t\t\t\t\t编写者:韩成功\n\t\t\t\t\t地点:江苏徐州\n\t\t\t\t\t时间:2020年11月";
cout << "\n\t\t\t\t******************************************\n";
cout << "\n请输入城市总数,并使总数小于" << N << ":";
cin >> city;
while (city > N)
{
cout << "请重新输入城市总数:";
cin >> city;
}
cout << "城市总数为:" << city << endl;
//初始化路径矩阵和运力矩阵
for (int i = 0; i < city; i++)
{
for (int j = i + 1; j < city; j++)
{
if (i == j)
{
CityPath[i][j] = 0;
CityPower[i][j] = 0;
}
else
{
//归一化城市之间的距离为0-9
CityPath[i][j] = rand() % 10;
CityPower[i][j] = rand() % 5 + 5;
}
CityPath[j][i] = CityPath[i][j];
CityPower[j][i] = CityPower[i][j];
}
}
//初始化包裹矩阵
for (int i = 0; i < city; i++)
{
for (int j = 0 ; j < city; j++)
{
if (i == j)
{
CityPackage[i][j] = 0;
}
else
{
CityPackage[i][j] = rand() % 10 + 5;
}
}
}
//输出矩阵
cout << "城市之间的路径矩阵如下:\n\t";
for (int i = 0; i < city; i++)
{
cout <<CityName[i]<< "\t";
}
cout << "\n";
for (int i = 0; i < city; i++)
{
cout << CityName[i]<< "\t";
for (int j = 0; j < city; j++)
{
cout << CityPath[i][j] << "\t";
}
cout << "\n";
}
cout << "城市之间的运力上线矩阵如下:\n\t";
for (int i = 0; i < city; i++)
{
cout << CityName[i] << "\t";
}
cout << "\n";
for (int i = 0; i < city; i++)
{
cout << CityName[i] << "\t";
for (int j = 0; j < city; j++)
{
cout << CityPower[i][j] << "\t";
}
cout << "\n";
}
cout << "城市之间的包裹运输矩阵如下:\n\t";
for (int i = 0; i < city; i++)
{
cout << CityName[i] << "\t";
}
cout << "\n";
for (int i = 0; i < city; i++)
{
cout << CityName[i] << "\t";
for (int j = 0; j < city; j++)
{
cout << CityPackage[i][j] << "\t";
}
cout << "\n";
}
//初始化图
Graph* CityWord=(Graph *)malloc(sizeof(Graph));
CityWord->vCnt = city;
//CityWord->adjMatrix[N][N] = CityPath[N][N];
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
CityWord->adjMatrix[i][j] = CityPath[i][j];
CityWord->CPower[i][j] = CityPower[i][j];
CityWord->CPackage[i][j] = CityPackage[i][j];
}
}
int ever = 1;
while (ever)
{
char CityFirst, CityLast;
cout << "本次算法涉及到的有以下城市:\n";
for (int i = 0; i < city; i++)
{
cout << CityName[i] << " ";
}
cout << "\n请输入您想要查询的城市包裹派送路线:\n";
cout << "查询a到b城市的派送路线\n输入示例为:a b \n";
getchar(); //把回车吃掉
int flag = 1;
char cc=CityName[city-1];
do
{
CityFirst = getchar();
if (CityFirst >= 'a'&&CityFirst <= cc)
{
cout << "第一个城市输入正确!\n";
getchar();
flag = 0;
}
else
{
cout << "输入有误!请重新输入!\n";
getchar();
flag = 1;
}
} while (flag);
do
{
CityLast = getchar();
if (CityLast >= 'a'&&CityLast <= cc&&CityLast != CityFirst)
{
cout << "第二个城市输入正确!\n";
getchar();
flag = 0;
}
else
{
cout << "输入有误!请重新输入!\n";
getchar();
flag = 1;
}
} while (flag);
Dijkstra(CityWord, CityFirst, CityLast);
cout << "\n输入Y/y继续查询,输入N/n退出查询!";
char s;
do
{
s = getchar();
if (s == 'Y' || s == 'y')
{
ever = 1;
flag = 0;
}
else if (s == 'N' || s == 'n')
{
ever = 0;
flag = 0;
}
else
{
cout << "输入有误! 请重新输入!\n";
flag = 1;
}
} while (flag);
}
cout << "查询结束,感谢使用!";
return 0;
}
后续修改整理