物流运输-最小化门店个数问题
例如,有一批食材要发到肯德基门店,分别分配给了2个司机去送货,如下:
1 2 3 4 5 6 7 8 9 10 11 | 都从上海出发 2个司机 第一个司机: 苏州 新苏站餐厅、园区车坊餐厅 无锡 高铁无锡东站餐厅、无锡城际餐厅 第二个司机: 无锡 海岸城餐厅、无锡城际餐厅、大润发 餐厅 常州 常州百货大楼餐厅、常州城际 餐厅 |
某位调度大哥一看,都经过无锡,还都要往“无锡城际餐厅”送货,感觉重复了,不能合并?现在2位司机的送货门店个数分别是4个门店以及5个门店,能不能缩减下(由于发现了公共门店,因此缩减门店数变成了可能)
对于这个问题,我们首先不考虑货物能不能装上同一辆车这种容量问题,先考虑门店合并问题,其次考虑容量问题。此文中只说门店合并问题。
和上篇的风格一样,先贴出我们期望的output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ------------原始组合------------- 第一个司机: 4个门店 full: 新苏站餐厅,园区车坊餐厅,高铁无锡东站餐厅,无锡城际餐厅, fixed: 新苏站餐厅,园区车坊餐厅, dynamic: 高铁无锡东站餐厅,无锡城际餐厅, 第二个司机: 5个门店 full: 常州百货大楼餐厅,常州城际 餐厅,海岸城餐厅,无锡城际餐厅,大润发 餐厅, fixed: 常州百货大楼餐厅,常州城际 餐厅, dynamic: 海岸城餐厅,无锡城际餐厅,大润发 餐厅, -------------移动组合------------- 第一个司机(5个门店):新苏站餐厅, 海岸城餐厅, 高铁无锡东站餐厅, 园区车坊餐厅, 大润发 餐厅, 第二个司机(3个门店):常州城际 餐厅, 无锡城际餐厅, 常州百货大楼餐厅, 从第一个司机move到第二个司机的:无锡城际餐厅, 从第二个司机move到第一个司机的:海岸城餐厅, 大润发 餐厅, 第一个司机(3个门店):新苏站餐厅, 无锡城际餐厅, 园区车坊餐厅, 第二个司机(5个门店):常州城际 餐厅, 海岸城餐厅, 高铁无锡东站餐厅, 常州百货大楼餐厅, 大润发 餐厅, 从第一个司机move到第二个司机的:海岸城餐厅, 从第二个司机move到第一个司机的:无锡城际餐厅, 第一个司机(5个门店):新苏站餐厅, 海岸城餐厅, 无锡城际餐厅, 园区车坊餐厅, 大润发 餐厅, 第二个司机(3个门店):常州城际 餐厅, 高铁无锡东站餐厅, 常州百货大楼餐厅, 从第一个司机move到第二个司机的:海岸城餐厅, 从第二个司机move到第一个司机的:海岸城餐厅, 无锡城际餐厅, 大润发 餐厅, 第一个司机(3个门店):新苏站餐厅, 海岸城餐厅, 园区车坊餐厅, 第二个司机(5个门店):常州城际 餐厅, 高铁无锡东站餐厅, 无锡城际餐厅, 常州百货大楼餐厅, 大润发 餐厅, 从第一个司机move到第二个司机的:海岸城餐厅, 无锡城际餐厅, 从第二个司机move到第一个司机的:海岸城餐厅, 第一个司机(3个门店):新苏站餐厅, 园区车坊餐厅, 大润发 餐厅, 第二个司机(5个门店):常州城际 餐厅, 海岸城餐厅, 高铁无锡东站餐厅, 无锡城际餐厅, 常州百货大楼餐厅, 从第一个司机move到第二个司机的:海岸城餐厅, 无锡城际餐厅, 从第二个司机move到第一个司机的:大润发 餐厅, -------------SUMMARY------------- OPTIMAL 有效移动组合个数:5 |
解题思路:
首先判读这个问题的类型,目测来看数据量较小,不存在穷举非常耗时这种问题,因此首选采用确定性算法,而不是非确定性算法(遗传算法、粒子算法这种是非确定的,每次执行都会变,特别是数据量大的时候)
既然选择确定性算法&&数据量小,因此就穷举组合了,按照上面的城市组合,又分成了固定与动态的城市区分。固定代表这个城市内的门店是不可调整的,动态代表这个城市内的门店是可调整的,可调整代表可以与其他司机的同城市的门店进行互换操作,其实区分固定与动态城市后,算法的数据量会进一步减小,因为只要穷举动态城市部分就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | public static void main(String[] args) { List<ReceiverItem> leftItems=generateData(ListUtils.newList( "新苏站餐厅" , "园区车坊餐厅" ), ListUtils.newList( "高铁无锡东站餐厅" , "无锡城际餐厅" )); List<ReceiverItem> rightItems=generateData(ListUtils.newList( "常州百货大楼餐厅" , "常州城际 餐厅" ), ListUtils.newList( "海岸城餐厅" , "无锡城际餐厅" , "大润发 餐厅" )); List<IntVar> leftFixedVars= new ArrayList<>(); List<IntVar> leftExchangableVars= new ArrayList<>(); List<IntVar> rightFixedVars= new ArrayList<>(); List<IntVar> rightExchangableVars= new ArrayList<>(); CpModel model = new CpModel(); //第一个司机 for (ReceiverItem receiverItem:leftItems) { if (!receiverItem.exchangable) { //不可以交换,因此固定为第一个司机 IntVar var=model.newConstant( 1 ); leftFixedVars.add(var); } else { IntVar var=model.newIntVar( 1 , 2 , "" ); leftExchangableVars.add(var); } } //第二个司机 for (ReceiverItem receiverItem:rightItems) { if (!receiverItem.exchangable) { //不可以交换,因此固定为第二个司机 IntVar var=model.newConstant( 2 ); rightFixedVars.add(var); } else { IntVar var=model.newIntVar( 1 , 2 , "" ); rightExchangableVars.add(var); } } VarArraySolutionPrinter varArraySolutionPrinter= new VarArraySolutionPrinter(leftItems, rightItems, leftFixedVars, leftExchangableVars, rightFixedVars, rightExchangableVars); CpSolver solver = new CpSolver(); System.out.println( "------------原始组合-------------" ); PrintUtils.display组合( "第一个司机:" , leftItems); PrintUtils.display组合( "第二个司机:" , rightItems); System.out.println( "-------------移动组合-------------" ); CpSolverStatus status=solver.searchAllSolutions(model, varArraySolutionPrinter); System.out.println( "-------------SUMMARY-------------" ); System.out.println(status); List<MoveAction> moveActions=varArraySolutionPrinter.getMoveActions(); System.out.println( "有效移动组合个数:" +moveActions.size()); } static class VarArraySolutionPrinter extends CpSolverSolutionCallback { private List<ReceiverItem> leftItems; private List<ReceiverItem> rightItems; private List<IntVar> leftFixedVars; private List<IntVar> leftExchangableVars; private List<IntVar> rightFixedVars; private List<IntVar> rightExchangableVars; private List<MoveAction> moveActions= new ArrayList<>(); public List<MoveAction> getMoveActions() { return this .moveActions; } public VarArraySolutionPrinter(List<ReceiverItem> leftItems, List<ReceiverItem> rightItems, List<IntVar> leftFixedVars, List<IntVar> leftExchangableVars, List<IntVar> rightFixedVars, List<IntVar> rightExchangableVars) { this .leftItems=leftItems; this .rightItems=rightItems; this .leftFixedVars=leftFixedVars; this .leftExchangableVars=leftExchangableVars; this .rightFixedVars=rightFixedVars; this .rightExchangableVars=rightExchangableVars; } @Override public void onSolutionCallback() { Set<String> leftReceivers= new HashSet<>(); Set<String> rightReceivers= new HashSet<>(); List<String> originalleft= new ArrayList<>(); List<String> move2left= new ArrayList<>(); List<String> originalright= new ArrayList<>(); List<String> move2right= new ArrayList<>(); //left fixed for ( int idx= 0 ;idx<leftFixedVars.size();idx++) { leftReceivers.add(leftItems.get(idx).name); } //left exchangable for ( int idx= 0 ;idx<leftExchangableVars.size();idx++) { IntVar var=leftExchangableVars.get(idx); long v=value(var); if (v== 1 ) { leftReceivers.add(leftItems.get(idx+leftFixedVars.size()).name); originalleft.add(leftItems.get(idx+leftFixedVars.size()).name); } } for ( int idx= 0 ;idx<rightExchangableVars.size();idx++) { IntVar var=rightExchangableVars.get(idx); long v=value(var); if (v== 1 ) { leftReceivers.add(rightItems.get(idx+rightFixedVars.size()).name); move2left.add(rightItems.get(idx+rightFixedVars.size()).name); } } //right fixed for ( int idx= 0 ;idx<rightFixedVars.size();idx++) { rightReceivers.add(rightItems.get(idx).name); } //right exchangable for ( int idx= 0 ;idx<leftExchangableVars.size();idx++) { IntVar var=leftExchangableVars.get(idx); long v=value(var); if (v== 2 ) { rightReceivers.add(leftItems.get(idx+leftFixedVars.size()).name); move2right.add(rightItems.get(idx+rightFixedVars.size()).name); } } for ( int idx= 0 ;idx<rightExchangableVars.size();idx++) { IntVar var=rightExchangableVars.get(idx); long v=value(var); if (v== 2 ) { rightReceivers.add(rightItems.get(idx+rightFixedVars.size()).name); originalright.add(rightItems.get(idx+rightFixedVars.size()).name); } } if (leftReceivers.size()<= 5 &&rightReceivers.size()<= 5 &&(leftReceivers.size()<= 3 ||rightReceivers.size()<= 3 )) //最终穷举出来的要满足的约束 { if (move2left.size()> 0 &&move2right.size()> 0 ) { PrintUtils.displayReceivers(leftReceivers, rightReceivers, "" ); PrintUtils.displayActions(originalleft, move2left, originalright, move2right); MoveAction moveAction = new MoveAction(); moveAction.setMove2Left(move2left); moveAction.setMove2Right(move2right); moveActions.add(moveAction); } } } } |
自省推动进步,视野决定未来。
心怀远大理想。
为了家庭幸福而努力。
商业合作请看此处:https://www.magicube.ai
心怀远大理想。
为了家庭幸福而努力。
商业合作请看此处:https://www.magicube.ai
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】