Search code examples
algorithmpathroutesshortest

what is the best algorithm to traverse a graph with negative nodes and looping nodes


I have a really difficult problem to solve and Im just wondering what what algorithm can be used to find the quickest route. The undirected graph consist of positive and negative adjustments, these adjustments effect a bot or thing which navigate the maze. The problem I have is mazes which contain loops that can be + or -. An example might help:-

  1. node A gives 10 points to the object

  2. node B takes 15 from the object

  3. node C gives 20 points to the object

route=""

the starting node is A, and the ending node is C

given the graph structure as:-

a(+10)-----b(-15)-----c+20

 node() means the node loops to itself   - and + are the adjustments

nodes with no loops are c+20, so node c has a positive adjustment of 20 but has no loops

if the bot or object has 10 points in its resource then the best path would be :-

a > b > c    the object would have 25 points when it arrives at c

route="a,b,c"

this is quite easy to implement, the next challenge is knowing how to backtrack to a good node, lets assume that at each node you can find out any of its neighbour's nodes and their adjustment level. here is the next example:-

if the bot started with only 5 points then the best path would be

a > a > b > c the bot would have 25 points when arriving at c

route="a,a,b,c"

this was a very simple graph, but when you have lots of more nodes it becomes very difficult for the bot to know whether to loop at a good node or go from one good node to another, while keeping track of a possible route.

such a route would be a backtrack queue.

A harder example would result in lots of going back and forth

bot has 10 points

a(+10)-----b(-5)-----c-30

a > b > a > b > a > b > a > b > a > b > c having 5 pts left.

another way the bot could do it is:-

a > a > a > b > c

this is a more efficient way, but how the heck you can program this is partly my question.

does anyone know of a good algorithm to solve this, ive already looked into Bellman-fords and Dijkstra but these only give a simple path not a looping one.

could it be recursive in some way or some form of heuristics?


referring to your analogy:-

I think I get what you mean, a bit of pseudo would be clearer, so far route()

q.add(v)
best=v
hash visited(v,true)

while(q is not empty)
    q.remove(v)
    for each u of v in G

        if u not visited before
            visited(u,true)
            best=u=>v.dist
        else
            best=v=>u.dist

Solution

  • This is a straightforward dynamic programming problem.

    Suppose that for a given length of path, for each node, you want to know the best cost ending at that node, and where that route came from. (The data for that length can be stored in a hash, the route in a linked list.)

    Suppose we have this data for n steps. Then for the n+1st we start with a clean slate, and then take each answer for the n'th, move it one node forward, and if we land on a node we don't have data for, or else that we're better than the best found, then we update the data for that node with our improved score, and add the route (just this node linking back to the previous linked list).

    Once we have this for the number of steps you want, find the node with the best existing route, and then you have your score and your route as a linked list.

    ========

    Here is actual code implementing the algorithm:

    class Graph:
        def __init__(self, nodes=[]):
            self.nodes = {}
            for node in nodes:
                self.insert(node)
    
        def insert(self, node):
            self.nodes[ node.name ] = node
    
        def connect(self, name1, name2):
            node1 = self.nodes[ name1 ]
            node2 = self.nodes[ name2 ]
            node1.neighbors.add(node2)
            node2.neighbors.add(node1)
    
        def node(self, name):
            return self.nodes[ name ]
    
    class GraphNode:
        def __init__(self, name, score, neighbors=[]):
            self.name = name
            self.score = score
            self.neighbors = set(neighbors)
    
        def __repr__(self):
            return self.name
    
    def find_path (start_node, start_score, end_node):
        prev_solution = {start_node: [start_score + start_node.score, None]}
        room_to_grow = True
        while end_node not in prev_solution:
            if not room_to_grow:
                # No point looping endlessly...
                return None
            room_to_grow = False
            solution = {}
            for node, info in prev_solution.iteritems():
                score, prev_path = info
                for neighbor in node.neighbors:
                    new_score = score + neighbor.score
                    if neighbor not in prev_solution:
                        room_to_grow = True
                    if 0 < new_score and (neighbor not in solution or solution[neighbor][0] < new_score):
                        solution[neighbor] = [new_score, [node, prev_path]]
            prev_solution = solution
        path = prev_solution[end_node][1]
        answer = [end_node]
        while path is not None:
            answer.append(path[0])
            path = path[1]
        answer.reverse()
        return answer
    

    And here is a sample of how to use it:

    graph = Graph([GraphNode('A', 10), GraphNode('B', -5), GraphNode('C', -30)])
    graph.connect('A', 'A')
    graph.connect('A', 'B')
    graph.connect('B', 'B')
    graph.connect('B', 'B')
    graph.connect('B', 'C')
    graph.connect('C', 'C')
    
    print find_path(graph.node('A'), 10, graph.node('C'))
    

    Note that I explicitly connected each node to itself. Depending on your problem you might want to make that automatic.

    (Note, there is one possible infinite loop left. Suppose that the starting node has a score of 0 and there is no way off of it. In that case we'll loop forever. It would take work to add a check for this case.)