实现双向图搜索

时间:2016-07-30 13:49:55

标签: java search graph artificial-intelligence bidirectional-search

我正在尝试实施bi-directional graph search。据我所知,我应该以某种方式合并两个广度优先搜索,一个从起始(或根)节点开始,另一个从目标(或结束)节点开始。当广度优先搜索"满足"时,双向搜索终止。在同一个顶点。

您能否提供代码示例(如果可能,使用Java)或链接代码以进行双向图搜索?

3 个答案:

答案 0 :(得分:11)

假设你有Node这样的(在文件Node.java中):

import java.util.HashSet;
import java.util.Set;

public class Node<T> {
    private final T data; // The data that you want to store in this node.
    private final Set<Node> adjacentNodes = new HashSet<>();

    // Constructor
    public Node(T data) {
        this.data = data;
    }

    // Getters

    /*
     * Returns the data stored in this node.
     * */
    public T getData() {
        return data;
    }

    /*
     * Returns a set of the adjacent nodes of this node.
     * */
    public Set<Node> getAdjacentNodes() {
        return adjacentNodes;
    }

    // Setters

    /*
     * Attempts to add node to the set of adjacent nodes of this node. If it was not previously added, it is added, and
     * true is returned. If it was previously added, it returns false.
     * */
    public boolean addAdjacent(Node node) {
        return adjacentNodes.add(node);
    }
}

然后双向搜索算法(在文件BidirectionalSearch.java中定义)看起来像这样:

import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
import java.util.LinkedList;


public class BidirectionalSearch {

    /*
     * Returns true if a path exists between Node a and b, false otherwise.
     * */
    public static boolean pathExists(Node a, Node b) {
        // LinkedList implements the Queue interface, FIFO queue operations (e.g., add and poll).

        // Queue to hold the paths from Node a.
        Queue<Node> queueA = new LinkedList<>();

        // Queue to hold the paths from Node a.
        Queue<Node> queueB = new LinkedList<>();

        // A set of visited nodes starting from Node a.
        Set<Node> visitedA = new HashSet<>();

        // A set of visited nodes starting from Node b.
        Set<Node> visitedB = new HashSet<>();

        visitedA.add(a);
        visitedB.add(b);

        queueA.add(a);
        queueB.add(b);

        // Both queues need to be empty to exit the while loop.
        while (!queueA.isEmpty() || !queueB.isEmpty()) {
            if (pathExistsHelper(queueA, visitedA, visitedB)) {
                return true;
            }
            if (pathExistsHelper(queueB, visitedB, visitedA)) {
                return true;
            }
        }

        return false;
    }

    private static boolean pathExistsHelper(Queue<Node> queue,
                                            Set<Node> visitedFromThisSide,
                                            Set<Node> visitedFromThatSide) {
        if (!queue.isEmpty()) {
            Node next = queue.remove();

            Set<Node> adjacentNodes = next.getAdjacentNodes();

            for (Node adjacent : adjacentNodes) {

                // If the visited nodes, starting from the other direction,
                // contain the "adjacent" node of "next", then we can terminate the search
                if (visitedFromThatSide.contains(adjacent)) {
                    return true;
                } else if (visitedFromThisSide.add(adjacent)) {
                    queue.add(adjacent);
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        // Test here the implementation above.
    }
}

答案 1 :(得分:0)

逻辑: 在正常过程中,BFS是递归的。但是在这里我们不能让它递归,因为如果我们从递归开始,那么它将从一侧(开始或结束)覆盖所有节点,并且只有在它无法找到结束或找到结束时才会停止。

因此,为了进行双向搜索,将使用以下示例解释逻辑:

npm install -g vue-cli@2.9.3

此代码如下:

/*
Let's say this is the graph
        2------5------8
       /              |
      /               |
     /                |
    1---3------6------9
     \                |
      \               |
       \              |
        4------7------10
We want to find the path between nodes 1 and 9. In order to do this we will need 2 DS, one for recording the path form beginning and other from end:*/

ArrayList<HashMap<Integer, LinkedList<Node<Integer>>>> startTrav = new ArrayList<>();
ArrayList<HashMap<Integer, LinkedList<Node<Integer>>>> endTrav = new ArrayList<>();

/*Before starting the loop, initialise these with the values shown below:
startTrav --> index=0 --> <1, {1}>
endTrav --> index=0 --> <9, {9}>

Note here that in the HashMap, the key is the node that we have reached and the value is a linkedList containing the path used to reach to that node. 
Now inside the loop we will start traversal on startTrav 1st. We will traverse it from index 0 to 0, and while traversing what ever children are there for the node under process, we will add in startTrav. So startTrav will transform like:
startTrav --> index=0 --> <1, {1}>
startTrav --> index=1 --> <2, {1,2}>
startTrav --> index=2 --> <3, {1,3}>
startTrav --> index=3 --> <4, {1,4}>

Now we will check for collision, i.e if either of nodes that we have covered in startTrav are found in endTrav (i.e if either of 1,2,3,4 is present in endTrav's list = 9). The answer is no, so continue loop.

Now do the same from endTrav
endTrav --> index=0 --> <9, {9}>
endTrav --> index=1 --> <8, {9,8}>
endTrav --> index=2 --> <6, {9,6}>
endTrav --> index=3 --> <10, {9,10}>

Now again we will check for collision, i.e if either of nodes that we have covered in startTrav are found in endTrav (i.e if either of 1,2,3,4 is present in endTrav's list = 9,8,6,10). The answer is no so continue loop.
// end of 1st iteration of while loop

// beginning of 2nd iteration of while loop
startTrav --> index=0 --> <1, {1}>
startTrav --> index=1 --> <2, {1,2}>
startTrav --> index=2 --> <3, {1,3}>
startTrav --> index=3 --> <4, {1,4}>
startTrav --> index=4 --> <5, {1,2,5}>
startTrav --> index=5 --> <6, {1,3,6}>
startTrav --> index=6 --> <7, {1,4,7}>

Now again we will check for collision, i.e if either of nodes that we have covered in startTrav are found in endTrav (i.e if either of 1,2,3,4,5,6,7 is present in endTrav's list = 9,8,6,10). The answer is yes. Colission has occurred on node 6. Break the loop now.

Now pick the path to 6 from startTrav and pick the path to 6 from endTrav and merge the 2.*/

更简洁,更容易理解的方法可以是阵列。我们将替换复杂的DS:

class Node<T> {
    public T value;
    public LinkedList<Node<T>> nextNodes = new LinkedList<>();
}
class Graph<T>{
    public HashMap<Integer, Node<T>> graph=new HashMap<>();
}
public class BiDirectionalBFS {
    public LinkedList<Node<Integer>> findPath(Graph<Integer> graph, int startNode, int endNode) {
        if(!graph.graph.containsKey(startNode) || !graph.graph.containsKey(endNode)) return null;

        if(startNode==endNode) {
            LinkedList<Node<Integer>> ll = new LinkedList<>();
            ll.add(graph.graph.get(startNode));
            return ll;
        }
        ArrayList<HashMap<Integer, LinkedList<Node<Integer>>>> startTrav = new ArrayList<>();
        ArrayList<HashMap<Integer, LinkedList<Node<Integer>>>> endTrav = new ArrayList<>();

        boolean[] traversedNodesFromStart = new boolean[graph.graph.size()];
        boolean[] traversedNodesFromEnd = new boolean[graph.graph.size()];

        addDetailsToAL(graph, startNode, startTrav, traversedNodesFromStart, null);
        addDetailsToAL(graph, endNode, endTrav, traversedNodesFromEnd, null);

        int collision = -1, startIndex=0, endIndex=0;

        while (startTrav.size()>startIndex && endTrav.size()>endIndex) {

            // Cover all nodes in AL from start and add new
            int temp=startTrav.size();
            for(int i=startIndex; i<temp; i++) {
                recordAllChild(graph, startTrav, i, traversedNodesFromStart);
            }
            startIndex=temp;

            //check collision
            if((collision = checkColission(traversedNodesFromStart, traversedNodesFromEnd))!=-1) {
                break;
            }

            //Cover all nodes in AL from end and add new
            temp=endTrav.size();
            for(int i=endIndex; i<temp; i++) {
                recordAllChild(graph, endTrav, i, traversedNodesFromEnd);
            }
            endIndex=temp;

            //check collision
            if((collision = checkColission(traversedNodesFromStart, traversedNodesFromEnd))!=-1) {
                break;
            }
        }

        LinkedList<Node<Integer>> pathFromStart = null, pathFromEnd = null;
        if(collision!=-1) {
            for(int i =0;i<traversedNodesFromStart.length && (pathFromStart==null || pathFromEnd==null); i++) {
                if(pathFromStart==null && startTrav.get(i).keySet().iterator().next()==collision) {
                    pathFromStart=startTrav.get(i).get(collision);
                }
                if(pathFromEnd==null && endTrav.get(i).keySet().iterator().next()==collision) {
                    pathFromEnd=endTrav.get(i).get(collision);
                }
            }
            pathFromEnd.removeLast();
            ListIterator<Node<Integer>> li = pathFromEnd.listIterator();
            while(li.hasNext()) li.next();
            while(li.hasPrevious()) {
                pathFromStart.add(li.previous());
            }
            return pathFromStart;
        }
        return null;
    }
    private void recordAllChild(Graph<Integer> graph, ArrayList<HashMap<Integer, LinkedList<Node<Integer>>>> listToAdd, int index, boolean[] traversedNodes) {
        HashMap<Integer, LinkedList<Node<Integer>>> record=listToAdd.get(index);
        Integer recordKey = record.keySet().iterator().next();
        for(Node<Integer> child:graph.graph.get(recordKey).nextNodes) {
            if(traversedNodes[child.value]!=true) {                 addDetailsToAL(graph, child.getValue(), listToAdd, traversedNodes, record.get(recordKey));
            }
        }
    }
    private void addDetailsToAL(Graph<Integer> graph, Integer node, ArrayList<HashMap<Integer, LinkedList<Node<Integer>>>> startTrav,
            boolean[] traversalArray, LinkedList<Node<Integer>> oldLLContent) {
        LinkedList<Node<Integer>> ll = oldLLContent==null?new LinkedList<>() : new LinkedList<>(oldLLContent);
        ll.add(graph.graph.get(node));
        HashMap<Integer, LinkedList<Node<Integer>>> hm = new HashMap<>();
        hm.put(node, ll);
        startTrav.add(hm);
        traversalArray[node]=true;
    }

    private int checkColission(boolean[] start, boolean[] end) {
        for (int i=0; i<start.length; i++) {
            if(start[i] && end[i]) {
                return i;
            }
        }
        return -1;
    }
}

用简单的

ArrayList<HashMap<Integer, LinkedList<Node<Integer>>>> 

这里,LL的索引将定义节点的数值。因此,如果节点具有值7,则到达7的路径将存储在阵列中的索引7处。此外,我们将删除布尔数组,以查找找到哪个元素的路径,因为可以使用我们的linkedList数组本身实现。我们将添加2

LinkedList<Node<Integer>>[]

将用于存储子项,就像树的级别顺序遍历一样。最后,我们用于存储从末尾遍历的路径,我们将以相反的顺序存储它,这样在合并时,我们不需要反转第二个数组中的元素。代码如下:

LinkedList<Node<Integer>>

希望它有所帮助。

快乐的编码。

答案 2 :(得分:0)

尝试一下:

Graph.java

else if (date >= new Date('2019-03-10T15:00:00') && date <= new Date('2019-04-25T17:00:00')) {
    return false;
}

GraphHelper.java

import java.util.HashSet;
import java.util.Set;

public class Graph<T> {
    private T value;
    private Set<Graph> adjacents = new HashSet<>();
    private Set<String> visitors = new HashSet<>();

    public Graph(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void addAdjacent(Graph adjacent) {
        this.adjacents.add(adjacent);
    }

    public Set<Graph> getAdjacents() {
        return this.adjacents;
    }

    public void setVisitor(String visitor) {
        this.visitors.add(visitor);
    }

    public boolean hasVisitor(String visitor) {
        return this.visitors.contains(visitor);
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("Value [").append(value).append("] visitors[");
        if (!visitors.isEmpty()) {
            for (String visitor : visitors) {
                sb.append(visitor).append(",");
            }
        }
        sb.append("]");
        return sb.toString().replace(",]", "]");
    }
}

GraphTest.java

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;

public class GraphHelper {
    // implements singleton pattern
    private static GraphHelper instance;

    private GraphHelper() {
    }

    /**
     * @return the instance
     */
    public static GraphHelper getInstance() {
        if (instance == null)
            instance = new GraphHelper();
        return instance;
    }

    public boolean isRoute(Graph gr1, Graph gr2) {
        Queue<Graph> queue1 = new LinkedList<>();
        Queue<Graph> queue2 = new LinkedList<>();

        addToQueue(queue1, gr1, "1");
        addToQueue(queue2, gr2, "2");

        while (!queue1.isEmpty() || !queue2.isEmpty()) {
            if (!queue1.isEmpty()) {
                Graph gAux1 = queue1.remove();
                Iterator<Graph> it1 = gAux1.getAdjacents().iterator();

                while (it1.hasNext()) {
                    Graph adj1 = it1.next();
                    System.out.println("adj1 " + adj1);
                    if (adj1.hasVisitor("2"))
                        return true;
                    else if (!adj1.hasVisitor("1"))
                        addToQueue(queue1, adj1, "1");
                }
            }

            if (!queue2.isEmpty()) {
                Graph gAux2 = queue2.remove();
                Iterator<Graph> it2 = gAux2.getAdjacents().iterator();
                while (it2.hasNext()) {
                    Graph adj2 = it2.next();
                    System.out.println("adj2 " + adj2);
                    if (adj2.hasVisitor("1"))
                        return true;
                    else if (!adj2.hasVisitor("2"))
                        addToQueue(queue2, adj2, "2");
                }
            }
        }

        return false;
    }

    private void addToQueue(Queue<Graph> queue, Graph gr, String visitor) {
        gr.setVisitor(visitor);
        queue.add(gr);
    }
}