物流运输一般是通过路线来的,但是考虑到实际场景,有可能具体城市的区与区之间是互斥的,不能放在同一条路线内,比如由于某一条较长的河分开了某些区域导致不能放一条路线内运输;又比如运输证是按照区域来发放的等等。
比如如下几个节点:昆山(区:昆山市)、苏州(区:相城区、吴中区)、佛山(区:顺德区)、佛山(区:高明区) 这些节点城市+区域,我们假设:苏州是个特别的城市,相城区、吴中区不能同时配在一条路线内部,求解
这个解最终是希望得到如下这样的结果:
存在的解决方案个数:2 昆山(昆山市)--->苏州(吴中区)--->佛山(顺德区)--->佛山(高明区)---> 昆山(昆山市)--->苏州(相城区)--->佛山(顺德区)--->佛山(高明区)--->
这次用google的OR Tool来解决这个问题,链接在此处:https://developers.google.cn/optimization
先定义数据结构,以及初始化demo数据
public static class CityLocation { public String cityName; public String districtName; public boolean special; public CityLocation(String cityName, String districtName) { this(cityName, districtName, false); } public CityLocation(String cityName, String districtName, boolean special) { this.cityName=cityName; this.districtName=districtName; this.special=special; } } private static List<CityLocation> generateData() { List<CityLocation> result=new ArrayList<>(); CityLocation location=new CityLocation("昆山", "昆山市"); result.add(location); location=new CityLocation("苏州", "吴中区", true); result.add(location); location=new CityLocation("苏州", "相城区", true); result.add(location); location=new CityLocation("佛山", "顺德区"); result.add(location); location=new CityLocation("佛山", "高明区"); result.add(location); return result; }
先说下思路,google的这个算法框架,需要加入一堆约束,比如==约束、<=约束、<=约束等等,然后算法框架会自己去穷举;又或者做最优化,又或者做可行解(意思就是非最优化解)。
我们先来加约束:
static class SpecialCityRestrict implements LinearExpr { private List<IntVar> vars; public SpecialCityRestrict(List<IntVar> vars) { this.vars=vars; } @Override public int numElements() { return this.vars.size(); } @Override public IntVar getVariable(int i) { return this.vars.get(i); } @Override public long getCoefficient(int i) { return 1; } } //main方法里加 SpecialCityRestrict restrict=new SpecialCityRestrict(varsInner); model.addLessOrEqual(restrict, 1);
model.addLessOrEqual就是加<=的约束,上述就是加了个<=1的约束,比如约束苏州的区最多只能出现1次,也可以不出现的意思,<=1次
上方的List<IntVar> vars就是用来变换苏州区是否被放入路线的控制变量,是个集合,会在main里放进去,如下:
List<CityLocation> globalCityLocations=generateData(); Map<String, List<IntVar>> specialCityVarsMap=new HashMap<>(); //特殊城市对应的or控制变量关系Map List<String> specialCities=globalCityLocations.stream().filter(f->f.special).map(f->f.cityName).distinct().collect(Collectors.toList()); //筛选出所有特殊城市 for(String specialCity:specialCities) specialCityVarsMap.put(specialCity, new ArrayList<>()); //初始化Map CpModel model = new CpModel(); //or tool模型构建
List<IntVar> globalVars=new ArrayList<IntVar>(); //这里会存放所有控制变量,后续会把这些控制变量传入收集解决方案解的回调类中
for(int idx=0;idx<globalCityLocations.size();idx++)
{
IntVar var=model.newIntVar(0, 1, String.valueOf(idx)); //新建1个控制变量-int类型,值变化区间是[0, 1]
globalVars.add(var);
CityLocation cityLocation=globalCityLocations.get(idx);
if(cityLocation.special)
{
specialCityVarsMap.get(cityLocation.cityName).add(var); //如果是特殊城市,就把控制变量维护到特殊城市控制变量关系map中
specialCityVars.add(var);
}
}
for(String key:specialCityVarsMap.keySet()) //动态的从特殊城市map中开始加模型约束,不用写死,很好
{
List<IntVar> varsInner=specialCityVarsMap.get(key);
SpecialCityRestrict restrict=new SpecialCityRestrict(varsInner); //针对某个具体特殊城市的约束对象
model.addLessOrEqual(restrict, 1); //对应的区的个数约束为<=1
}
然后就要搜索解决方案了
VarArraySolutionPrinter varArraySolutionPrinter=new VarArraySolutionPrinter(globalVars, globalCityLocations); //这个类是自定义的,用来收集解的集合的,在后面有定义
CpSolver solver = new CpSolver();
CpSolverStatus status=solver.searchAllSolutions(model, varArraySolutionPrinter);
System.out.println(status);
varArraySolutionPrinter.printSolutions();
static class VarArraySolutionPrinter extends CpSolverSolutionCallback { private int solutionCount; private List<IntVar> globalVars; private List<CityLocation> globalCityLocations; private List<List<CityLocation>> solutions=new ArrayList<>(); public VarArraySolutionPrinter(List<IntVar> globalVars, List<CityLocation> globalCityLocations) { this.globalCityLocations=globalCityLocations; this.globalVars=globalVars; } @Override public void onSolutionCallback() { List<CityLocation> oneSolution=new ArrayList<>(); for(int idx=0;idx<globalVars.size();idx++) { IntVar v=globalVars.get(idx); long oneOrZero=value(v); if(oneOrZero==1) { oneSolution.add(this.globalCityLocations.get(idx)); } } solutions.add(oneSolution); solutionCount++; } public int getSolutionCount() { return solutionCount; } public void printSolutions() { System.out.println("存在的解决方案个数:"+this.getSolutionCount()); for(List<CityLocation> oneSolution:this.solutions) { StringBuilder sb=new StringBuilder(); for(CityLocation cityLocation:oneSolution) { sb.append(cityLocation.cityName+"("+cityLocation.districtName+")--->"); } System.out.println(" "+sb.toString()); } System.out.println("--END--"); } }
运行后的结果出乎意料:
OPTIMAL 存在的解决方案个数:24 苏州(吴中区)---> 苏州(相城区)---> 苏州(相城区)--->佛山(顺德区)---> 佛山(顺德区)---> 苏州(吴中区)--->佛山(顺德区)---> 苏州(吴中区)--->佛山(顺德区)--->佛山(高明区)---> 佛山(顺德区)--->佛山(高明区)---> 苏州(相城区)--->佛山(顺德区)--->佛山(高明区)---> 苏州(相城区)--->佛山(高明区)---> 佛山(高明区)---> 苏州(吴中区)--->佛山(高明区)---> 昆山(昆山市)--->苏州(吴中区)--->佛山(高明区)---> 昆山(昆山市)--->苏州(吴中区)--->佛山(顺德区)--->佛山(高明区)---> 昆山(昆山市)--->苏州(吴中区)--->佛山(顺德区)---> 昆山(昆山市)--->苏州(吴中区)---> 昆山(昆山市)---> 昆山(昆山市)--->佛山(顺德区)---> 昆山(昆山市)--->佛山(顺德区)--->佛山(高明区)---> 昆山(昆山市)--->佛山(高明区)---> 昆山(昆山市)--->苏州(相城区)--->佛山(高明区)---> 昆山(昆山市)--->苏州(相城区)---> 昆山(昆山市)--->苏州(相城区)--->佛山(顺德区)---> 昆山(昆山市)--->苏州(相城区)--->佛山(顺德区)--->佛山(高明区)---> --END--
似乎感觉不太对,得再加些约束啊,看来。
我们要求特殊城市必须出现在线路中,也就是说特殊城市节点的约束是>=1
在执行搜索的代码前加入这些代码:
SpecialCityRestrict specialCityMustExistsRestrict=new SpecialCityRestrict(specialCityVars); model.addGreaterOrEqual(specialCityMustExistsRestrict, 1);
然后再加个约束:非特殊城市必须出现在路线之中,也就是非特殊城市变换后选中为1的size要==非特殊城市的个数
SpecialCityRestrict normalCityRestrict=new SpecialCityRestrict(normalCityVars);
model.addEquality(normalCityRestrict, normalCityVars.size()); //这个normalCityVars需要改main里的循环,既:当为普通city时把控制变量add到这个List中
再执行下
OPTIMAL 存在的解决方案个数:2 昆山(昆山市)--->苏州(吴中区)--->佛山(顺德区)--->佛山(高明区)---> 昆山(昆山市)--->苏州(相城区)--->佛山(顺德区)--->佛山(高明区)---> --END--