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