我试图解决Sedgewick& Wayne的算法书:单源最短的比特路径。
对那些不熟悉问题的人的一些定义:
单调最短路径:单调最短路径是边缘严格增加或严格减小的最短路径。
Bitonic最短路径:从s到t的最短路径,其中存在中间顶点v,使得路径s到v上的边缘的权重严格增加,并且路径上的边缘的权重来自v到t严格减少。
问题的想法是:
给定边加权有向图,找到从给定源顶点到每个其他顶点(如果存在)的比特最短路径。路径应该很简单(没有重复的顶点)。
到目前为止我的内容如下:
单调最短路径可以通过以升序放松图中的所有边(以找到上升的单调最短路径)或按降序放松图中的所有边来计算(以找到降序单调)最短路径)。
我预先计算了从源顶点到所有其他顶点的上升单调最短路径(这可以在O(E)中完成,因为它只是一个最短路径树。)
然后我预先计算了所有顶点对的下降单调最短路径,因为任何顶点都可以是中间顶点,任何顶点都可以是目标顶点。这需要O(E * V)时间。现在,对于从s开始到t结束的每条路径,我可以检查(1)从s到中间顶点v的升序单调最短路径和(2)从v到降序单调最短路径的所有组合t并选择重量最轻的路径。 这将给我一个从s到t的比特最短路径。
然而,有一个问题:我们不能在路径中重复顶点,而上述想法并没有解决这个问题。
解决问题最后一部分的任何想法? 任何其他解决问题的想法/方法也是受欢迎的。
答案 0 :(得分:0)
(注意:我没有检查你的想法的宏观方案是否真的有用,或者是否有更快的算法。这只能解决重复顶点的问题。)
假设减少路径和增加路径都不包含重复的顶点,重复顶点的唯一机会是,如果顶点存在于两者中,则#34; bitonic"路径。
A --1-- B --3-- C --5-- D --7-- E --7-- D --5-- C --2-- F --1-- Z
在此示例中,C
位于路径的两个部分中,E
是中间顶点。可以看出,此也表示从C
到E
以及从E
到C
的段必须相同增加和减少的路径。如果减少路径中存在不同(较短)的路径,则通过该节点路由时,增加路径也会更短。
这意味着,我们可以简单地剪切C
的两个实例之间的片段,并留下更短的" bitonic"路径。 (或者,我们可以忽略较长的比特路径,因为我们会在以后将C
而不是E
视为中间节点时找到较短的路径。)
A --1-- B --3-- C --2-- F --1-- Z
这会导致像A --1-- B --1-- Z
这样看似奇怪的结果,其中两个连续的边具有相同的权重。但是根据你的定义"从s到t的最短路径,其中存在中间顶点v,使得路径s到v上的边缘的权重严格增加并且路径上的边缘的权重从v到t严格减少",这应该仍然是一个bitonic路径,因为A --1-- C
和C --1-- Z
分别是严格单调增加和减少。
答案 1 :(得分:0)
事实证明,答案看起来比我想象的要简单。
我评论的策略(1)预先计算从源顶点到所有其他顶点的上升单调最短路径,(2)预先计算来自所有顶点对的下降单调最短路径,( 3)对于从s开始到t结束的每个路径,检查从s到中间顶点v的上升单调最短路径的所有组合和从v到t的下降单调最短路径仅在路径可以具有重复顶点时起作用。 / p>
当我们寻找简单的路径(没有重复的顶点)时,我们可以(1)按升序放松图中的所有边,然后,在这些相同的路径上,(2)放松图中的所有边。降序排列。虽然我们按升序放松边缘,但我们确保丢弃仅下降的路径,或者所有边缘具有相同权重的路径(除非恰好有2个相同权重的边缘,请参阅下面对此边缘情况的评论)。当以降序放松边缘时,我们丢弃仅具有上升边缘权重的路径。
这是我提出的算法的一般概念。还涉及一些实现细节:优先级队列用于放松,其中Path对象按最小权重排序。重要的是要考虑具有正好2个相等权重边的路径的边缘情况(根据问题定义,这是一个比特路径)。
运行时复杂性似乎是O(P lg P),其中P是图中路径的数量。
代码及其依赖项和测试可以在我的GitHub中找到: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;
}
}