单源最短双子路径

Single-source shortest bitonic path

我正在尝试解决 Sedgewick & Wayne 的算法书中的一个问题:单源最短双调路径。

一些不熟悉问题的定义:

Monotonic shortest-path: a monotonic shortest-path is a shortest-path in which the edges are either strictly increasing or strictly decreasing.

Bitonic shortest-path: a shortest-path from s to t in which there is an intermediate vertex v such that the weights of the edges on the path s to v are strictly increasing and the weights of the edges on the path from v to t are strictly decreasing.

题目思路是:

Given an edge-weighted digraph, find a bitonic shortest path from a given source vertex to every other vertex (if one exists). The path should be simple (no repeated vertices).

到目前为止我有以下内容:

单调最短路径可以通过按升序放宽图中的所有边(找到升序单调最短路径)或按降序放宽图中的所有边(找到降序单调最短路径)。

我预先计​​算了从源顶点到所有其他顶点的升序单调最短路径(这可以在 O(E) 中完成,因为它只是一棵最短路径树)。

然后我预先计算了所有顶点对的降序单调最短路径,因为任何顶点都可以是中间顶点,任何顶点都可以是目标顶点。这需要 O(E * V) 时间。

现在,对于从 s 开始到 t 结束的每条路径,我可以检查 (1) 从 s 到中间顶点 v 的上升单调最短路径和 (2) 从 s 开始的下降单调最短路径的所有组合v 到 t 和 select 权重最低的路径。 这会给我一条从 s 到 t 的双调最短路径。

但是,有一个问题:我们不能在路径中有重复的顶点,并且上述想法没有解决这个问题。

对于解决问题的最后一部分有什么想法吗? 也欢迎解决问题的任何其他ideas/approaches。

(注意:我没有检查你的想法的宏伟计划是否真的成立,或者是否有更快的算法。这仅解决重复顶点的问题。)

假设递减路径和递增路径都不包含重复顶点,重复顶点的唯一机会是如果一个顶点同时存在于"bitonic"路径的递减和递增部分。

A --1-- B --3-- C --5-- D --7-- E --7-- D --5-- C --2-- F --1-- Z

在此示例中,C 在路径的两个部分中,E 是中间顶点。可以看出,这个意味着从CE和从EC的段必须是上升路径和下降路径相同。如果递减路径中有不同的(更短的)路径,则递增路径在通过该节点路由时也会更短。

这意味着,我们可以简单地切断 C 的两个实例之间的段,并留下更短的 "bitonic" 路径。 (或者,我们可以忽略较长的双音路径,因为我们稍后会找到较短的路径,无论如何,当考虑 C 而不是 E 作为中间节点时。)

A --1-- B --3-- C --2-- F --1-- Z

这会导致看似奇怪的结果,例如 A --1-- B --1-- Z,其中两条连续的边具有相同的权重。但是根据你的定义 "a shortest-path from s to t in which there is an intermediate vertex v such that the weights of the edges on the path s to v are strictly increasing and the weights of the edges on the path from v to t are strictly decreasing",这应该仍然是双调路径,因为 A --1-- CC --1-- Z 分别是严格单调递增和单调递减。

原来答案看起来比我想象的要简单

我评论的策略是 (1) 预计算从源顶点到所有其他顶点的单调升序最短路径,(2) 预计算所有顶点对的降序单调最短路径,以及, (3) 对于从 s 开始到 t 结束的每条路径,检查从 s 到中间顶点 v 的升序单调最短路径和从 v 到 t 的降序单调最短路径的所有组合仅在路径可以具有重复顶点时有效.

当我们寻找简单的路径(没有重复的顶点)时,我们可以 (1) 按升序松弛图中的所有边,然后,在这些相同的路径上,(2) 松弛图中的所有边图降序排列。当我们按升序松弛边时,我们确保丢弃仅下降的路径,或者所有边具有相同权重的路径(除非恰好有 2 个边具有相同的权重,请参阅下面我对这个边缘情况的评论)。当按降序松弛边时,我们丢弃只有上升边权重的路径。

这是我想出的算法的大概思路。还涉及一些实现细节:优先级队列用于两个放宽,路径对象按最小权重排序。重要的是要考虑具有恰好 2 个权重相等的边的路径的边缘情况(根据问题定义,这是一条双调路径)。

运行时复杂度似乎是 O(P lg P),其中 P 是图中路径的数量。

代码及其依赖和测试可以在我的GitHub中找到,在这个class中:BitonicShortestPaths.java

我也把主要代码贴在这里供参考:

public class BitonicSP {

    private Path[] bitonicPathTo;  // bitonic path to vertex

    public BitonicSP(EdgeWeightedDigraph edgeWeightedDigraph, int source) {

        bitonicPathTo = new Path[edgeWeightedDigraph.vertices()];

        // 1- Relax edges in ascending order to get a monotonic increasing shortest path
        Comparator<DirectedEdge> edgesComparator = new Comparator<DirectedEdge>() {
            @Override
            public int compare(DirectedEdge edge1, DirectedEdge edge2) {
                if(edge1.weight() > edge2.weight()) {
                    return -1;
                } else if(edge1.weight() < edge2.weight()) {
                    return 1;
                } else {
                    return 0;
                }
            }
        };

        List<Path> allCurrentPaths = new ArrayList<>();

        relaxAllEdgesInSpecificOrder(edgeWeightedDigraph, source, edgesComparator, allCurrentPaths,true);

        // 2- Relax edges in descending order to get a monotonic decreasing shortest path
        edgesComparator = new Comparator<DirectedEdge>() {
            @Override
            public int compare(DirectedEdge edge1, DirectedEdge edge2) {
                if(edge1.weight() < edge2.weight()) {
                    return -1;
                } else if(edge1.weight() > edge2.weight()) {
                    return 1;
                } else {
                    return 0;
                }
            }
        };

        relaxAllEdgesInSpecificOrder(edgeWeightedDigraph, source, edgesComparator, allCurrentPaths, false);
    }

    private void relaxAllEdgesInSpecificOrder(EdgeWeightedDigraph edgeWeightedDigraph, int source,
                                              Comparator<DirectedEdge> edgesComparator, List<Path> allCurrentPaths,
                                              boolean isAscendingOrder) {

        // Create a map with vertices as keys and sorted outgoing edges as values
        Map<Integer, VertexInformation> verticesInformation = new HashMap<>();
        for(int vertex = 0; vertex < edgeWeightedDigraph.vertices(); vertex++) {
            DirectedEdge[] edges = new DirectedEdge[edgeWeightedDigraph.outdegree(vertex)];

            int edgeIndex = 0;
            for(DirectedEdge edge : edgeWeightedDigraph.adjacent(vertex)) {
                edges[edgeIndex++] = edge;
            }

            Arrays.sort(edges, edgesComparator);

            verticesInformation.put(vertex, new VertexInformation(edges));
        }

        PriorityQueue<Path> priorityQueue = new PriorityQueue<>();

        // If we are relaxing edges for the first time, add the initial paths to the priority queue
        if(isAscendingOrder) {
            VertexInformation sourceVertexInformation = verticesInformation.get(source);
            while (sourceVertexInformation.getEdgeIteratorPosition() < sourceVertexInformation.getEdges().length) {
                DirectedEdge edge = sourceVertexInformation.getEdges()[sourceVertexInformation.getEdgeIteratorPosition()];
                sourceVertexInformation.incrementEdgeIteratorPosition();

                Path path = new Path(edge);
                priorityQueue.offer(path);

                allCurrentPaths.add(path);
            }
        }

        // If we are relaxing edges for the second time, add all existing ascending paths to the priority queue
        if(!allCurrentPaths.isEmpty()) {
            for(Path currentPath : allCurrentPaths) {
                priorityQueue.offer(currentPath);
            }
        }

        while (!priorityQueue.isEmpty()) {
            Path currentShortestPath = priorityQueue.poll();

            DirectedEdge currentEdge = currentShortestPath.directedEdge;

            int nextVertexInPath = currentEdge.to();
            VertexInformation nextVertexInformation = verticesInformation.get(nextVertexInPath);

            // Edge case: a bitonic path consisting of 2 edges of the same weight.
            // s to v with only one edge is strictly increasing, v to t with only one edge is strictly decreasing
            boolean isEdgeCase = false;

            if(currentShortestPath.numberOfEdges() == 2
                    && currentEdge.weight() == currentShortestPath.previousPath.directedEdge.weight()) {
                isEdgeCase = true;
            }

            if((currentShortestPath.isDescending() || isEdgeCase)
                    && (currentShortestPath.weight() < bitonicPathDistTo(nextVertexInPath)
                    || bitonicPathTo[nextVertexInPath] == null)) {
                bitonicPathTo[nextVertexInPath] = currentShortestPath;
            }

            double weightInPreviousEdge = currentEdge.weight();

            while (nextVertexInformation.getEdgeIteratorPosition() < nextVertexInformation.getEdges().length) {
                DirectedEdge edge =
                        verticesInformation.get(nextVertexInPath).getEdges()[nextVertexInformation.getEdgeIteratorPosition()];

                boolean isEdgeInEdgeCase = currentShortestPath.numberOfEdges() == 1
                        && edge.weight() == weightInPreviousEdge;

                if(!isEdgeInEdgeCase && ((isAscendingOrder && edge.weight() <= weightInPreviousEdge)
                        || (!isAscendingOrder && edge.weight() >= weightInPreviousEdge))) {
                    break;
                }

                nextVertexInformation.incrementEdgeIteratorPosition();

                Path path = new Path(currentShortestPath, edge);
                priorityQueue.offer(path);

                // If we are relaxing edges for the first time, store the ascending paths so they can be further
                // relaxed when computing the descending paths on the second relaxation
                if(isAscendingOrder) {
                    allCurrentPaths.add(path);
                }
            }
        }
    }

    public double bitonicPathDistTo(int vertex) {
        if(hasBitonicPathTo(vertex)) {
            return bitonicPathTo[vertex].weight();
        } else {
            return Double.POSITIVE_INFINITY;
        }
    }

    public boolean hasBitonicPathTo(int vertex) {
        return bitonicPathTo[vertex] != null;
    }

    public Iterable<DirectedEdge> bitonicPathTo(int vertex) {

        if(!hasBitonicPathTo(vertex)) {
            return null;
        }

        return bitonicPathTo[vertex].getPath();
    }
}

public class Path implements Comparable<Path> {
    private Path previousPath;
    private DirectedEdge directedEdge;
    private double weight;
    private boolean isDescending;
    private int numberOfEdges;

    Path(DirectedEdge directedEdge) {
        this.directedEdge = directedEdge;
        weight = directedEdge.weight();

        numberOfEdges = 1;
    }

    Path(Path previousPath, DirectedEdge directedEdge) {
        this(directedEdge);
        this.previousPath = previousPath;

        weight += previousPath.weight();
        numberOfEdges += previousPath.numberOfEdges;

        if(previousPath != null && previousPath.directedEdge.weight() > directedEdge.weight()) {
            isDescending = true;
        }
    }

    public double weight() {
        return weight;
    }

    public boolean isDescending() {
        return isDescending;
    }

    public int numberOfEdges() {
        return numberOfEdges;
    }

    public Iterable<DirectedEdge> getPath() {
        LinkedList<DirectedEdge> path = new LinkedList<>();

        Path iterator = previousPath;

        while (iterator != null && iterator.directedEdge != null) {
            path.addFirst(iterator.directedEdge);

            iterator = iterator.previousPath;
        }
        path.add(directedEdge);

        return path;
    }

    @Override
    public int compareTo(Path other) {
        if(this.weight < other.weight) {
            return -1;
        } else if(this.weight > other.weight) {
            return 1;
        } else {
            return 0;
        }
    }
}

public class VertexInformation {

    private DirectedEdge[] edges;
    private int edgeIteratorPosition;

    VertexInformation(DirectedEdge[] edges) {
        this.edges = edges;
        edgeIteratorPosition = 0;
    }

    public void incrementEdgeIteratorPosition() {
        edgeIteratorPosition++;
    }

    public DirectedEdge[] getEdges() {
        return edges;
    }

    public int getEdgeIteratorPosition() {
        return edgeIteratorPosition;
    }
}