/* * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.util.graph.impl; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.util.containers.FList; import com.intellij.util.containers.MultiMap; import com.intellij.util.graph.Graph; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; import java.util.*; /** * Algorithm to search k shortest paths between two vertices in unweighted directed graph. * Based on article "Finding the k shortest paths" by D. Eppstein, 1997. * * @author nik */ public class KShortestPathsFinder<Node> { private static final Logger LOG = Logger.getInstance("#com.intellij.util.graph.impl.KShortestPathsFinder"); private final Graph<Node> myGraph; private final Node myStart; private final Node myFinish; private final ProgressIndicator myProgressIndicator; private MultiMap<Node, GraphEdge<Node>> myNonTreeEdges; private List<Node> mySortedNodes; private Map<Node, Node> myNextNodes; private Map<Node, HeapNode<Node>> myOutRoots; private Map<Node,Heap<Node>> myHeaps; public KShortestPathsFinder(@NotNull Graph<Node> graph, @NotNull Node start, @NotNull Node finish, @NotNull ProgressIndicator progressIndicator) { myGraph = graph; myStart = start; myFinish = finish; myProgressIndicator = progressIndicator; } private void computeDistancesToTarget() { myNonTreeEdges = new MultiMap<>(); mySortedNodes = new ArrayList<>(); myNextNodes = new HashMap<>(); TObjectIntHashMap<Node> distances = new TObjectIntHashMap<>(); Deque<Node> nodes = new ArrayDeque<>(); nodes.addLast(myFinish); distances.put(myFinish, 0); while (!nodes.isEmpty()) { myProgressIndicator.checkCanceled(); Node node = nodes.removeFirst(); mySortedNodes.add(node); int d = distances.get(node) + 1; Iterator<Node> iterator = myGraph.getIn(node); while (iterator.hasNext()) { Node prev = iterator.next(); if (distances.containsKey(prev)) { int dPrev = distances.get(prev); myNonTreeEdges.putValue(prev, new GraphEdge<>(prev, node, d - dPrev)); continue; } distances.put(prev, d); myNextNodes.put(prev, node); nodes.addLast(prev); } } } private void buildOutHeaps() { myOutRoots = new HashMap<>(); for (Node node : mySortedNodes) { myProgressIndicator.checkCanceled(); List<HeapNode<Node>> heapNodes = new ArrayList<>(); Collection<GraphEdge<Node>> edges = myNonTreeEdges.get(node); if (edges.isEmpty()) continue; HeapNode<Node> root = null; for (GraphEdge<Node> edge : edges) { HeapNode<Node> heapNode = new HeapNode<>(edge); heapNodes.add(heapNode); if (root == null || root.myEdge.getDelta() > heapNode.myEdge.getDelta()) { root = heapNode; } } LOG.assertTrue(root != null); heapNodes.remove(root); myOutRoots.put(node, root); if (!heapNodes.isEmpty()) { for (int j = 1; j < heapNodes.size(); j++) { HeapNode<Node> heapNode = heapNodes.get(j); HeapNode<Node> parent = heapNodes.get((j+1)/2 - 1); parent.myChildren[(j+1) % 2] = heapNode; } for (int j = heapNodes.size() / 2 - 1; j >= 0; j--) { heapify(heapNodes.get(j)); } root.myChildren[2] = heapNodes.get(0); } } } private void buildMainHeaps() { myHeaps = new HashMap<>(); for (Node node : mySortedNodes) { myProgressIndicator.checkCanceled(); HeapNode<Node> outRoot = myOutRoots.get(node); Node next = myNextNodes.get(node); if (outRoot == null) { if (next != null) { myHeaps.put(node, myHeaps.get(next)); } continue; } final Heap<Node> nextHeap = myHeaps.get(next); if (nextHeap == null) { myHeaps.put(node, new Heap<>(outRoot)); continue; } final Heap<Node> tHeap = nextHeap.insert(outRoot); myHeaps.put(node, tHeap); } } private void heapify(HeapNode<Node> node) { while (true) { HeapNode<Node> min = node; for (int i = 0; i < 2; i++) { HeapNode<Node> child = node.myChildren[i]; if (child != null && child.myEdge.getDelta() < min.myEdge.getDelta()) { min = child; } } if (min != node) { GraphEdge<Node> t = min.myEdge; min.myEdge = node.myEdge; node.myEdge = t; node = min; } else { break; } } } public List<List<Node>> findShortestPaths(int k) { try { if (myStart.equals(myFinish)) { return Collections.singletonList(Collections.singletonList(myStart)); } computeDistancesToTarget(); if (!myNextNodes.containsKey(myStart)) { return Collections.emptyList(); } buildOutHeaps(); buildMainHeaps(); PriorityQueue<Sidetracks<Node>> queue = new PriorityQueue<>(); List<FList<HeapNode<Node>>> sidetracks = new ArrayList<>(); sidetracks.add(FList.emptyList()); final Heap<Node> heap = myHeaps.get(myStart); if (heap != null) { queue.add(new Sidetracks<>(0, FList.<HeapNode<Node>>emptyList().prepend(heap.getRoot()))); for (int i = 2; i <= k; i++) { if (queue.isEmpty()) break; myProgressIndicator.checkCanceled(); final Sidetracks<Node> current = queue.remove(); sidetracks.add(current.myEdges); final HeapNode<Node> e = current.myEdges.getHead(); final Heap<Node> next = myHeaps.get(e.myEdge.getFinish()); if (next != null) { final HeapNode<Node> f = next.getRoot(); queue.add(new Sidetracks<>(current.myLength + f.myEdge.getDelta(), current.myEdges.prepend(f))); } for (HeapNode<Node> child : e.myChildren) { if (child != null) { queue.add(new Sidetracks<>(current.myLength - e.myEdge.getDelta() + child.myEdge.getDelta(), current.myEdges.getTail().prepend(child))); } } } } return computePathsBySidetracks(sidetracks); } catch (ProcessCanceledException e) { return Collections.emptyList(); } } private List<List<Node>> computePathsBySidetracks(List<FList<HeapNode<Node>>> sidetracks) { final List<List<Node>> result = new ArrayList<>(); for (FList<HeapNode<Node>> sidetrack : sidetracks) { myProgressIndicator.checkCanceled(); List<GraphEdge<Node>> edges = new ArrayList<>(); while (!sidetrack.isEmpty()) { edges.add(sidetrack.getHead().myEdge); sidetrack = sidetrack.getTail(); } Node current = myStart; final List<Node> path = new ArrayList<>(); path.add(current); int i = edges.size() - 1; while (!current.equals(myFinish) || i >= 0) { if (i >= 0 && edges.get(i).getStart().equals(current)) { current = edges.get(i).getFinish(); i--; } else { current = myNextNodes.get(current); LOG.assertTrue(current != null); } path.add(current); } result.add(path); } return result; } private static class Sidetracks<Node> implements Comparable<Sidetracks> { private int myLength; private final FList<HeapNode<Node>> myEdges; private Sidetracks(int length, FList<HeapNode<Node>> edges) { myLength = length; myEdges = edges; } @Override public int compareTo(Sidetracks o) { return myLength - o.myLength; } } private static class Heap<Node> { private final int mySize; private HeapNode<Node> myRoot; public Heap(HeapNode<Node> root) { myRoot = root; mySize = 1; } private Heap(int size, HeapNode<Node> root) { mySize = size; myRoot = root; } public HeapNode<Node> getRoot() { return myRoot; } public Heap<Node> insert(HeapNode<Node> node) { int pos = mySize + 1; int pow = 1; while (pos >= pow << 2) { pow <<= 1; } HeapNode<Node> newRoot = myRoot.copy(); HeapNode<Node> place = newRoot; List<HeapNode<Node>> parents = new ArrayList<>(); while (true) { parents.add(place); final int ind = (pos & pow) != 0 ? 1 : 0; if (pow == 1) { place.myChildren[ind] = node; break; } HeapNode<Node> copy = place.myChildren[ind].copy(); place.myChildren[ind] = copy; place = copy; pow >>= 1; } for (int i = parents.size() - 1; i >= 0; i--) { HeapNode<Node> parent = parents.get(i); if (parent.myEdge.getDelta() < node.myEdge.getDelta()) { break; } final GraphEdge<Node> t = parent.myEdge; parent.myEdge = node.myEdge; node.myEdge = t; final HeapNode<Node> t2 = parent.myChildren[2]; parent.myChildren[2] = node.myChildren[2]; node.myChildren[2] = t2; node = parent; } return new Heap<>(mySize + 1, newRoot); } } private static class HeapNode<Node> { public HeapNode<Node>[] myChildren; public GraphEdge<Node> myEdge; private HeapNode(GraphEdge<Node> edge) { myEdge = edge; myChildren = new HeapNode[3]; } public HeapNode(HeapNode<Node> node) { myEdge = node.myEdge; myChildren = node.myChildren.clone(); } public HeapNode<Node> copy() { return new HeapNode<>(this); } } }