物流運輸-最小化門店個數問題

例如,有一批食材要發到肯德基門店,分別分配給了2個司機去送貨,如下: 

都從上海出發
2個司機

第一個司機:
	蘇州	新蘇站餐廳、園區車坊餐廳
	無錫	高鐵無錫東站餐廳、無錫城際餐廳


第二個司機:
	無錫	海岸城餐廳、無錫城際餐廳、大潤發 餐廳
	常州	常州百貨大樓餐廳、常州城際 餐廳  

 

某位調度大哥一看,都經過無錫,還都要往「無錫城際餐廳」送貨,感覺重複了,不能合併?現在2位司機的送貨門店個數分別是4個門店以及5個門店,能不能縮減下(由於發現了公共門店,因此縮減門店數變成了可能)

對於這個問題,我們首先不考慮貨物能不能裝上同一輛車這種容量問題,先考慮門店合併問題,其次考慮容量問題。此文中只說門店合併問題。

和上篇的風格一樣,先貼出我們期望的output:

------------原始組合-------------
第一個司機:       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  

 

 解題思路:

首先判讀這個問題的類型,目測來看數據量較小,不存在窮舉非常耗時這種問題,因此首選採用確定性算法,而不是非確定性算法(遺傳算法、粒子算法這種是非確定的,每次執行都會變,特別是數據量大的時候)

既然選擇確定性算法&&數據量小,因此就窮舉組合了,按照上面的城市組合,又分成了固定與動態的城市區分。固定代表這個城市內的門店是不可調整的,動態代表這個城市內的門店是可調整的,可調整代表可以與其他司機的同城市的門店進行互換操作,其實區分固定與動態城市後,算法的數據量會進一步減小,因為只要窮舉動態城市部分就行了。

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);
                }
            }
        }
    }