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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
|
package nl.camilstaps.cs.graphs;
import java.util.*;
import java.util.stream.Collectors;
/**
* A Graph is a list of Nodes.
*
* @author Camil Staps
*/
public class Graph<T> {
private final List<Node<T>> nodes;
// When removing nodes, it's possible to create restore points to add them back later on.
private final Stack<List<Node<T>>> restorePoints = new Stack<>();
public Graph() {
this.nodes = new ArrayList<>();
restorePoint();
}
public Graph(List<Node<T>> nodes) {
this.nodes = nodes;
restorePoint();
}
/**
* Computes the size of the maximum independent set using Robson's algorithm.
*
* @see #maximumIndependentSetSizeWith1From(Node, Node)
* @see #maximumIndependentSetSizeWith2From(Collection)
*
* @return the size of the maximum independent set
*/
public int maximumIndependentSetSize() {
if (nodes.size() <= 1)
return nodes.size();
int miss;
restorePoint();
Graph<T> stronglyConnectedComponent = findStronglyConnectedComponent();
if (stronglyConnectedComponent.size() == size()) {
// Case: connected graph. Pick a nice Node A (low degree) and a neighbour B with high degree.
Collections.sort(nodes);
Node<T> A = nodes.get(0);
Collections.sort(A.getNeighbourhood(), Collections.reverseOrder());
Node<T> B = A.getNeighbourhood().get(0);
int try_1, try_2;
switch (A.getDegree()) {
case 1:
// One neighbour; pick A and continue with the rest of the Graph.
removeNodes(A.getInclusiveNeighbourhood());
miss = 1 + maximumIndependentSetSize();
break;
case 2:
// Two neighbours: if they are connected pick A and continue with the rest of the Graph. If not,
// try both picking the neighbours and picking A with two of its second order neighbours.
Node<T> B_ = A.getNeighbourhood().get(1);
if (B.getNeighbourhood().contains(B_)) {
removeNodes(A.getInclusiveNeighbourhood());
miss = 1 + maximumIndependentSetSize();
} else {
restorePoint();
removeNodes(B.getInclusiveNeighbourhood());
removeNodes(B_.getInclusiveNeighbourhood());
try_1 = 2 + maximumIndependentSetSize();
restore();
removeNodes(A.getInclusiveNeighbourhood());
try_2 = 1 + maximumIndependentSetSizeWith2From(A.getSecondNeighbourhood());
miss = Math.max(try_1, try_2);
}
break;
case 3:
// Three neighbours: either we pick two of the neighbours, or we pick A.
removeNode(A);
try_1 = maximumIndependentSetSizeWith2From(A.getNeighbourhood());
removeNodes(A.getNeighbourhood());
try_2 = 1 + maximumIndependentSetSize();
miss = Math.max(try_1, try_2);
break;
default:
// If A dominates his neighbour we may safely remove that neighbour. Otherwise, we try both with
// and without the neighbour.
if (A.dominates(B)) {
removeNode(B);
miss = maximumIndependentSetSize();
} else {
removeNode(B);
try_1 = maximumIndependentSetSize();
removeNodes(B.getNeighbourhood());
try_2 = 1 + maximumIndependentSetSize();
miss = Math.max(try_1, try_2);
}
break;
}
} else {
// Case: at least two strongly connected components. Compute for the first component, and for the rest.
miss = stronglyConnectedComponent.maximumIndependentSetSize();
removeNodes(stronglyConnectedComponent.nodes);
miss += maximumIndependentSetSize();
}
restore();
return miss;
}
/**
* Compute the maximum independent set size if it should contain (at least) one of two nodes.
*
* This is a helper function for {@link #maximumIndependentSetSizeWith2From(Collection)}, which is a helper
* function for {@link #maximumIndependentSetSize()}, which uses Robson's algorithm.
*
* @param s_1 The first node
* @param s_2 The second node
* @return the maximum independent set size
*/
private int maximumIndependentSetSizeWith1From(Node<T> s_1, Node<T> s_2) {
assert s_1.getDegree() <= s_2.getDegree();
int miss;
restorePoint();
if (s_1.getDegree() <= 1) {
miss = maximumIndependentSetSize();
} else if (s_1.getNeighbourhood().contains(s_2)) {
if (s_1.getDegree() <= 3) {
miss = maximumIndependentSetSize();
} else {
int try_1, try_2;
restorePoint();
removeNodes(s_1.getInclusiveNeighbourhood());
try_1 = maximumIndependentSetSize();
restore();
removeNodes(s_2.getInclusiveNeighbourhood());
try_2 = maximumIndependentSetSize();
miss = Math.max(try_1, try_2) + 1;
}
} else if (!s_1.neighbourhoodsDisjoint(s_2)) {
removeNodes(s_1.neighbourhoodIntersection(s_2));
miss = maximumIndependentSetSizeWith1From(s_1, s_2);
} else if (s_2.getDegree() == 2) {
Node<T> E = s_1.getNeighbourhood().get(0), F = s_1.getNeighbourhood().get(1);
if (E.getNeighbourhood().contains(F)) {
removeNodes(s_1.getInclusiveNeighbourhood());
miss = 1 + maximumIndependentSetSize();
} else {
boolean subset = true;
for (Node n : E.getNeighbourhood())
if (n != s_1 && !s_2.getNeighbourhood().contains(n))
subset = false;
for (Node n : F.getNeighbourhood())
if (n != s_1 && !s_2.getNeighbourhood().contains(n))
subset = false;
if (subset) {
removeNodes(s_1.getInclusiveNeighbourhood());
removeNodes(s_2.getInclusiveNeighbourhood());
miss = 3 + maximumIndependentSetSize();
} else {
int try_1, try_2;
removeNodes(s_1.getInclusiveNeighbourhood());
try_1 = 1 + maximumIndependentSetSize();
removeNodes(E.getInclusiveNeighbourhood());
removeNodes(F.getInclusiveNeighbourhood());
removeNodes(s_2.getInclusiveNeighbourhood());
try_2 = 3 + maximumIndependentSetSize();
miss = Math.max(try_1, try_2);
}
}
} else {
int try_1, try_2;
restorePoint();
removeNodes(s_2.getInclusiveNeighbourhood());
try_1 = maximumIndependentSetSize();
restore();
removeNodes(s_1.getInclusiveNeighbourhood());
removeNode(s_2);
try_2 = maximumIndependentSetSizeWith2From(s_2.getNeighbourhood());
miss = Math.max(try_1, try_2);
}
restore();
return miss;
}
/**
* Compute the maximum independent set size if at least two Nodes of some set should be in it.
*
* This is a helper function for {@link #maximumIndependentSetSize()}, which uses Robson's algorithm.
*
* @see #maximumIndependentSetSizeWith1From(Node, Node)
*
* @param Scol the set of which two Nodes should be in the maximum independent set
* @return the size of the maximum independent set
*/
private int maximumIndependentSetSizeWith2From(Collection<Node<T>> Scol) {
List<Node<T>> S = new ArrayList<>(Scol);
int miss;
restorePoint();
if (S.size() < 2) {
// Less than two Nodes to pick from; this is impossible
miss = 0;
} else if (S.size() == 2) {
// Two Nodes to pick from; try to pick both and fail otherwise.
if (S.get(0).getNeighbourhood().contains(S.get(1)))
miss = 0;
else {
removeNodes(S.get(0).getInclusiveNeighbourhood());
removeNodes(S.get(1).getInclusiveNeighbourhood());
miss = 2 + maximumIndependentSetSize();
}
} else if (S.size() == 3) {
// Three Nodes to pick from. An ugly case distinction.
Node<T> s_1 = S.get(0), s_2 = S.get(1), s_3 = S.get(2);
// If there is a Node with degree 0, we add it to the independent set and choose 1 Node from the other two
if (s_1.getDegree() == 0) {
removeNode(s_1);
miss = 1 + maximumIndependentSetSizeWith1From(s_2, s_3);
} else if (s_2.getDegree() == 0) {
removeNode(s_2);
miss = 1 + maximumIndependentSetSizeWith1From(s_1, s_3);
} else if (s_3.getDegree() == 0) {
removeNode(s_3);
miss = 1 + maximumIndependentSetSizeWith1From(s_1, s_2);
}
// If the Nodes are connected we can't choose two in an independent set.
else if (s_1.getNeighbourhood().contains(s_2) &&
s_2.getNeighbourhood().contains(s_3) &&
s_3.getNeighbourhood().contains(s_1)) {
miss = 0;
}
// If there is at least one edge from s_1
else if (s_1.getNeighbourhood().contains(s_2) || s_1.getNeighbourhood().contains(s_3)){
Node<T> s_i, s_j, s_k;
s_i = s_j = s_k = null;
// If there are two edges among S, say si-sj-sk, we pick si and sk.
if (s_1.getNeighbourhood().contains(s_2) && s_1.getNeighbourhood().contains(s_3)) {
s_i = s_1; s_j = s_2; s_k = s_3;
} else if (s_2.getNeighbourhood().contains(s_1) && s_2.getNeighbourhood().contains(s_3)) {
s_i = s_2; s_j = s_1; s_k = s_3;
} else if (s_3.getNeighbourhood().contains(s_1) && s_3.getNeighbourhood().contains(s_2)) {
s_i = s_3; s_j = s_1; s_k = s_2;
}
// Check if we managed to find two edges. If not, there must be at least one edge, say si-sj. We then
// choose sk and either si or sj. These are the cases that s_1 != sk.
if (s_i != null) { // Case two edges
removeNodes(s_j.getInclusiveNeighbourhood());
removeNodes(s_k.getInclusiveNeighbourhood());
miss = 2 + maximumIndependentSetSize();
} else if (s_1.getNeighbourhood().contains(s_2)) { // Case one edge; s_1 - s_2
removeNodes(s_3.getInclusiveNeighbourhood());
miss = 1 + maximumIndependentSetSizeWith1From(s_1, s_2);
} else { // Case one edge; s_1 - s_3 (implicit)
removeNodes(s_2.getInclusiveNeighbourhood());
miss = 1 + maximumIndependentSetSizeWith1From(s_1, s_3);
}
}
// Final case with one edge; between s_2 and s_3: pick s_1 and either s_2 or s_3.
else if (s_2.getNeighbourhood().contains(s_3)) {
removeNodes(s_1.getInclusiveNeighbourhood());
miss = 1 + maximumIndependentSetSizeWith1From(s_2, s_3);
}
// When two neighbourhoods of two nodes si, sj aren't disjoint, we can remove the intersection, because
// either si or sj is in the independent set and so the intersection is not. The intersection has at most
// one member.
else if (!s_1.neighbourhoodsDisjoint(s_2)) {
removeNode(s_1.neighbourhoodIntersection(s_2).get(0));
miss = maximumIndependentSetSizeWith2From(Scol);
} else if (!s_1.neighbourhoodsDisjoint(s_3)) {
removeNode(s_1.neighbourhoodIntersection(s_3).get(0));
miss = maximumIndependentSetSizeWith2From(Scol);
} else if (!s_2.neighbourhoodsDisjoint(s_3)) {
removeNode(s_2.neighbourhoodIntersection(s_3).get(0));
miss = maximumIndependentSetSizeWith2From(Scol);
}
// If s_1 has degree 1, we pick it and either s_2 or s_3
else if (s_1.getDegree() == 1) {
removeNodes(s_1.getInclusiveNeighbourhood());
miss = 1 + maximumIndependentSetSizeWith1From(s_2, s_3);
}
// If all else fails, we try both picking s_1 with either s_2 or s_3, and picking s_1 and s_2 and at least
// one neighbour of s_1 (Note: Robson has forgotten to add 2 for s_2 and s_3 in this step).
else {
int try_1, try_2;
restorePoint();
removeNodes(s_1.getInclusiveNeighbourhood());
try_1 = 1 + maximumIndependentSetSizeWith1From(s_2, s_3);
restore();
removeNodes(s_2.getInclusiveNeighbourhood());
removeNodes(s_3.getInclusiveNeighbourhood());
removeNode(s_1);
try_2 = 2 + maximumIndependentSetSizeWith2From(s_1.getNeighbourhood());
miss = Math.max(try_1, try_2);
}
} else if (S.size() == 4) {
// Four Nodes to pick from. If there's a node with degree <= 3 in the Graph, it's more efficient to just
// compute normally. If not, we try recursively both with and without the first Node in S.
Collections.sort(nodes);
if (nodes.get(0).getDegree() <= 3) {
miss = maximumIndependentSetSize();
} else {
Node<T> s_1 = S.get(0);
int try_1, try_2;
removeNode(s_1);
S.remove(s_1);
try_1 = maximumIndependentSetSizeWith2From(S);
removeNodes(s_1.getNeighbourhood());
try_2 = 1 + maximumIndependentSetSize();
miss = Math.max(try_1, try_2);
}
} else {
miss = maximumIndependentSetSize();
}
restore();
return miss;
}
/**
* Find a strongly connected component of the graph. This may return itself, if the graph is empty or connected.
* It is not guaranteed that the smallest / largest strongly connected component is returned. The method simply
* returns the one that contains the first node (if it exists).
*
* @return some strongly connected component
*/
private Graph<T> findStronglyConnectedComponent() {
if (nodes.isEmpty())
return this;
List<Node<T>> seen = new ArrayList<>();
Queue<Node<T>> queue = new LinkedList<>();
queue.add(nodes.get(0));
while (!queue.isEmpty()) {
Node<T> n = queue.remove();
queue.addAll(n.getNeighbourhood().stream().filter(x -> !seen.contains(x)).collect(Collectors.toList()));
seen.add(n);
}
return new Graph<>(seen);
}
/**
* Add a Node, and update its neighbours to include it as a neighbour.
* @param node the Node to add
*/
public void addNode(Node<T> node) {
this.nodes.add(node);
node.getNeighbourhood().stream()
.filter(nodes::contains)
.filter(n -> !n.getNeighbourhood().contains(node))
.forEach(n -> n.getNeighbourhood().add(node));
restorePoints.peek().remove(node);
}
/**
* Get a Node by its index. Note: some internal methods may reorder the list; only use this directly after adding
* nodes.
* @param i the index in the list
* @return the Node
*/
public Node<T> getNode(int i) {
return this.nodes.get(i);
}
/**
* Remove a node, and remove its reference from its neighbours that are still in the graph
*
* @see #removeNodes(Collection)
*
* @param node the Node
*/
private void removeNode(Node<T> node) {
nodes.remove(node);
node.getNeighbourhood().stream()
.filter(nodes::contains)
.forEach(n -> n.getNeighbourhood().remove(node));
restorePoints.peek().add(node);
}
/**
* Remove a list of nodes, and update all references
*
* @see #removeNode(Node)
*
* @param ns the Nodes to remove
*/
private void removeNodes(Collection<Node<T>> ns) {
nodes.removeAll(ns);
for (Node<T> n : nodes)
n.getNeighbourhood().removeAll(ns);
restorePoints.peek().addAll(ns);
}
/**
* Create a restore point to return back to after removing some Nodes.
*
* @see #restore()
* @see #restorePoints
*/
private void restorePoint() {
restorePoints.push(new ArrayList<>());
}
/**
* Go back to the last restore point, re-adding all Nodes that were removed since then.
*
* @see #restorePoint()
* @see #restorePoints
*/
private void restore() {
List<Node<T>> removed = restorePoints.pop();
Collections.reverse(removed);
for (Node<T> node : removed) {
this.nodes.add(node);
node.getNeighbourhood().stream()
.filter(nodes::contains)
.filter(n -> !n.getNeighbourhood().contains(node))
.forEach(n -> n.getNeighbourhood().add(node));
}
removed.clear();
}
/**
* The size of the graph is the number of Nodes.
* @return the number of Nodes.
*/
public int size() {
return nodes.size();
}
/**
* Represent the Graph as a String
* @return a String representation of the Graph
*/
public String toString() {
StringBuilder sb = new StringBuilder("Graph:");
for (Node n : nodes) {
sb.append("\n ").append(n).append(": ");
for (Object n2 : n.getNeighbourhood()) sb.append(" - ").append(n2);
}
return sb.toString();
}
}
|