容量限制的设施位置问题

一、 问题陈述

问题如上图,下面为中文描述:

       有容量限制的设施地址问题:假设有n个设施和m个顾客,我们可以作以下操作:

       ①开启设施 ②分配顾客到某设施

       上述两个操作都有各自的成本,我们希望总成本最低,且分配到某设施的总需求不能超过其容量。

二、建立模型

  为了方便问题的解决,我们首先建立模型,更具体地说,我们为设施、顾客创建一个具有相应属性的类。

  我们以一个实例来更好地了解如何构建一个类:

  

  由上图可知,设施有容量、开启费用、是否开启、服务某个顾客的费用四个属性;顾客有需求、被哪个设施服务两个属性。为了区分每个设施和顾客,我们用ID区分他们,由此建立Facility,Customer两个类:

    public class Facility {
        int facilityId;
        int capacity;
        int cost;
        boolean open;
        // 从customerId -> cost的映射
        Map<Integer, Integer> assignmentCost;
        public int getFacilityId() {
            return facilityId;
        }
        public void setFacilityId(int facilityId) {
            this.facilityId = facilityId;
        }
        public int getCapacity() {
            return capacity;
        }
        public void setCapacity(int capacity) {
            this.capacity = capacity;
        }
        public int getCost() {
            return cost;
        }
        public void setCost(int cost) {
            this.cost = cost;
        }
        public Map<Integer, Integer> getAssignmentCost() {
            return assignmentCost;
        }
        public void setAssignmentCost(Map<Integer, Integer> assignmentCost) {
            this.assignmentCost = assignmentCost;
        }
        public boolean isOpen() {
            return open;
        }
        public void setOpen(boolean open) {
            this.open = open;
        }
        public Facility(Facility faci) {
            super();
            this.facilityId = faci.facilityId;
            this.capacity = faci.capacity;
            this.cost = faci.cost;
            this.open = faci.open;
            this.assignmentCost = faci.assignmentCost;
        }
        public Facility() {}
    }
    
类Facility
 1     public class Customer {
 2         int customerId;
 3         int demand;
 4         int assignedTo; // 去哪个设施
 5         public int getCustomerId() {
 6             return customerId;
 7         }
 8         public void setCustomerId(int customerId) {
 9             this.customerId = customerId;
10         }
11         public int getDemand() {
12             return demand;
13         }
14         public void setDemand(int demand) {
15             this.demand = demand;
16         }
17         public int getAssignedTo() {
18             return assignedTo;
19         }
20         public void setAssignedTo(int assignedTo) {
21             this.assignedTo = assignedTo;
22         }
23         public Customer(Customer cust) {
24             super();
25             this.customerId = cust.customerId;
26             this.demand = cust.demand;
27             this.assignedTo = cust.assignedTo;
28         }
29         public Customer() {}
30     }
类Customer

三、 读取文件及展示

  在解决问题前,我们需要得到数据,以方便测试。一个样例的数据格式和第二部分的第一张图一样,输出结果的格式如下图:

 

       我们为样例也构造一个类,格式如下:

 1 public class Instance {
 2         int result;
 3         int time;
 4         String id;
 5         List<Boolean> openList;
 6         List<Integer> assignmentList;
 7         public int getResult() {
 8             return result;
 9         }
10         public void setResult(int result) {
11             this.result = result;
12         }
13         public int getTime() {
14             return time;
15         }
16         public void setTime(int time) {
17             this.time = time;
18         }
19         public List<Boolean> getOpenList() {
20             return openList;
21         }
22         public void setOpenList(List<Boolean> openList) {
23             this.openList = openList;
24         }
25         public List<Integer> getAssignmentList() {
26             return assignmentList;
27         }
28         public void setAssignmentList(List<Integer> assignmentList) {
29             this.assignmentList = assignmentList;
30         }
31         public String getId() {
32             return id;
33         }
34         public void setId(String id) {
35             this.id = id;
36         }
37         
38     }
类Instance

       我们用List保存每个顾客、每个设施、每个实例,以及记录他们的数量:

  建立好数据结构后,我们编写读取文件和初始化每个对象的代码:

 1 //读取文件内容,默认文件内容格式正确,不做检查
 2     public void ReadFileAndInit(String path) {
 3         File file = new File(path);
 4         //System.out.println(path);
 5         BufferedReader bReader = null;
 6         try {
 7             // 字符串相关
 8             String str;
 9             List<Integer> intList = null;
10             bReader = new BufferedReader(new FileReader(file));
11 
12             // 读取设施和顾客数量
13             str = bReader.readLine();
14             intList = getNumberFromLine(str);
15             facilityNum = intList.get(0).intValue();
16             customerNum = intList.get(1).intValue();
17             
18             // 读取设施容量和开销
19             for (int i = 0; i < facilityNum; i++) {
20                 str = bReader.readLine();
21                 intList = getNumberFromLine(str);
22                 Facility faci = new Facility();
23                 faci.setCapacity(intList.get(0).intValue());
24                 faci.setCost(intList.get(1).intValue());
25                 faci.setOpen(false);
26                 faci.setFacilityId(i);
27                 faci.setAssignmentCost(new HashMap<Integer, Integer>());
28                 facilityList.add(faci);
29             }
30             // 读取顾客的需求
31             for (int i = 0; i < customerNum; ) {
32                 str = bReader.readLine();
33                 intList = getNumberFromLine(str);
34                 for (Integer tmp : intList) {
35                     Customer cust = new Customer();
36                     cust.setAssignedTo(-1);
37                     cust.setCustomerId(i);
38                     cust.setDemand(tmp);
39                     customerList.add(cust);
40                     i++;
41                 }
42             }
43             // 读取每个顾客到设施的开销
44             for (int i = 0; i < facilityNum; i++) {
45                 for (int j = 0; j < customerNum; ) {
46                     str = bReader.readLine();
47                     intList = getNumberFromLine(str);
48                     Facility faci = facilityList.get(i);
49                     for (Integer tmp : intList) {
50                         faci.getAssignmentCost().put(new Integer(j), tmp);
51                         j++;
52                     }
53                 }
54             }
55         } catch(Exception e) {
56             e.printStackTrace();
57         } finally {
58             if (bReader != null) {
59                 try {
60                     bReader.close();
61                 } catch(Exception ex) {
62                     ex.printStackTrace();
63                 }
64             }
65         }
66     }
读取样例及初始化

       再编写用于展示的代码:

 1     public void GenerateTable() {
 2         if (instanceList == null) {
 3             return;
 4         }
 5         System.out.println("\t"+"result"+ " " + "Time(s)");
 6         
 7         for (Instance ins : instanceList) {
 8             System.out.print(ins.getId() + "   ");
 9             System.out.print(ins.getResult());
10             System.out.print("   ");
11             // 转化为毫秒
12             System.out.print((double)ins.getTime()/1000);
13             System.out.print("\n");
14         }
15     }
16     public void DisplayInstance() {
17         if (instanceList == null) {
18             return;
19         }
20         for (Instance ins : instanceList) {
21             System.out.println(ins.getResult());
22             for (Boolean bool : ins.getOpenList()) {
23                 System.out.print(bool ? 1 : 0);
24                 System.out.print(" ");
25             }
26             System.out.println("");
27             for (Integer tmp : ins.getAssignmentList()) {
28                 System.out.print(tmp.intValue());
29                 System.out.print(" ");
30             }
31             System.out.println("");
32         }
33     }
展示结果

  编写用于生成实例的代码:

 1     public Instance GenerateInstance(String id) {
 2         Instance ins = new Instance();
 3         long t1 = System.currentTimeMillis();
 4         //int result = Greedy();
 5         int result = SimulateAnneal();
 6         long t2 = System.currentTimeMillis();
 7         List<Boolean> openList = new ArrayList<Boolean>();
 8         List<Integer> assignmentList = new ArrayList<Integer>();
 9         for (Facility faci : facilityList) {
10             openList.add(faci.isOpen());
11         }
12         for (Customer cust : customerList) {
13             assignmentList.add(cust.getAssignedTo());
14         }
15         ins.setId(id);
16         ins.setResult(result);
17         ins.setTime((int)(t2-t1));
18         ins.setOpenList(openList);
19         ins.setAssignmentList(assignmentList);
20         
21         return ins;
22     }
生成实例Instance

四、问题思路及算法

1.    贪心算法

  比较简单的解决办法是贪心算法,虽然不能够得到最优解,但它的思路最直接、最简单,实现起来简单,且时间复杂度不算高,下面说下贪心算法在该问题下的运用。

  N个用户,编号为1-N,首先编号1选择服务费用最低的且容量足够的设施,编号2一样,只不过在1选择之后选择,以此类推,这并没有考虑到设施的开启费用,这是因为顾客的数量一般比设施多,所以如果设施开启的费用相对服务顾客的费用比较低的话,设施开启的费用是个次要矛盾,因为服务费用占的比例会大很多,当然,如果这个前提不成立的话,贪心算法的效果会差很多。

       根据上面所说,我们编写代码:

 1     public int Greedy() {
 2         int result = 0;
 3         for (Customer cust : customerList) {
 4             int demand = cust.getDemand();
 5             int cost = Integer.MAX_VALUE;
 6             int faciId = -1;
 7             for (Facility faci : facilityList) {
 8                 Map<Integer, Integer> assignmentMap = faci.getAssignmentCost();
 9                 int assignmentCost = assignmentMap.get(cust.getCustomerId());
10                 if (assignmentCost < cost && faci.getCapacity() >= demand) {
11                     cost = assignmentCost;
12                     faciId = faci.getFacilityId();
13                 }    
14             }
15             cust.setAssignedTo(faciId);
16             if (faciId >= 0) {
17                 Facility faci = facilityList.get(faciId);
18                 result += cost;
19                 if (!faci.isOpen()) {
20                     faci.setOpen(true);
21                     result += faci.getCost();
22                 }
23                 faci.setCapacity(faci.getCapacity()-demand);
24             }
25 
26         }
27         return result;
28     }
贪心算法

  具体效果在最后一同展示。

2.    模拟退火

  模拟退火算法来源于固体退火原理,是一种基于概率的算法,将固体加温至充分高,再让其徐徐冷却,加温时,固体内部粒子随温升变为无序状,内能增大,而徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小。

根据热力学规律并结合计算机对离散数据的处理, 我们定义: 如果当前温度为T, 当前状态与新状态之间的能量差为ΔE , 则发生状态转移的概率为:

  伪代码如下图(来自一篇博客):

http://www.cnblogs.com/heaad/archive/2010/12/20/1911614.html#!comments

 1 /*
 2 * J(y):在状态y时的评价函数值
 3 * Y(i):表示当前状态
 4 * Y(i+1):表示新的状态
 5 * r: 用于控制降温的快慢
 6 * T: 系统的温度,系统初始应该要处于一个高温的状态
 7 * T_min :温度的下限,若温度T达到T_min,则停止搜索
 8 */
 9 while( T > T_min )
10 {
11   dE = J( Y(i+1) ) - J( Y(i) ) ; 
12 
13   if ( dE >=0 ) //表达移动后得到更优解,则总是接受移动
14 Y(i+1) = Y(i) ; //接受从Y(i)到Y(i+1)的移动
15   else
16   {
17 // 函数exp( dE/T )的取值范围是(0,1) ,dE/T越大,则exp( dE/T )也
18 if ( exp( dE/T ) > random( 0 , 1 ) )
19 Y(i+1) = Y(i) ; //接受从Y(i)到Y(i+1)的移动
20   }
21   T = r * T ; //降温退火 ,0<r<1 。r越大,降温越慢;r越小,降温越快
22   /*
23   * 若r过大,则搜索到全局最优解的可能会较高,但搜索的过程也就较长。若r过小,则搜索的过程会很快,但最终可能会达到一个局部最优值
24   */
25   i ++ ;
26 }

 

  在该问题下,如果想得到新的状态Y(i+1),还不是十分清晰。换句话说,我们需要考虑如何得到邻近解,我采用的策略有两个:一是将两个顾客位置调换,即挑两个顾客出来,让一个顾客去另一个顾客的设施,另一个顾客去该顾客的设施。二是让一个顾客去另一个设施。顾客都是随机挑选的,两个策略在某个时刻时仅会执行一个。另外如果执行策略时,发现某些不合法的行为,就不会执行,直接放弃,例如某个设施容量不足。因为策略和顾客都是随机挑选的,且执行策略的次数会很大,所以放弃执行某次策略并不会影响整体效果。

综上,我们执行模拟退火的步骤如下:

  ①为了方便,状态初始化为贪心算法里的结果,设定初始温度,终止温度,温度下降率。

  ②开始循环,在某个温度时(内循环),根据上述两种策略得到临近解,然后将得到的临近解和当前解进行比较,采取状态转移的步骤,由公式得到概率,决定是否向较差的情况转移。内循环结束后,将当前解与最优解比较,更新最优解。开始降温。

  ③当温度降至终止温度时,结束循环。得到该算法下最有解。

  代码如下:

 1 public int SimulateAnneal() {
 2         double temper = 100000; //初始温度
 3         double minTemper = 0.001; //终止温度
 4         double coolRate = 0.99;
 5         double count = 1000;
 6         // 初始状态,为了方便选用贪婪算法的解
 7         int bestVal = Greedy();
 8         int curVal = bestVal;
 9         int nextVal = bestVal;
10         List<Facility> facilityListBestCopy = new ArrayList<Facility>();
11         List<Customer> customerListBestCopy = new ArrayList<Customer>();
12         for (Facility faci : facilityList) {
13             facilityListBestCopy.add(new Facility(faci));
14         }
15         for (Customer cust : customerList) {
16             customerListBestCopy.add(new Customer(cust));
17         }
18         while (temper > minTemper) {
19             for (int i = 0; i < count; i++) {
20                 //拷贝,用于还原
21                 List<Facility> facilityListCopy = new ArrayList<Facility>();
22                 List<Customer> customerListCopy = new ArrayList<Customer>();
23                 for (Facility faci : facilityList) {
24                     facilityListCopy.add(new Facility(faci));
25                 }
26                 for (Customer cust : customerList) {
27                     customerListCopy.add(new Customer(cust));
28                 }
29                 nextVal = GetNextResult(curVal);
30                 double delta = nextVal - curVal;
31                 if (delta < 0) {
32                     curVal = nextVal;
33                 } else {
34                     if (Math.exp(-delta/temper) > Math.random()) {
35                         curVal = nextVal;
36                     } else {
37                         facilityList = facilityListCopy;
38                         customerList = customerListCopy;
39                     }
40                 }
41             }
42             
43             if (curVal < bestVal) {
44                 bestVal = curVal;
45                 facilityListBestCopy = facilityList;
46                 customerListBestCopy = customerList;
47             }
48             temper *= coolRate;
49         }
50         facilityList = facilityListBestCopy;
51         customerList = customerListBestCopy;
52         return bestVal;
53     }
模拟退火

五、运算结果

  设施开启状态和顾客去了哪个设施的结果可以在https://github.com/thougr/CFLP/tree/master/src/docs查看。

  下面展示每个实例的运算时间和问题的结果(时间精度为毫秒):

 

result(SA)

Time(s)

result(Greedy)

Time(s)

p1

8958

2.738

9440

0.001

p2

8010

2.187

8126

0

p3

9389

1.974

10126

0.001

p4

10714

1.978

12126

0

p5

9142

1.966

9375

0

p6

7809

1.985

8061

0.007

p7

9577

1.971

10061

0.001

p8

11173

1.931

12061

0

p9

8742

2.074

9040

0.001

p10

7617

2.045

7726

0.002

p11

9077

2.508

9726

0.002

p12

10132

2.066

11726

0

p13

8492

2.418

12032

0

p14

7526

2.391

9180

0.002

p15

8937

2.512

13180

0

p16

10764

2.458

17180

0.001

p17

8378

2.335

12032

0.002

p18

7152

2.351

9180

0.002

p19

9042

2.406

13180

0

p20

11071

2.417

17180

0

p21

8667

2.427

12032

0

p22

7194

2.402

9180

0.001

p23

8746

2.434

13180

0

p24

11483

2.394

17180

0

p25

13191

5.039

19197

0.002

p26

11022

4.95

16131

0.002

p27

13037

4.919

21531

0.002

p28

16410

4.925

26931

0.002

p29

13289

4.96

19305

0.001

p30

12171

4.893

16239

0.001

p31

14228

4.937

21639

0.001

p32

15903

5.005

27039

0.001

p33

12220

4.973

19055

0.002

p34

11004

5.006

15989

0.001

p35

13637

4.926

21389

0

p36

15004

4.929

26789

0

p37

11935

4.946

19055

0

p38

10984

4.933

15989

0.001

p39

12984

4.944

21389

0.001

p40

14984

4.951

26789

0

p41

7103

2.932

7226

0

p42

6678

3.201

9957

0

p43

6758

3.038

12448

0

p44

7128

2.848

7585

0

p45

7478

3.102

9848

0

p46

6160

3.044

12639

0

p47

6257

2.865

6634

0

p48

6642

3.069

9044

0

p49

5658

3.048

12420

0

p50

9239

3.12

10062

0

p51

7920

3.451

11351

0.001

p52

9247

3.042

10364

0

p53

9319

3.43

12470

0

p54

9034

3.028

10351

0

p55

7938

3.451

11970

0

p56

22710

6.109

23882

0.001

p57

29464

6.079

32882

0.001

p58

43765

6.105

53882

0

p59

32854

6.113

39121

0.001

p60

23086

6.144

23882

0.001

p61

30093

6.193

32882

0.002

p62

41891

6.261

53882

0.001

p63

31788

6.32

39121

0.001

p64

22443

6.136

23882

0.003

p65

29279

6.15

32882

0.001

p66

44219

6.124

53882

0.001

p67

32471

7.23

39671

0

p68

23024

6.149

23882

0.001

p69

30318

6.145

32882

0.017

p70

43835

6.152

53882

0

p71

32071

6.128

39121

0

 

 

 

 

 

    由上面的运算结果可以看出,贪心算法运算的很快,但相对来说结果没有那么好,模拟退火算法运算时间上升了很多,但结果优化了很多。

  完整代码可以在 https://github.com/thougr/CFLP 看到。

转载于:https://www.cnblogs.com/thougr/p/10166441.html

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 设施选址问题是指在给定的区域内,确定一个或多个设施位置,以便最大限度地满足需求并最小化成本。经典的设施选址模型包括以下几类: 1. 单设施选址模型:在给定的区域内选择一个位置建设一个设施,使得该位置到需求点的距离最小化。 2. 多设施选址模型:在给定的区域内选择多个位置建设设施,使得所有需求点到最近的设施的距离之和最小化。 3. 设施选址-服务区域模型:在给定的区域内选择多个位置建设设施,并确定每个设施的服务区域,使得所有需求点到最近的设施的距离之和最小化,并且每个需求点都被至少一个设施所服务。 这些模型的特点是:单设施选址模型比较简单,但是只适用于某些特定的情况;多设施选址模型能够更好地满足需求,但是计算复杂度较高;设施选址-服务区域模型能够满足需求并考虑服务区域的限制,但是计算复杂度更高,需要更多的输入信息。 ### 回答2: 设施选址问题是指在某个区域内确定设施的最佳选址位置,以使得各种因素达到最优。经典模型主要分为三类,分别是最小总链接成本模型、最小总覆盖半径模型和最大服务覆盖模型。 最小总链接成本模型是一种常见的经典模型,其特点在于需要确定设施位置,以使得所有用户到设施的链接成本之和最小。这种模型考虑了用户对设施的使用频率和链接成本,通常采用数学优化方法来求解。 最小总覆盖半径模型是另一种经典模型,其特点在于需要确定设施位置,以使得所有用户离最近的设施的距离之和最小。这种模型主要考虑用户对设施的可达性,通常采用图论和几何优化方法来解决。 最大服务覆盖模型是第三类经典模型,其特点在于需要确定设施位置,以使得设施的服务范围最大化。这种模型注重设施容量和服务范围,通常采用地理信息系统(GIS)和网络覆盖分析来求解。 这三类经典模型在设施选址问题中各有特点,选择合适的模型取决于问题的具体情况和需求。最小总链接成本模型适用于用户链接成本较重要的情况,最小总覆盖半径模型适用于用户可达性较重要的情况,最大服务覆盖模型适用于设施服务范围较重要的情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值