Search code examples
pythonpython-3.xbreadth-first-searchbidirectional-search

Bidirectional Search


I am trying to implement a bi-directional search in python.

As I understand, I should somehow merge two breadth-first searches, one which starts at the starting (or root) node and one which starts at the goal (or end) node. The bi-directional search terminates when both breadth-first searches "meet" at the same vertex. I have implemented BFS the code is given below

def bfs(graph, start):
    path = []
    queue = [start]
    while queue:
        vertex = queue.pop(0)
        if vertex not in path:
            path.append(vertex)
            queue.extend(graph[vertex])
    return path

Could you provide me with a code example (in Python) or link with code for the bidirectional graph search?


Solution

  • This seemed like a fun problem so I had a go at it. Here's my attempt. You didn't say what format your graph is in, so I guessed a dictionary like the following:

    example_graph = {0:[1,2], 1:[0,3,4], 3:[1], 4:[1], 2:[0,5,6], 5:[2], 6:[2]}
    

    The code runs through a list of currently active vertices, starting from the specified start and goal vertices, and remembers how it got to them through a dictionary of {vertices : lists}. If an active vertex finds itself next to another active vertex with a path that started from the other end, it merges the lists and returns the results, otherwise it extends all the paths and continues.

    def bi_directional_search(graph, start, goal):
        # Check if start and goal are equal.
        if start == goal:
            return [start]
        # Get dictionary of currently active vertices with their corresponding paths.
        active_vertices_path_dict = {start: [start], goal: [goal]}
        # Vertices we have already examined.
        inactive_vertices = set()
    
        while len(active_vertices_path_dict) > 0:
    
            # Make a copy of active vertices so we can modify the original dictionary as we go.
            active_vertices = list(active_vertices_path_dict.keys())
            for vertex in active_vertices:
                # Get the path to where we are.
                current_path = active_vertices_path_dict[vertex]
                # Record whether we started at start or goal.
                origin = current_path[0]
                # Check for new neighbours.
                current_neighbours = set(graph[vertex]) - inactive_vertices
                # Check if our neighbours hit an active vertex
                if len(current_neighbours.intersection(active_vertices)) > 0:
                    for meeting_vertex in current_neighbours.intersection(active_vertices):
                        # Check the two paths didn't start at same place. If not, then we've got a path from start to goal.
                        if origin != active_vertices_path_dict[meeting_vertex][0]:
                            # Reverse one of the paths.
                            active_vertices_path_dict[meeting_vertex].reverse()
                            # return the combined results
                            return active_vertices_path_dict[vertex] + active_vertices_path_dict[meeting_vertex]
    
                # No hits, so check for new neighbours to extend our paths.
                if len(set(current_neighbours) - inactive_vertices - set(active_vertices))  == 0:
                    # If none, then remove the current path and record the endpoint as inactive.
                    active_vertices_path_dict.pop(vertex, None)
                    inactive_vertices.add(vertex)
                else:
                    # Otherwise extend the paths, remove the previous one and update the inactive vertices.
                    for neighbour_vertex in current_neighbours - inactive_vertices - set(active_vertices):
                        active_vertices_path_dict[neighbour_vertex] = current_path + [neighbour_vertex]
                        active_vertices.append(neighbour_vertex)
                    active_vertices_path_dict.pop(vertex, None)
                    inactive_vertices.add(vertex)
    
        return None