目录
算法就是探索的过程嘛~,记录问题研究过程,不专业,其中可能有些错误~还望指正!
问题及数据
1. 问题说明
通过实际案例描述,根据配送点和业务需求量,进行最优路线的计算。由物流中心点出发,配送多个客户点后再回到起点,根据车辆数量,承载限制,不同车辆服务成本、运行里程限制等条件选择最优运输路径(总里程最短),使成本最小化,配送订单最大化,满载率最大化(如由一个配送中心向各个销售点配送货物,通过算法确定配送中心每辆车的配送方案,包括配送至哪个客户,配送量,下一个配送目的地)。
2. 测试数据和说明
某物流中心有5台配送车辆,车辆的最大载重均为8T,一次配送的最大行驶距离都为50KM,需要向20个客户送货,物流中心和20个客户的坐标及其客户的需求量随机产生,其中,物流中心的坐标为(14.2KM,13.1km),要求合理安排车辆的配送路线和载重量,使配送总里程最短。
3. 数据
客户点 | 横坐标x(km) | 横坐标x(km) | 需求量q(t) |
---|---|---|---|
1 | 12.8 | 8.5 | 0.1 |
2 | 18.4 | 3.4 | 0.4 |
3 | 15.4 | 16.6 | 1.2 |
4 | 18.9 | 15.2 | 1.5 |
5 | 15.5 | 11.6 | 0.8 |
6 | 3.9 | 10.6 | 1.3 |
7 | 10.6 | 7.6 | 1.7 |
8 | 8.6 | 8.4 | 0.6 |
9 | 12.5 | 2.1 | 1.2 |
10 | 13.8 | 5.2 | 0.4 |
11 | 6.7 | 16.9 | 0.9 |
12 | 14.8 | 2.6 | 1.3 |
13 | 1.8 | 8.7 | 1.3 |
14 | 17.1 | 11 | 1.9 |
15 | 7.4 | 1 | 1.7 |
16 | 0.2 | 2.8 | 1.1 |
17 | 11.9 | 19.8 | 1.5 |
18 | 13.2 | 15.1 | 1.6 |
19 | 6.4 | 5.6 | 1.7 |
20 | 9.6 | 14.8 | 1.5 |
说明:各客户相互之间和物流中心与客户之间的距离均采用直线距离
思路一:贪心算法
1. 原始方案
[1] 过程分析
贪心算法其实很简单。
即每次选择距离最小的点,然后进行判断:
- 加入此点后,货车的剩余行驶里程能否从此点直接回到出发点。(此处可优化)
- 加入此点,是否造成载重不足情况。
如果都可以那么加入此点,寻找下一个距离最近的点,直到不满足条件回到出发点。
第一辆车回到出发点后,如果还存在未运输的城市,那么继续采用第二辆车以此类推,直到城市全部运输完成。
[2] 例子分析
用此方法的第一辆车做例子来讲解这个方法
计算与出发点距离最近的点,即5号点,那么进入5号点。进入完成后,再计算与5号点最近距离的点,以此类推。
计算所得:
所得路线图如图所示
[3] 结果分析
贪心算法所取得的效果并不尽人意。
从每辆车的行驶距离和载重可以看出,大部分情况下都是载重先用完。
虽然一辆车的局部是最佳的但整体来说不是最佳的。
结果如图所示:
路线图:
[4] 案例代码
#include <fstream>
#include <string>
#include <iostream>
#include <vector>
#include<math.h>
using namespace std;
// 功能:将filename 中的数据(共cols列)读取到_vector中,_vector可视为二维数组
int read_scanf(const string &filename, const int &cols, vector<double *> &_vector)
{
FILE *fp = fopen(filename.c_str(), "r");
bool flag = true;
int i = 0;
if (!fp)
{
cout << "File open error!\n";
return 0;
}
while (flag)
{
double *rowArray = new double[cols]; //new一个double类型的动态数组
for (i = 0; i < cols; i++) //读取数据,存在_vector[cols]中
{
if (EOF == fscanf(fp,"%lf", &rowArray[i]))
{
flag = false;
break;
}
//输出rowArray存入的数据
//cout << rowArray[0] << " " << rowArray[1] << " " << rowArray[2] << " " << rowArray[3] << endl;
}
if (cols == i) //将txt文本文件中的一行数据存入rowArray中,并将rowArray存入vector中
_vector.push_back(rowArray);
}
fclose(fp);
return 1;
}
int main()
{
/*
定义数据
*/
double data_xy[21][2];//用来记录每个点的X,Y坐标。
double data_q[21];//各点重量
double data_dis[21][21];//各点到各点的距离
int data_fin[21]= {0}; //是否走过此点
int car_num=0;//车的数量
double car_km[5];
double car_kg[5];
int car_pa[5][21];//这辆车经过的点
int carlu[5]={1,1,1,1,1};
data_xy[0][0]=14.2;
data_xy[0][1]=13.1;
data_fin[0]=1;
/*
读取数据
*/
string file ="C:/Users/49786/Desktop/data.txt";
//txt文件中有4列
int columns = 4;
vector<double *> output_vector;
if (!read_scanf(file, columns, output_vector))
{
return 0;
}
//output_vector可视为二维数组;输出数组元素:
int rows = output_vector.size();
for (int i = 0; i < rows; i++)
{
for (int j = 1; j < 4; j++)
{
if(j!=3)
{
data_xy[i+1][j-1]=output_vector[i][j];
}
else
{
data_q[i+1]=output_vector[i][j];
}
}
}
//cout<<data_xy[1][0]<<endl;
//cout<<data_xy[1][1]<<endl;
// cout<<data_q[1]<<endl;
/*
计算各个点之间的距离
*/
for(int i=0; i<21; i++)
{ //cout<<i<<": "<<endl;
for(int j=0; j<21; j++)
{ //cout<<j<<"---"<<endl;
//cout<<"x相减:"<<data_xy[i][0]-data_xy[j][0]<<"平方:"<<pow(data_xy[i][0]-data_xy[j][0],2)<<endl;
// cout<<"y相减:"<<data_xy[i][1]-data_xy[j][1]<<"平方:"<<pow(data_xy[i][1]-data_xy[j][1],2)<<endl;
//cout<<"相加开方"<<sqrt(pow(data_xy[i][0]-data_xy[j][0],2)+pow(data_xy[i][1]-data_xy[j][1],2))<<endl;
data_dis[i][j]=sqrt(pow(data_xy[i][0]-data_xy[j][0],2)+pow(data_xy[i][1]-data_xy[j][1],2));
}
}
/*
for(int i=0;i<21;i++){
cout<<i<<": ";
for(int j=0;j<21;j++){
cout<<data_dis[i][j]<<" ";
}
cout<<endl;
}
*/
for(int car=1; car<=5; car++)
{
double km=50;//最大行驶路程
double kg=8;//最大载重
int di=0;//下一步要走的地点。
int ki=0;//记录下一步
int chong=0;//避免重复
car_pa[car-1][0]=di;
for(int i=0; km>=0||kg>=0; i++)
{
double minum=100;
for(int j=1; j<21;j++)
{
if(j!=di&&data_fin[j]!=1)
{
if(data_dis[di][j]<minum)
{
minum=data_dis[di][j];
ki=j;
//cout<<"是否出现"<<data_fin[j]<<" ";
//cout<<"找出最小值下标:"<<ki<<endl;
//cout<<"找出最小值:"<<minum<<endl;
}
}//找出最小的数值以及下表
}
di=ki;
//cout<<"距离:"<<km-data_dis[car_pa[car-1][i]][di]<<"回去的距离:"<<data_dis[di][0]<<endl;
//cout<<"如果加入那么减去的重量"<<kg-data_q[di]<<endl;
if((km-data_dis[car_pa[car-1][i]][di])>=data_dis[di][0]&&(kg-data_q[di])>=0)
{
if(chong==di){
//这一步大有可为!
car_pa[car-1][i+1]=0;
car_km[car-1]+=data_dis[car_pa[car-1][i]][0];
carlu[car-1]++;
break;
}
car_pa[car-1][i+1]=di;//下一步设置为di
data_fin[di]=1;
km-=data_dis[car_pa[car-1][i]][di];
kg-=data_q[di];
car_km[car-1]+=data_dis[car_pa[car-1][i]][di];
car_kg[car-1]+=data_q[di];
chong=di;
carlu[car-1]++;
}
else
{
//这一步大有可为!
car_pa[car-1][i+1]=0;
car_km[car-1]+=data_dis[car_pa[car-1][i]][0];
carlu[car-1]++;
break;
}
}
int k=1;
for(int i=1;i<21;i++){
if(data_fin[i]==0){
k=0;
}
}
if(k==1)break;
car_num=car_num+1;
}
double carallkm=0;
for(int i=0;i<=car_num;i++){
cout<<"第"<<i+1<<"辆车路径:";
for(int j=0;j<carlu[i];j++){
cout<<car_pa[i][j]<<"-";
}
cout<<"行驶距离:"<<car_km[i]<<endl;
cout<<"载重:"<<car_kg[i]<<endl;
carallkm+=car_km[i];
}
cout<<"总距离"<<carallkm<<endl;
return 0;
}
2. 改进尝试A-失败(╯▔皿▔)╯
a. 改进点讨论
在原始方案中,过程分析的第一点提到了,车辆在最后一个城市会直接返回到原点。
改进:
在到达最后一个城市后,不是直接返回原点,而是挑选能够到达并且到达后还能直接回到原点的城市,载重也必须够用。
因为加入这个新城市,还能够直接跳回原点的必然是相较于贪心选择的城市离原点更近。
只要能多完成一个城市,必然可以减少下一辆车的距离。从而从从整体上优化行车的距离。
判断条件:
1.到达贪心最后一个城市后不再考虑距离,只考虑加入的这个城市后在能够直接回到原点(从符合条件的点中,选择离最后一个城市最近的【值得考虑】)。
2.载重够用。
b.例子分析
以第二辆车为例子:
在第二辆车到达9点时,按照正常计算应该计算 最近的12点是否符合条件,12点 行程足够而重量不足,如黄色箭头所示,按第一种方法应该直接返回出发点。
而根据改进尝试,在到达9点到12点无法达到时,将不再考虑离其最近的路程点,而是考虑剩余的行程和载重合适的点,如蓝色所示。
c. 结果分析
很好 反向操作,负优化 !!!(╯▔皿▔)╯
路线图:
c. 代码
#include <fstream>
#include <string>
#include <iostream>
#include <vector>
#include<math.h>
using namespace std;
// 功能:将filename 中的数据(共cols列)读取到_vector中,_vector可视为二维数组
int read_scanf(const string &filename, const int &cols, vector<double *> &_vector)
{
FILE *fp = fopen(filename.c_str(), "r");
bool flag = true;
int i = 0;
if (!fp)
{
cout << "File open error!\n";
return 0;
}
while (flag)
{
double *rowArray = new double[cols]; //new一个double类型的动态数组
for (i = 0; i < cols; i++) //读取数据,存在_vector[cols]中
{
if (EOF == fscanf(fp,"%lf", &rowArray[i]))
{
flag = false;
break;
}
//输出rowArray存入的数据
//cout << rowArray[0] << " " << rowArray[1] << " " << rowArray[2] << " " << rowArray[3] << endl;
}
if (cols == i) //将txt文本文件中的一行数据存入rowArray中,并将rowArray存入vector中
_vector.push_back(rowArray);
}
fclose(fp);
return 1;
}
int main()
{
/*
定义数据
*/
double data_xy[21][2];//用来记录每个点的X,Y坐标。
double data_q[21];//各点重量
double data_dis[21][21];//各点到各点的距离
int data_fin[21]= {0}; //是否走过此点
int car_num=0;//车的数量
double car_km[5];
double car_kg[5];
int car_pa[5][21];//这辆车经过的点
int carlu[5]={1,1,1,1,1};
data_xy[0][0]=14.2;
data_xy[0][1]=13.1;
data_fin[0]=1;
/*
读取数据
*/
string file ="C:/Users/49786/Desktop/data.txt";
//txt文件中有4列
int columns = 4;
vector<double *> output_vector;
if (!read_scanf(file, columns, output_vector))
{
return 0;
}
//output_vector可视为二维数组;输出数组元素:
int rows = output_vector.size();
for (int i = 0; i < rows; i++)
{
for (int j = 1; j < 4; j++)
{
if(j!=3)
{
data_xy[i+1][j-1]=output_vector[i][j];
}
else
{
data_q[i+1]=output_vector[i][j];
}
}
}
//cout<<data_xy[1][0]<<endl;
//cout<<data_xy[1][1]<<endl;
// cout<<data_q[1]<<endl;
/*
计算各个点之间的距离
*/
for(int i=0; i<21; i++)
{ //cout<<i<<": "<<endl;
for(int j=0; j<21; j++)
{ //cout<<j<<"---"<<endl;
//cout<<"x相减:"<<data_xy[i][0]-data_xy[j][0]<<"平方:"<<pow(data_xy[i][0]-data_xy[j][0],2)<<endl;
// cout<<"y相减:"<<data_xy[i][1]-data_xy[j][1]<<"平方:"<<pow(data_xy[i][1]-data_xy[j][1],2)<<endl;
//cout<<"相加开方"<<sqrt(pow(data_xy[i][0]-data_xy[j][0],2)+pow(data_xy[i][1]-data_xy[j][1],2))<<endl;
data_dis[i][j]=sqrt(pow(data_xy[i][0]-data_xy[j][0],2)+pow(data_xy[i][1]-data_xy[j][1],2));
}
}
/*
for(int i=0;i<21;i++){
cout<<i<<": ";
for(int j=0;j<21;j++){
cout<<data_dis[i][j]<<" ";
}
cout<<endl;
}
*/
for(int car=1; car<=5; car++)
{
double km=50;//最大行驶路程
double kg=8;//最大载重
int di=0;//下一步要走的地点。
int ki=0;//记录下一步
int chong=0;//避免重复
car_pa[car-1][0]=di;
for(int i=0; km>=0||kg>=0; i++)
{
double minum=100;
for(int j=1; j<21;j++)
{
if(j!=di&&data_fin[j]!=1)
{
if(data_dis[di][j]<minum)
{
minum=data_dis[di][j];
ki=j;
//cout<<"是否出现"<<data_fin[j]<<" ";
//cout<<"找出最小值下标:"<<ki<<endl;
//cout<<"找出最小值:"<<minum<<endl;
}
}//找出最小的数值以及下表
}
di=ki;
//cout<<"距离:"<<km-data_dis[car_pa[car-1][i]][di]<<"回去的距离:"<<data_dis[di][0]<<endl;
//cout<<"如果加入那么减去的重量"<<kg-data_q[di]<<endl;
if((km-data_dis[car_pa[car-1][i]][di])>=data_dis[di][0]&&(kg-data_q[di])>=0)
{
if(chong==di){
//这一步大有可为!
car_pa[car-1][i+1]=0;
car_km[car-1]+=data_dis[car_pa[car-1][i]][0];
carlu[car-1]++;
break;
}
car_pa[car-1][i+1]=di;//下一步设置为di
data_fin[di]=1;
km-=data_dis[car_pa[car-1][i]][di];
kg-=data_q[di];
car_km[car-1]+=data_dis[car_pa[car-1][i]][di];
car_kg[car-1]+=data_q[di];
chong=di;
carlu[car-1]++;
}
else
{
double minuu=100;
int uj=0;//记录当前
for(int u=1;u<21;u++){
if(data_fin[u]!=1){
if((km-data_dis[car_pa[car-1][i]][u])>=data_dis[u][0]&&(kg-data_q[u])>=0&&data_dis[u][0]<=data_dis[car_pa[car-1][i]][0]){
if(data_dis[car_pa[car-1][i]][u]<minuu){
minuu=data_dis[car_pa[car-1][i]][u];
uj=u;
}
}
}
}
if(uj!=0){
car_pa[car-1][i+1]=uj;
data_fin[uj]=1;
km-=data_dis[car_pa[car-1][i]][uj];
kg-=data_q[uj];
car_km[car-1]+=data_dis[car_pa[car-1][i]][uj];
car_kg[car-1]+=data_q[uj];
carlu[car-1]++;
}else{
car_pa[car-1][i+1]=0;
car_km[car-1]+=data_dis[car_pa[car-1][i]][0];
carlu[car-1]++;
break;
}
//这一步大有可为!
}
}
int k=1;
for(int i=1;i<21;i++){
if(data_fin[i]==0){
k=0;
}
}
if(k==1)break;
car_num=car_num+1;
}
double carallkm=0;
for(int i=0;i<=car_num;i++){
cout<<"第"<<i+1<<"辆车路径:";
for(int j=0;j<carlu[i];j++){
cout<<car_pa[i][j]<<"-";
}
cout<<"行驶距离:"<<car_km[i]<<endl;
cout<<"载重:"<<car_kg[i]<<endl;
carallkm+=car_km[i];
}
cout<<"总距离"<<carallkm<<endl;
return 0;
}
3. 改进尝试B-略有改进(注意:理论上貌似行不通)
a. 改进点讨论
在原始方案中,采取了选择最小距离的办法,那么我们引入权重,用权重来进行贪心算法(距离/重量 即选取每吨重量所用最少距离的地点)
改进:
将原本的距离,变成 距离/重量,即选取每吨重量所用最小千米数的地点
判断条件:
1.距离与重量的比值,选取最小的。
2.载重够用。
b.例子分析
其实在每个方案的计算中,我们都会拿到每个点到达每个点之间的距离表。现在我们要引入每个点到每个点的距离与重量的比值表,拿它作为贪心的依据:
数据暂不提供。
c. 结果分析
感觉还不错,最起码下降了一点(。・∀・)ノ
路线图:
c. 代码
#include <fstream>
#include <string>
#include <iostream>
#include <vector>
#include<math.h>
using namespace std;
// 功能:将filename 中的数据(共cols列)读取到_vector中,_vector可视为二维数组
int read_scanf(const string &filename, const int &cols, vector<double *> &_vector)
{
FILE *fp = fopen(filename.c_str(), "r");
bool flag = true;
int i = 0;
if (!fp)
{
cout << "File open error!\n";
return 0;
}
while (flag)
{
double *rowArray = new double[cols]; //new一个double类型的动态数组
for (i = 0; i < cols; i++) //读取数据,存在_vector[cols]中
{
if (EOF == fscanf(fp,"%lf", &rowArray[i]))
{
flag = false;
break;
}
//输出rowArray存入的数据
//cout << rowArray[0] << " " << rowArray[1] << " " << rowArray[2] << " " << rowArray[3] << endl;
}
if (cols == i) //将txt文本文件中的一行数据存入rowArray中,并将rowArray存入vector中
_vector.push_back(rowArray);
}
fclose(fp);
return 1;
}
int main()
{
/*
定义数据
*/
double data_xy[21][2];//用来记录每个点的X,Y坐标。
double data_q[21];//各点重量
double data_dis[21][21];//各点到各点的距离
double data_qz[21][21];//各点到各点的权重
int data_fin[21]= {0}; //是否走过此点
int car_num=0;//车的数量
double car_km[5];
double car_kg[5];
int car_pa[5][21];//这辆车经过的点
int carlu[5]={1,1,1,1,1};
data_xy[0][0]=14.2;
data_xy[0][1]=13.1;
data_fin[0]=1;
/*
读取数据
*/
string file ="C:/Users/49786/Desktop/data.txt";
//txt文件中有4列
int columns = 4;
vector<double *> output_vector;
if (!read_scanf(file, columns, output_vector))
{
return 0;
}
//output_vector可视为二维数组;输出数组元素:
int rows = output_vector.size();
for (int i = 0; i < rows; i++)
{
for (int j = 1; j < 4; j++)
{
if(j!=3)
{
data_xy[i+1][j-1]=output_vector[i][j];
}
else
{
data_q[i+1]=output_vector[i][j];
}
}
}
//cout<<data_xy[1][0]<<endl;
//cout<<data_xy[1][1]<<endl;
// cout<<data_q[1]<<endl;
/*
计算各个点之间的距离
*/
for(int i=0; i<21; i++)
{ //cout<<i<<": "<<endl;
for(int j=0; j<21; j++)
{ //cout<<j<<"---"<<endl;
//cout<<"x相减:"<<data_xy[i][0]-data_xy[j][0]<<"平方:"<<pow(data_xy[i][0]-data_xy[j][0],2)<<endl;
// cout<<"y相减:"<<data_xy[i][1]-data_xy[j][1]<<"平方:"<<pow(data_xy[i][1]-data_xy[j][1],2)<<endl;
//cout<<"相加开方"<<sqrt(pow(data_xy[i][0]-data_xy[j][0],2)+pow(data_xy[i][1]-data_xy[j][1],2))<<endl;
data_dis[i][j]=sqrt(pow(data_xy[i][0]-data_xy[j][0],2)+pow(data_xy[i][1]-data_xy[j][1],2));
}
}
for(int i=0; i<21; i++)
{ //cout<<i<<": "<<endl;
for(int j=0; j<21; j++)
{ //cout<<j<<"---"<<endl;
//cout<<"x相减:"<<data_xy[i][0]-data_xy[j][0]<<"平方:"<<pow(data_xy[i][0]-data_xy[j][0],2)<<endl;
// cout<<"y相减:"<<data_xy[i][1]-data_xy[j][1]<<"平方:"<<pow(data_xy[i][1]-data_xy[j][1],2)<<endl;
//cout<<"相加开方"<<sqrt(pow(data_xy[i][0]-data_xy[j][0],2)+pow(data_xy[i][1]-data_xy[j][1],2))<<endl;
data_qz[i][j]=data_dis[i][j]/data_q[j];
}
}
/*
for(int i=0;i<21;i++){
cout<<i<<": ";
for(int j=0;j<21;j++){
cout<<data_dis[i][j]<<" ";
}
cout<<endl;
}
*/
for(int car=1; car<=5; car++)
{
double km=50;//最大行驶路程
double kg=8;//最大载重
int di=0;//下一步要走的地点。
int ki=0;//记录下一步
int chong=0;//避免重复
car_pa[car-1][0]=di;
for(int i=0; km>=0||kg>=0; i++)
{
double minum=100;
for(int j=1; j<21;j++)
{
if(j!=di&&data_fin[j]!=1)
{
if(data_qz[di][j]<minum)
{
minum=data_qz[di][j];
ki=j;
//cout<<"是否出现"<<data_fin[j]<<" ";
//cout<<"找出最小值下标:"<<ki<<endl;
//cout<<"找出最小值:"<<minum<<endl;
}
}//找出最小的数值以及下表
}
di=ki;
//cout<<"距离:"<<km-data_dis[car_pa[car-1][i]][di]<<"回去的距离:"<<data_dis[di][0]<<endl;
//cout<<"如果加入那么减去的重量"<<kg-data_q[di]<<endl;
if((km-data_dis[car_pa[car-1][i]][di])>=data_dis[di][0]&&(kg-data_q[di])>=0)
{
if(chong==di){
//这一步大有可为!
car_pa[car-1][i+1]=0;
car_km[car-1]+=data_dis[car_pa[car-1][i]][0];
carlu[car-1]++;
break;
}
car_pa[car-1][i+1]=di;//下一步设置为di
data_fin[di]=1;
km-=data_dis[car_pa[car-1][i]][di];
kg-=data_q[di];
car_km[car-1]+=data_dis[car_pa[car-1][i]][di];
car_kg[car-1]+=data_q[di];
chong=di;
carlu[car-1]++;
}
else
{
//这一步大有可为!
car_pa[car-1][i+1]=0;
car_km[car-1]+=data_dis[car_pa[car-1][i]][0];
carlu[car-1]++;
break;
}
}
int k=1;
for(int i=1;i<21;i++){
if(data_fin[i]==0){
k=0;
}
}
if(k==1)break;
car_num=car_num+1;
}
double carallkm=0;
for(int i=0;i<=car_num;i++){
cout<<"第"<<i+1<<"辆车路径:";
for(int j=0;j<carlu[i];j++){
cout<<car_pa[i][j]<<"-";
}
cout<<"行驶距离:"<<car_km[i]<<endl;
cout<<"载重:"<<car_kg[i]<<endl;
carallkm+=car_km[i];
}
cout<<"总距离"<<carallkm<<endl;
return 0;
}
4. 改进尝试C-在改进A中引入B的比值法
a. 改进点讨论
在改进A中引入B的比值来进行路线的查找。
改进:
在尝试A的基础上,将原本的距离,变成 距离/重量,即选取每吨重量所用最小千米数的地点
判断条件:
1.在尝试A的基础上距离与重量的比值,选取最小的。
2.载重够用。
b. 结果分析
略逊于B方案,相较于原始改进A 略强
路线图:
c. 代码
#include <fstream>
#include <string>
#include <iostream>
#include <vector>
#include<math.h>
using namespace std;
// 功能:将filename 中的数据(共cols列)读取到_vector中,_vector可视为二维数组
int read_scanf(const string &filename, const int &cols, vector<double *> &_vector)
{
FILE *fp = fopen(filename.c_str(), "r");
bool flag = true;
int i = 0;
if (!fp)
{
cout << "File open error!\n";
return 0;
}
while (flag)
{
double *rowArray = new double[cols]; //new一个double类型的动态数组
for (i = 0; i < cols; i++) //读取数据,存在_vector[cols]中
{
if (EOF == fscanf(fp,"%lf", &rowArray[i]))
{
flag = false;
break;
}
//输出rowArray存入的数据
//cout << rowArray[0] << " " << rowArray[1] << " " << rowArray[2] << " " << rowArray[3] << endl;
}
if (cols == i) //将txt文本文件中的一行数据存入rowArray中,并将rowArray存入vector中
_vector.push_back(rowArray);
}
fclose(fp);
return 1;
}
int main()
{
/*
定义数据
*/
double data_xy[21][2];//用来记录每个点的X,Y坐标。
double data_q[21];//各点重量
double data_dis[21][21];//各点到各点的距离
double data_qz[21][21];
int data_fin[21]= {0}; //是否走过此点
int car_num=0;//车的数量
double car_km[5];
double car_kg[5];
int car_pa[5][21];//这辆车经过的点
int carlu[5]={1,1,1,1,1};
data_xy[0][0]=14.2;
data_xy[0][1]=13.1;
data_fin[0]=1;
/*
读取数据
*/
string file ="C:/Users/49786/Desktop/data.txt";
//txt文件中有4列
int columns = 4;
vector<double *> output_vector;
if (!read_scanf(file, columns, output_vector))
{
return 0;
}
//output_vector可视为二维数组;输出数组元素:
int rows = output_vector.size();
for (int i = 0; i < rows; i++)
{
for (int j = 1; j < 4; j++)
{
if(j!=3)
{
data_xy[i+1][j-1]=output_vector[i][j];
}
else
{
data_q[i+1]=output_vector[i][j];
}
}
}
//cout<<data_xy[1][0]<<endl;
//cout<<data_xy[1][1]<<endl;
// cout<<data_q[1]<<endl;
/*
计算各个点之间的距离
*/
for(int i=0; i<21; i++)
{ //cout<<i<<": "<<endl;
for(int j=0; j<21; j++)
{ //cout<<j<<"---"<<endl;
//cout<<"x相减:"<<data_xy[i][0]-data_xy[j][0]<<"平方:"<<pow(data_xy[i][0]-data_xy[j][0],2)<<endl;
// cout<<"y相减:"<<data_xy[i][1]-data_xy[j][1]<<"平方:"<<pow(data_xy[i][1]-data_xy[j][1],2)<<endl;
//cout<<"相加开方"<<sqrt(pow(data_xy[i][0]-data_xy[j][0],2)+pow(data_xy[i][1]-data_xy[j][1],2))<<endl;
data_dis[i][j]=sqrt(pow(data_xy[i][0]-data_xy[j][0],2)+pow(data_xy[i][1]-data_xy[j][1],2));
}
}
for(int i=0; i<21; i++)
{ //cout<<i<<": "<<endl;
for(int j=0; j<21; j++)
{ //cout<<j<<"---"<<endl;
//cout<<"x相减:"<<data_xy[i][0]-data_xy[j][0]<<"平方:"<<pow(data_xy[i][0]-data_xy[j][0],2)<<endl;
// cout<<"y相减:"<<data_xy[i][1]-data_xy[j][1]<<"平方:"<<pow(data_xy[i][1]-data_xy[j][1],2)<<endl;
//cout<<"相加开方"<<sqrt(pow(data_xy[i][0]-data_xy[j][0],2)+pow(data_xy[i][1]-data_xy[j][1],2))<<endl;
data_qz[i][j]=data_dis[i][j]/data_q[j];
}
}
/*
for(int i=0;i<21;i++){
cout<<i<<": ";
for(int j=0;j<21;j++){
cout<<data_dis[i][j]<<" ";
}
cout<<endl;
}
*/
for(int car=1; car<=5; car++)
{
double km=50;//最大行驶路程
double kg=8;//最大载重
int di=0;//下一步要走的地点。
int ki=0;//记录下一步
int chong=0;//避免重复
car_pa[car-1][0]=di;
for(int i=0; km>=0||kg>=0; i++)
{
double minum=100;
for(int j=1; j<21;j++)
{
if(j!=di&&data_fin[j]!=1)
{
if(data_qz[di][j]<minum)
{
minum=data_qz[di][j];
ki=j;
//cout<<"是否出现"<<data_fin[j]<<" ";
//cout<<"找出最小值下标:"<<ki<<endl;
//cout<<"找出最小值:"<<minum<<endl;
}
}//找出最小的数值以及下表
}
di=ki;
//cout<<"距离:"<<km-data_dis[car_pa[car-1][i]][di]<<"回去的距离:"<<data_dis[di][0]<<endl;
//cout<<"如果加入那么减去的重量"<<kg-data_q[di]<<endl;
if((km-data_dis[car_pa[car-1][i]][di])>=data_dis[di][0]&&(kg-data_q[di])>=0)
{
if(chong==di){
//这一步大有可为!
car_pa[car-1][i+1]=0;
car_km[car-1]+=data_dis[car_pa[car-1][i]][0];
carlu[car-1]++;
break;
}
car_pa[car-1][i+1]=di;//下一步设置为di
data_fin[di]=1;
km-=data_dis[car_pa[car-1][i]][di];
kg-=data_q[di];
car_km[car-1]+=data_dis[car_pa[car-1][i]][di];
car_kg[car-1]+=data_q[di];
chong=di;
carlu[car-1]++;
}
else
{
double minuu=100;
int uj=0;//记录当前
for(int u=1;u<21;u++){
if(data_fin[u]!=1){
if((km-data_dis[car_pa[car-1][i]][u])>=data_dis[u][0]&&(kg-data_q[u])>=0&&data_dis[u][0]<=data_dis[car_pa[car-1][i]][0]){
if(data_dis[car_pa[car-1][i]][u]<minuu){
minuu=data_dis[car_pa[car-1][i]][u];
uj=u;
}
}
}
}
if(uj!=0){
car_pa[car-1][i+1]=uj;
data_fin[uj]=1;
km-=data_dis[car_pa[car-1][i]][uj];
kg-=data_q[uj];
car_km[car-1]+=data_dis[car_pa[car-1][i]][uj];
car_kg[car-1]+=data_q[uj];
carlu[car-1]++;
}else{
car_pa[car-1][i+1]=0;
car_km[car-1]+=data_dis[car_pa[car-1][i]][0];
carlu[car-1]++;
break;
}
//这一步大有可为!
}
}
int k=1;
for(int i=1;i<21;i++){
if(data_fin[i]==0){
k=0;
}
}
if(k==1)break;
car_num=car_num+1;
}
double carallkm=0;
for(int i=0;i<=car_num;i++){
cout<<"第"<<i+1<<"辆车路径:";
for(int j=0;j<carlu[i];j++){
cout<<car_pa[i][j]<<"-";
}
cout<<"行驶距离:"<<car_km[i]<<endl;
cout<<"载重:"<<car_kg[i]<<endl;
carallkm+=car_km[i];
}
cout<<"总距离"<<carallkm<<endl;
return 0;
}
思路二:暴力求解
1.思路分析
对于运输的路径,我们采取随机的方式生成,也就是将20个点随机的排列组合,然后进行计算。
整体上思路比较简单,但是需要花费的时间较多。
2.过程分析
首先会随机生成一个序列,从出发点,顺着此序列进行计算,当不满足条件时回到出发点,换下一辆车继续计算,知道路径走完或者车走完为止。
例如:
注意!!!
这张表格只是为了方便向大家展示随机生成的序列,我们每次都是随机生成一条的,并不是生成一张表格,因为这样的话,消耗太大,也没有必要。
每个序列完成后,都会记录其所需要的路程和其路径。
3.判断条件
1.加入此点后,能否直接回到出发点,如果不能那么回到出发点,换下一辆车(贪心算法原始方案相同)直到计算完成整个序列,记录其值和路径。
2.记录当前获取的总路径的最小值,如果序列在未走完时所消耗的路程已经大于当前总路径的最小值,那么直接舍弃,重新生成新的序列计算。
3.载重够用。
4.结果分析
暴力求解结果过于随机,理论上只要无限跑总能找到最优的解。
并且因为是随机生成的序列,所以理论上还是看运气。
就像下面一样两亿次还不如一亿次(╯▔皿▔)╯
下面是分别跑了 1亿次 和 2亿次的结果和路径图:
- 1亿次得出的最小的总路径:
路径:第一辆车:0-6-20-11-17-4-3-0
第二辆车:0-5-1-15-19-16-13-8-0
第三辆车:0-2-9-12-7-10-14-0
第四辆车:0-18-0
总里程:134.39KM
路径图:
- 2亿次得出的最小的总路径:
路径:第一辆车:0-18-8-6-13-11-17-0
第二辆车:0-20-19-16-15-1-10-5-0
第三辆车:0-4-2-12-9-7-14-0
第四辆车:0-3-0
总里程:138.09KM
路径图:
5.代码
思路三:智能算法
代码
https://download.csdn.net/download/jnbfknasf113/18908669?spm=1001.2014.3001.5503
1.禁忌算法
(1) 禁忌算法基本思路
1.选定一个初始解𝑋_𝑛𝑜𝑤;令禁忌表𝐻=∅ 。
-
若满足终止准则,则转第四步;否则,在𝑋_𝑛𝑜𝑤的邻域𝑁(𝑋_𝑛𝑜𝑤)中选出满足禁忌要求的候选集𝐶𝑎𝑛_𝑁(𝑋_𝑛𝑜𝑤),转第三步。
-
在𝐶𝑎𝑛_𝑁(𝑋_𝑛𝑜𝑤)中选一个评价值最好的解𝑋_𝑏𝑒𝑠𝑡,令𝑋_𝑛𝑜𝑤=𝑋_𝑏𝑒𝑠𝑡,更新禁忌表H,转第二步。
-
输出计算结果,停止。
(2)禁忌算法伪代码
(3)禁忌算法具体实现细节
初始化:随机产生一个可行路径作为初始解,将其设置为最优路径。清空禁忌表,设置禁忌长度(5, 10, 15)。
邻域搜索产生候选解:邻域函数(交换两个元素)。我们探测该解的全邻域,即一共190个解(19+18+ … +2+1),将其中满足限制条件(重量、距离)且未被禁忌的解挑选出来作为邻域。
选择最好的候选解:在邻域中选出适应度(距离最短)最好的候选解,并将其与当前最优值进行比较。如果优于当前最佳解,则更新最佳解。更新禁忌表(禁忌该操作方式,并将其他被禁方式禁忌长度减1),将该最好的候选解作为下一轮的种子。
判断终止条件: 迭代是否达到200次。
(4)禁忌算法算法结果
2.模拟退火算法
(1) 模拟退火算法基本思路
1.选定一个初始解𝑋_𝑛𝑜𝑤;设定初始温度𝑇_s。
-
若满足终止准则,则转第四步;否则,在𝑋_𝑛𝑜𝑤的邻域𝑁(𝑋_𝑛𝑜𝑤 ) 中选出候选集𝐶𝑎𝑛_𝑁(𝑋_𝑛𝑜𝑤),转第三步。
-
在𝐶𝑎𝑛_𝑁(𝑋_𝑛𝑜𝑤)中选一个评价值最好的解𝑋_𝑏𝑒𝑠𝑡。如果𝑋_best优于𝑋_(𝑐𝑢𝑟_𝑏𝑒𝑠𝑡),则令𝑋_𝑛𝑜𝑤=𝑋_𝑏𝑒𝑠𝑡,并且更新𝑋_(𝑐𝑢𝑟_𝑏𝑒𝑠𝑡);否则,以一定的概率p接受𝑋_𝑏𝑒𝑠𝑡 作为下一轮的种子。降温,转第二步。
-
输出计算结果,停止。
(2)模拟退火算法伪代码
(3)模拟退火算法具体实现细节
初始化: 随机产生一个可行路径并设置初始温度𝑻(𝟎) = 𝟏𝟎𝟎。
退火速率: 1. 𝑻(𝒏)=𝒍𝒓 ∗𝑻(𝒏−𝟏),我们设置的lr = 0.95。
2. 𝑻(𝒏)=𝑻(𝟎)/(𝐥𝐨𝐠(𝟏+𝒏))
3. 𝑻(𝒏)=(𝑻(𝟎))/(𝟏+𝒏)
邻域搜索产生候选解:邻域函数(交换两个元素)。随机交换两个元素40次。
选择下一轮种子: 找到邻域中的最优值𝑋_𝑏𝑒𝑠𝑡,如果𝑿_𝒃𝒆𝒔𝒕优于𝑿_(𝒄𝒖𝒓_𝒃𝒆𝒔𝒕),则𝑿_𝒃𝒆𝒔𝒕作为下一轮种子。否则,下一轮种子 = 𝐞𝐱𝐩((𝑿_𝒃𝒆𝒔𝒕−𝑿_(𝒄𝒖𝒓_𝒃𝒆𝒔𝒕))/𝑻(𝒌) ) > random[0,1)? 𝑿_𝒃𝒆𝒔𝒕: 𝑿_𝒏𝒐𝒘
以上述公式降低温度。若迭代次数达到100次,则停止。
(4)模拟退火算法算法结果
3.遗传算法
(1) 遗传算法基本思路
1. 根据问题的目标函数构造适值函数(即适应度)。
2. 产生一个随即种群(100-1000)。
3. 根据适应度计算选择概率,不断进行繁殖。
4. 若干代后得到适应度最好的个体即最优解。
(2)遗传算法伪代码
(3)遗传算法具体实现细节
初始种群的产生: 随机产生一条路径,判断是否是可行解,若是则添加到初始种群中,若不是则舍弃。循环该过程直至初始种群数量达到指定值(100,500,1000)。
编码方法:用1~20组成的序列代表一条路径。
适应度:个体i的适应度 = 𝟏 − 𝒅_𝒊/(∑_(𝒋∈𝑵)▒𝒅_𝒋 ) 。其中𝑑_𝑖表示个体i的路径长度,N表示当前种群。
遗传运算:双切点交叉;变异概率2%
选择策略:个体i被选择概率 = 个体i的适应度/(种群数量-1),这一步是为了使所有个体被选择的概率和等于1。采用赌轮法随机选择父体。
停止准则:达到一定的迭代次数(迭代次数1000)。
(4)遗传算法结果
(5)图