在包含Java循环的图形中查找特定距离内的所有边

时间:2015-07-09 12:01:21

标签: java algorithm search graph priority-queue

我有一个由Node个对象连接的Edge个对象的图表,需要按以下方式进行探索:

我获得了一个起始Edge source,需要查找所有其他Edge个对象,以便沿路径传递的边长度总和不超过MAX_RANGE并执行符合条件的每个Edge的一些操作。

我对此问题的解决方案是递归分支,随时跟踪行进距离(Edge#getEdgeConnections()返回包含连接到{{1}的所有ArrayList<Edge>个对象的Edge它被召唤了):

Edge

现在,这应该可以正常工作,除了一件事 - 图表包含几个循环的情况。

因此,如果递归算法以循环周围较长的路径开始,则可能会错过选择较短路径时指定范围内的某些边缘,如下所示

private final ArrayList<Edge> occupiedEdges = new ArrayList<>();

private void doStuffWithinRangeOf(Edge source) {
    doStuffAtEdge(source);
    for (Edge connection : source.getEdgeConnections()) {
        doStuffAtBranch(connection, source, 0);
    }
}

private void doStuffAtBranch(Edge edge, Edge source, double distance) {
    double newDistance = distance + edge.getLength();
    doStuffAtEdge(edge);
    for (Edge connection : edge.getEdgeConnections()) {
        if (!connection.equals(source) 
                && !isOccupied(connection) 
                && (newDistance < MAX_AP_RANGE)) {
            doStuffAtBranch(connection, edge, newDistance);
        }
    }
}

private void duStuffAtEdge(Edge edge) {
    occupiedEdges.add(edge);
    ... // Some amount of work that mustn't be done more than once per Edge
}

private boolean isOccupied(Edge edge) {
    return occupiedEdges.contains(edge);
}

现在,我想到的解决方案是做一个不同的搜索模式,在那里我将有一个&#34; frontier&#34;的列表(或地图)。边缘,并按照距离增加的顺序探索它们(每次探索边缘时都会向边界添加新边缘)。

可能涉及大量边缘,因此我不是每次都要遍历整个列表,以找到距离源最短距离的那个。

是否有某种类型的集合能够以这种方式自动保存订单并且在添加/删除元素方面有效?

SortedMap是我要找的吗?在这种情况下我如何使用它?

修改 感谢所有响应者。我最终使用带有包装类的 ----------- C ---- D - E // The algorithm explores AB, BC, CD and makes them occupied | | B | // DE is too far along this path, and isn't occupied | | ------------A // When the algorithm explores along AC, it finds that CD // is already occupied and stops // even though DE is really within range (有关详细信息和代码,请参阅my answer)。

7 个答案:

答案 0 :(得分:2)

我建议您调整算法,而不是使用其他一些数据结构:

您已经实施了某种depth-first-search来浏览图表。如果您使用某种breadth-first-search,则可以在达到指定范围时停止,并且只访问范围内的每个边缘一次(通过使用已经实现的isOccupied逻辑​​)。

答案 1 :(得分:1)

您正在寻找的算法是经过修改的Dijkstra,而不是搜索从A到B的最短路径,而是搜索所有短于X的最短路径.Dijkstra保证您将访问每个节点从开始的距离递增,并从开始通过最短路径。此外,如果没有负长度边缘,那么父母身份将永远不会改变 - 并且保证内部if将沿着到节点的最小路径的每条边执行一次且仅执行一次。但是,由于“比X更接近的节点”的集合仅在结尾处是已知的(=具有最终距离&lt; max的那些节点),因此您可以等到算法结束到doStuffAtBranch仅用于实际引导某个地方有趣的分支

伪代码如下:

final HashMap<Node, Double> distances = new HashMap<>();
HashMap<Node, Node> parents = new HashMap<>();
distances.put(start, 0);  // start is at distance 0 from start

PriorityQueue<Vertex> q = new PriorityQueue(allVertices.size(), 
    new Comparator<Vertex>() {
        public int compare(Vertex a, Vertex b) {
            return distances.get(a) - distances.get(b);
        }
});

for (Vertex v : allVertices) {
    q.add(v, distances.contains(v) ? 
        distances.get(v) : Double.POSITIVE_INFINITY);
}

while ( ! q.isEmpty()) {
    Vertex u = q.poll(); // extract closest
    double current = distances.get(u);
    if (current > max) {
        // all nodes that are reachable in < max have been found
        break;
    }
    for (Edge e : u.getEdges()) {
        Vertex v = u.getNeighbor(e);
        double alt = current + e.length();
        if (alt < distances.get(v)) {
            q.remove(v);       // remove before updating distance
            distances.put(v, alt);
            parents.put(v, u); // v will now be reached via u
            q.add(v);          // re-add with updated distance
            // if there are no negative-weight edges, e will never be re-visited
        }
    }
}

答案 2 :(得分:1)

此Java实现使用DFS,该图表示为Adjacency Matrix。还对已处理节点使用int数组,并使用int数组标记节点与startNode的距离

public List<Integer> findMinDistantNodesUsingBF(int[][] graph, int startNode, int distance) {
    int len = graph.length;
    boolean[] processed = new boolean[len];
    int[] distanceArr = new int[len];
    LinkedList<Integer> queue = new LinkedList<Integer>();
    List<Integer> result = new ArrayList<Integer>();
    queue.add(startNode);
    processed[startNode] = true;
    distanceArr[startNode] = 0;
    while (!queue.isEmpty()) {
        int node = queue.remove();
        for (int i = 0; i < len; i++) {
            if (graph[node][i] == 1 && !processed[i]) {
                if (distanceArr[node] == distance - 1) {
                    result.add(i);
                } else {
                    queue.add(i);
                    distanceArr[i] = distanceArr[node] + 1;
                }
                processed[i] = true;
            }
        }
    }

    return result;
}

答案 3 :(得分:0)

您可以使用BFS或DFS的修改,您还可以为路径的MAX_LENGTH添加额外规则(除了检查您是否访问过所有邻居节点)。

我建议您选择DFS,因为它与您目前所做的有点接近。您也可以轻松地“插入”doSomethingToEdge中的任何一种方法

答案 4 :(得分:0)

注意:在写完答案之后,我意识到你正在谈论边缘,但是我在节点方面提到的同样的例子和相同的理论也适用于边缘。

如果您只是简单地使用BFS,那么它将无法正常工作,因为您会过早地将node标记为已访问过。考虑这个例子

你的max_range是20&amp; d(x,y)表示xy

之间的边缘权重
         A->(10)E->D(10)->(5)F // d(A,E)=d(E,D)=10 & d(D,F)=5

         A->(5)B->(5)C->(5)D->(5)F //d(A,B)=d(B,C)=d(C,D)=d(D,F)=5  

在这种情况下,您首先会在路径(A-> E-> D)中找到D(因为它在此路径中更接近A),并且您将D标记为已访问。这实际上是错误的,因为将D标记为已访问会阻止您访问F,如果您的路径是(A->B->C->D->F),则可以访问List of nodes

为了避免重复并解决此问题,对于您要添加的每个节点,您还应添加在当前路径中看到的F。这样您仍然可以访问D,因为当您在路径(A->B->C->D)中到达 NodeWrapper{ Node node; List<Node> Parents; int pathSum; } 时,您会看到它未被访问,因为它没有出现在您的路径中。

来实施,我会给你一个粗略的想法:

创建一个包装类

 { 

   Queue<NodeWrapper> queue = new LinkedList<>();
   Queue.add(new NodeWrapper(sourceNode,new ArrayList<Node>));

   while(!queue.isEmpty()){
         NodeWrapper temp = queue.poll();
         Node presentNode = temp.getNode();
         List<Node> parentsList = temp.getParentsList();
         for all neighbours of presentNode
              if neighbour is not in presentNode && (pathSum +currentEdgeWeight )< max && your conditions
             add currentNode to parent list
             queue.add(new NodeWrapper(neighbour,parentList,(pathSum +currentEdgeWeight)));

   }

 }

您的BFS应如下所示:

<a href="http://drive.google.com/drive/folders/folderidgoeshere" target="_blank">Drive button</a>

答案 5 :(得分:0)

经过几次迭代后,我最终使用了类似to that of user tucuxi的解决方案。我使用PriorityQueue和一个实现Comparable接口的包装类。当我意识到我需要在几个不同的情况下探索图形并做不同的事情时,我在Edge类中创建了一个通用的方法,它返回所提供范围内的所有其他边。

<强>代码:

public ArrayList<Edge> uniqueEdgesWithinRange(double range) {
    ArrayList<Edge> edgeList = new ArrayList<>();
    PriorityQueue<ComparableEdge> frontier = new PriorityQueue<>();
    frontier.add(new ComparableEdge(0.0, this));

    while(!frontier.isEmpty()) {
        ComparableEdge cEdge = frontier.poll();

        edgeList.add(cEdge.edge);

        if (cEdge.distance < range) {
            for (Edge connection : cEdge.edge.getEdgeConnections()) {
                if (!edgeList.contains(connection)) {
                    frontier.add(new ComparableEdge(cEdge.distance + connection.getLength(), connection));
                }
            }
        }
    }

    return edgeList;
}

private class ComparableEdge implements Comparable<ComparableEdge> {
    private double distance; // Distance from closest point on source to furthest point on edge
    private Edge edge;

    private ComparableEdge(double distance, Edge edge) {
        this.distance = distance;
        this.edge = edge;
    }

    @Override
    public int compareTo(ComparableEdge another) {
        return Double.compare(distance, another.distance);
    }
}

该方法中的缩进量使我感觉很高兴,所以我可能会重构它,但是否则它应该在功能上完整。

答案 6 :(得分:0)

// Finds all nodes that are maximum x distance away from given node.
public Set<Integer> findMaxDistantNodesRecurse(int[][] graph, int startNode, int distance) {
    int len = graph.length;
    Set<Integer> set = new HashSet<Integer>();

    for (int j = 0; j < len; j++) {
        if (startNode == j)
            continue;
        if (graph[startNode][j] == 1) {
            set.add(j);
            if (distance > 1)
                set.addAll(findMaxDistantNodesRecurse(graph, j, distance - 1));

        }
    }

    return set;
}