146 | | protected void rememberSuccessor(NodePair pair) { |
147 | | List<NodePair> l = successors.computeIfAbsent(pair.getA(), k -> new ArrayList<>()); |
148 | | if (!l.contains(pair)) { |
149 | | l.add(pair); |
150 | | } |
151 | | } |
152 | | |
153 | | protected void rememberPredecessors(NodePair pair) { |
154 | | List<NodePair> l = predecessors.computeIfAbsent(pair.getB(), k -> new ArrayList<>()); |
155 | | if (!l.contains(pair)) { |
156 | | l.add(pair); |
157 | | } |
158 | | } |
159 | | |
160 | | protected boolean isTerminalNode(Node n) { |
161 | | if (successors.get(n) == null) return false; |
162 | | if (successors.get(n).size() != 1) return false; |
163 | | if (predecessors.get(n) == null) return true; |
164 | | if (predecessors.get(n).size() == 1) { |
165 | | NodePair p1 = successors.get(n).get(0); |
166 | | NodePair p2 = predecessors.get(n).get(0); |
167 | | return p1.equals(p2.swap()); |
168 | | } |
169 | | return false; |
170 | | } |
171 | | |
| 140 | @SuppressWarnings("unchecked") |
| 150 | |
| 151 | // calculate an index for each node contained in this graph |
| 152 | allNodes = Collections.unmodifiableList(new ArrayList<>(getNodes())); |
| 153 | |
| 154 | // calculate the adjacency list representation. each node is represented by an integer 0...n |
| 155 | // which refers to the position in the list allNodes |
| 156 | adj = new LinkedList[allNodes.size()]; |
| 157 | for (int i = 0; i < allNodes.size(); i++) { |
| 158 | adj[i] = new LinkedList<>(); |
| 159 | } |
| 160 | // map the nodes in this graph to an Integer |
| 161 | HashMap<Node, Integer> nodeIdMap = new HashMap<>(); |
| 162 | for (int i = 0; i < allNodes.size(); i++) { |
| 163 | nodeIdMap.put(allNodes.get(i), i); |
| 164 | } |
| 165 | |
| 166 | for (NodePair edge : undirectedEdges) { |
| 167 | int idxA = nodeIdMap.get(edge.getA()); |
| 168 | int idxB = nodeIdMap.get(edge.getB()); |
| 169 | addEdge(idxA, idxB); |
| 170 | } |
213 | | protected Set<Node> getTerminalNodes() { |
214 | | return getNodes().stream().filter(this::isTerminalNode).collect(Collectors.toCollection(LinkedHashSet::new)); |
215 | | } |
216 | | |
217 | | private List<NodePair> getConnectedPairs(Node node) { |
218 | | List<NodePair> connected = new ArrayList<>(); |
219 | | connected.addAll(Optional.ofNullable(successors.get(node)).orElseGet(Collections::emptyList)); |
220 | | connected.addAll(Optional.ofNullable(predecessors.get(node)).orElseGet(Collections::emptyList)); |
221 | | return connected; |
222 | | } |
223 | | |
224 | | protected List<NodePair> getOutboundPairs(NodePair pair) { |
225 | | return getOutboundPairs(pair.getB()); |
226 | | } |
227 | | |
228 | | protected List<NodePair> getOutboundPairs(Node node) { |
229 | | return Optional.ofNullable(successors.get(node)).orElseGet(Collections::emptyList); |
230 | | } |
231 | | |
251 | | * Tries to find a spanning path starting from node <code>startNode</code>. |
252 | | * |
253 | | * Traverses the path in depth-first order. |
254 | | * |
255 | | * @param startNode the start node |
256 | | * @return the spanning path; empty list if no path is found |
257 | | */ |
258 | | protected List<Node> buildSpanningPath(Node startNode) { |
259 | | if (startNode != null) { |
260 | | Deque<NodePair> path = new ArrayDeque<>(); |
261 | | Set<NodePair> dupCheck = new HashSet<>(); |
262 | | Deque<NodePair> nextPairs = new ArrayDeque<>(); |
263 | | nextPairs.addAll(getOutboundPairs(startNode)); |
264 | | while (!nextPairs.isEmpty()) { |
265 | | NodePair cur = nextPairs.removeLast(); |
266 | | if (!dupCheck.contains(cur) && !dupCheck.contains(cur.swap())) { |
267 | | while (!path.isEmpty() && !path.peekLast().isPredecessorOf(cur)) { |
268 | | dupCheck.remove(path.removeLast()); |
269 | | } |
270 | | path.addLast(cur); |
271 | | dupCheck.add(cur); |
272 | | if (isSpanningWay(path)) |
273 | | return buildPathFromNodePairs(path); |
274 | | nextPairs.addAll(getOutboundPairs(path.peekLast())); |
275 | | } |
276 | | } |
277 | | } |
278 | | return Collections.emptyList(); |
279 | | } |
280 | | |
281 | | /** |
290 | | if (numUndirectedEges > 0 && isConnected()) { |
291 | | // try to find a path from each "terminal node", i.e. from a |
292 | | // node which is connected by exactly one undirected edges (or |
293 | | // two directed edges in opposite direction) to the graph. A |
294 | | // graph built up from way segments is likely to include such |
295 | | // nodes, unless the edges build one or more closed rings. |
296 | | // We order the nodes to start with the best candidates, but |
297 | | // it might take very long if there is no valid path as we iterate over all nodes |
298 | | // to find out. |
299 | | Set<Node> nodes = getTerminalNodes(); |
300 | | nodes = nodes.isEmpty() ? getMostFrequentVisitedNodesFirst() : nodes; |
301 | | return nodes.stream() |
302 | | .map(this::buildSpanningPath) |
303 | | .filter(path -> !path.isEmpty()) |
304 | | .findFirst().orElse(null); |
| 217 | if (numUndirectedEges > 0) { |
| 218 | int indicator = isEulerian(); |
| 219 | if (indicator > 0) { |
| 220 | List<Integer> indexes = findpath(adj); |
| 221 | if (indexes.size() == numUndirectedEges + 1) { |
| 222 | List<Node> path = new ArrayList<>(indexes.size()); |
| 223 | for (Integer idx : indexes) { |
| 224 | path.add(allNodes.get(idx)); |
| 225 | } |
| 226 | Collections.reverse(path); |
| 227 | return path; |
| 228 | } |
| 229 | } |
355 | | private Set<Node> getMostFrequentVisitedNodesFirst() { |
356 | | if (edges.isEmpty()) |
357 | | return Collections.emptySet(); |
358 | | // count appearance of nodes in edges |
359 | | Map<Node, Integer> counters = new HashMap<>(); |
360 | | for (NodePair pair : edges) { |
361 | | Integer c = counters.get(pair.getA()); |
362 | | counters.put(pair.getA(), c == null ? 1 : c + 1); |
363 | | c = counters.get(pair.getB()); |
364 | | counters.put(pair.getB(), c == null ? 1 : c + 1); |
| 282 | private int isEulerian() { |
| 283 | // Check if all vertices are connected |
| 284 | if (!isConnected()) |
| 285 | return 0; |
| 286 | |
| 287 | // Count vertices with odd degree |
| 288 | int odd = 0; |
| 289 | for (int i = 0; i < allNodes.size(); i++) { |
| 290 | if (adj[i].size() % 2 != 0) |
| 291 | odd++; |
366 | | // group by counters |
367 | | TreeMap<Integer, Set<Node>> sortedMap = new TreeMap<>(Comparator.reverseOrder()); |
368 | | for (Entry<Node, Integer> e : counters.entrySet()) { |
369 | | sortedMap.computeIfAbsent(e.getValue(), x -> new LinkedHashSet<>()).add(e.getKey()); |
| 293 | |
| 294 | // If count is more than 2, then graph is not Eulerian |
| 295 | if (odd > 2) |
| 296 | return 0; |
| 297 | |
| 298 | // If odd count is 2, then semi-eulerian. |
| 299 | // If odd count is 0, then eulerian |
| 300 | // Note that odd count can never be 1 for undirected graph |
| 301 | return (odd == 2) ? 1 : 2; |
| 302 | } |
| 303 | |
| 304 | /** |
| 305 | * Find the Eulerian path. Code taken from https://www.geeksforgeeks.org/eulerian-path-undirected-graph/ |
| 306 | * Original code is contributed by sanjeev2552 |
| 307 | * @param adj adjacency list representation of the graph |
| 308 | * @return List of integers if a path exists, else an empty list |
| 309 | */ |
| 310 | static List<Integer> findpath(LinkedList<Integer>[] adj) { |
| 311 | final int n = adj.length; |
| 312 | |
| 313 | // create deep copy of adjacency matrix since the data is modified |
| 314 | @SuppressWarnings("unchecked") |
| 315 | LinkedList<Integer>[] adjWork = new LinkedList[n]; |
| 316 | for (int i = 0; i < n; i++) { |
| 317 | adjWork[i] = new LinkedList<>(adj[i]); |
377 | | return Collections.unmodifiableSet(result); |
| 329 | |
| 330 | // If number of vertex with odd number of edges |
| 331 | // is greater than two return "No Solution". |
| 332 | if (numOdd > 2) { |
| 333 | return Collections.emptyList(); |
| 334 | } |
| 335 | |
| 336 | // conditions are met, find the path |
| 337 | Deque<Integer> stack = new ArrayDeque<>(); |
| 338 | List<Integer> path = new ArrayList<>(n + 1); |
| 339 | int cur = startPoint; |
| 340 | |
| 341 | // Loop will run while there is element in the stack |
| 342 | // or current edge has some neighbour. |
| 343 | while (!stack.isEmpty() || !adjWork[cur].isEmpty()) { |
| 344 | // If current node has no neighbour |
| 345 | // add it to path and pop stack |
| 346 | // set new current to the popped element |
| 347 | if (adjWork[cur].isEmpty()) { |
| 348 | path.add(cur); |
| 349 | cur = stack.removeLast(); |
| 350 | } else { |
| 351 | // If the current vertex has at least one |
| 352 | // neighbour add the current vertex to stack, |
| 353 | // remove the edge between them and set the |
| 354 | // current to its neighbour. |
| 355 | stack.addLast(cur); |
| 356 | int next = adjWork[cur].removeFirst(); |
| 357 | adjWork[next].removeFirstOccurrence(cur); |
| 358 | cur = next; |
| 359 | } |
| 360 | } |
| 361 | path.add(cur); |
| 362 | return path; |