teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

3

d

2019/06/25 03:50

投稿

tiitoi
tiitoi

スコア21960

answer CHANGED
@@ -17,6 +17,7 @@
17
17
  2部グラフの最大重みマッチング
18
18
 
19
19
  [Maximum Matching in General Graphs](https://www.slideshare.net/akhayyat/maximum-matching-in-general-graphs)
20
+ [最大重みマッチング問題](http://www.ocw.titech.ac.jp/index.php?module=General&action=DownLoad&file=201702129-632-0-12.pdf&type=cal&JWC=201702129)
20
21
 
21
22
  できる限り各個人の希望を反映したマッチング ==> 最大重みマッチングです。
22
23
 
@@ -41,13 +42,140 @@
41
42
  # グラフを作成する。
42
43
  G = nx.Graph()
43
44
  for person, wish in wishes.items():
44
- for i, snack in enumerate(wish):
45
+ for snack, weight in zip(wish, range(3, 0, -1)):
45
46
  # (人, 希望するお菓子) という辺を追加する。
46
- # 重みは第1希望: 0, 第2希望: -1, 第3希望: -2 になる。
47
+ # 重みは第1希望: 3, 第2希望: 2, 第3希望: 1 になる。
47
- G.add_edge(person, snack, weight=-i)
48
+ G.add_edge(person, snack, weight=weight)
48
49
 
49
- # 「辺の数が最大 (maxcardinality=True)となるマッチング」のうち、「重み最大となるマッチング」を探す。
50
+ # 「重み最大となるマッチング」を探す。
50
- matches = nx.max_weight_matching(G, maxcardinality=True)
51
+ matches = nx.max_weight_matching(G)
52
+ print(matches)
51
- # {('ケーキ', 'Bさん'), ('メロン', 'Aさん'),
53
+ # {('ケーキ', 'Bさん'), ('プリン', 'Dさん'), ('いちご', 'Cさん'), ('Aさん', 'メロン')}
54
+
55
+ y = sum([G.edges[e1, e2]["weight"] for e1, e2 in matches])
56
+ print(f"y = {y}")
57
+ ```
58
+
59
+ ## 追記 整数計画問題として解く方法
60
+
61
+ 次のように変数を定義することで整数計画問題としても定式化できます。
62
+
63
+ ![イメージ説明](7174e2e50e65a18e4623e68bc26cdf8a.png)
64
+
65
+ [PuLP](https://pythonhosted.org/PuLP/) という線形ソルバーで説いたところ、networkx の最大重みマッチングの結果と一致することが確認できました。
66
+
67
+ ```python
68
+ from pulp import *
69
+
70
+ # お菓子の一覧
71
+ snacks = ["いちご", "キャラメル", "ケーキ", "プリン", "メロン"]
72
+
73
+ # 子供の希望
74
+ wishes = {
75
+ # "名前": [第1希望, 第2希望, 第3希望]
76
+ "Aさん": ["プリン", "ケーキ", "メロン"],
77
+ "Bさん": ["ケーキ", "いちご", "メロン"],
52
- # ('Cさん', 'いちご'), ('Dさん', 'プリン')}
78
+ "Cさん": ["いちご", "プリン", "キャラメル"],
79
+ "Dさん": ["プリン", "いちご", "メロン"],
80
+ }
81
+
82
+ # モデルを作成する。
83
+ model = LpProblem(name="max weight matching", sense=LpMaximize)
84
+
85
+ # # 変数を作成する。
86
+ X = [
87
+ [LpVariable(f"{person}_{snack}", cat=LpBinary) for snack in snacks]
88
+ for person in wishes
89
+ ]
90
+
91
+ # 目的関数を設定する。
92
+ weights = range(3, 0, -1) # 重み: 第1希望:3、第2希望:2、第3希望:1
93
+ snack_to_idx = {name: i for i, name in enumerate(snacks)} # お菓子の名前がキー、インデックスが値の辞書
94
+
95
+ y = 0
96
+ for i, wish in enumerate(wishes.values()):
97
+ for snack, w in zip(wish, weights):
98
+ j = snack_to_idx[snack]
99
+ y += X[i][j] * w
100
+ model += y
101
+
102
+ # 制約を設定する。
103
+
104
+ # (1) 一人1つしかお菓子を選べない。
105
+ for i in range(len(wishes)):
106
+ model += lpSum(X[i]) == 1
107
+
108
+ # (2) 二人以上が同じお菓子を選べない。
109
+ for j in range(len(snacks)):
110
+ s = 0
111
+ for i in range(len(wishes)):
112
+ s += X[i][j]
113
+ model += s <= 1
114
+
115
+ # モデルを表示する。
116
+ print(model)
117
+
118
+ # 解く。
119
+ ret = model.solve()
120
+ print("status", LpStatus[ret]) # status Optimal
121
+
122
+ # 解けた場合は結果を表示する。
123
+ if ret == 1:
124
+ print(f"y = {model.objective.value()}")
125
+ for i, person in enumerate(wishes):
126
+ select_idx = np.argmax([x.value() for x in X[i]]) # 選択したお菓子のインデックス
127
+ print(f"{person}: {snacks[select_idx]}")
128
+ ```
129
+
130
+ ```output
131
+ max weight matching:
132
+ MAXIMIZE
133
+ 2*Aさん_ケーキ + 3*Aさん_プリン + 1*Aさん_メロン + 2*Bさん_いちご + 3*Bさん_ケーキ + 1*Bさん_メロン + 3*Cさん_いちご + 1*Cさん_キャラメル + 2*Cさん_プリン + 2*Dさん_いちご + 3*Dさん_プリン + 1*Dさん_メロン + 0
134
+ SUBJECT TO
135
+ _C1: Aさん_いちご + Aさん_キャラメル + Aさん_ケーキ + Aさん_プリン + Aさん_メロン = 1
136
+
137
+ _C2: Bさん_いちご + Bさん_キャラメル + Bさん_ケーキ + Bさん_プリン + Bさん_メロン = 1
138
+
139
+ _C3: Cさん_いちご + Cさん_キャラメル + Cさん_ケーキ + Cさん_プリン + Cさん_メロン = 1
140
+
141
+ _C4: Dさん_いちご + Dさん_キャラメル + Dさん_ケーキ + Dさん_プリン + Dさん_メロン = 1
142
+
143
+ _C5: Aさん_いちご + Bさん_いちご + Cさん_いちご + Dさん_いちご <= 1
144
+
145
+ _C6: Aさん_キャラメル + Bさん_キャラメル + Cさん_キャラメル + Dさん_キャラメル <= 1
146
+
147
+ _C7: Aさん_ケーキ + Bさん_ケーキ + Cさん_ケーキ + Dさん_ケーキ <= 1
148
+
149
+ _C8: Aさん_プリン + Bさん_プリン + Cさん_プリン + Dさん_プリン <= 1
150
+
151
+ _C9: Aさん_メロン + Bさん_メロン + Cさん_メロン + Dさん_メロン <= 1
152
+
153
+ VARIABLES
154
+ 0 <= Aさん_いちご <= 1 Integer
155
+ 0 <= Aさん_キャラメル <= 1 Integer
156
+ 0 <= Aさん_ケーキ <= 1 Integer
157
+ 0 <= Aさん_プリン <= 1 Integer
158
+ 0 <= Aさん_メロン <= 1 Integer
159
+ 0 <= Bさん_いちご <= 1 Integer
160
+ 0 <= Bさん_キャラメル <= 1 Integer
161
+ 0 <= Bさん_ケーキ <= 1 Integer
162
+ 0 <= Bさん_プリン <= 1 Integer
163
+ 0 <= Bさん_メロン <= 1 Integer
164
+ 0 <= Cさん_いちご <= 1 Integer
165
+ 0 <= Cさん_キャラメル <= 1 Integer
166
+ 0 <= Cさん_ケーキ <= 1 Integer
167
+ 0 <= Cさん_プリン <= 1 Integer
168
+ 0 <= Cさん_メロン <= 1 Integer
169
+ 0 <= Dさん_いちご <= 1 Integer
170
+ 0 <= Dさん_キャラメル <= 1 Integer
171
+ 0 <= Dさん_ケーキ <= 1 Integer
172
+ 0 <= Dさん_プリン <= 1 Integer
173
+ 0 <= Dさん_メロン <= 1 Integer
174
+
175
+ status Optimal
176
+ y = 10.0
177
+ Aさん: メロン
178
+ Bさん: ケーキ
179
+ Cさん: いちご
180
+ Dさん: プリン
53
181
  ```

2

d

2019/06/25 03:50

投稿

tiitoi
tiitoi

スコア21960

answer CHANGED
@@ -18,6 +18,8 @@
18
18
 
19
19
  [Maximum Matching in General Graphs](https://www.slideshare.net/akhayyat/maximum-matching-in-general-graphs)
20
20
 
21
+ できる限り各個人の希望を反映したマッチング ==> 最大重みマッチングです。
22
+
21
23
  ## Python のサンプルコード
22
24
 
23
25
  Python だと networkx というグラフライブラリの max_weight_matching() という関数で実装されています。

1

d

2019/06/24 05:43

投稿

tiitoi
tiitoi

スコア21960

answer CHANGED
@@ -29,6 +29,7 @@
29
29
 
30
30
  # 子供の希望
31
31
  wishes = {
32
+ # "名前": [第1希望, 第2希望, 第3希望]
32
33
  "Aさん": ["プリン", "ケーキ", "メロン"],
33
34
  "Bさん": ["ケーキ", "いちご", "メロン"],
34
35
  "Cさん": ["いちご", "プリン", "ケーキ"],