Search code examples
pythonrecursiongraphdirected-acyclic-graphslongest-path

Function to find the highest score path in a graph with python


I'm trying to make a function to find the highest score in a directed graph. I do have a start node and can't go throught the same node twice. I've tried to use recursive to get the sum of values until I hit one end node. Then I would call back my function to the start node and try other option ultil I hit another. And so on.

My problem is that when i return to a node with more than one path, the score value for this node is the sum of all paths it can take. And I only want the sum of one specific path.

Here is my code so far:


caminho = list()
def maxscore(start, parentals, score):
    global caminho
    parentals += start + '|'

    if len(graph[start]) > 0:
        for i in graph[start]:
            if i not in parentals.split('|'):
                value = graph[start][i]
                if value:
                    score += value

                func = maxscore(i, parentals, score)

            else:
                continue

        if func[0] > score:
            score = func[0]
            caminho = parentals.split('|')

        return score, caminho

    else:
        return score, start

graph = {
    'a': {'b': 2, 'c': 4},
    'b': {'d': 5},
    'c': {'a': 1, 'e': 3},
    'd': {'f': 4},
    'e': {'b': 2, 'f': 3, 'g': 2},
    'f': {},
    'g': {}
}

print(maxscore('a', '', 0))

How could I make it work to return in the end only the best score with the path(caminho) it took.

Sorry if I couldn't make myself clear enough. Feel free to ask any questions.


Solution

  • Here is an approach:

    def maxscore(start, path, score):
        best_score = -1
        best_i = None
        best_path = None
    
        current_path = path + [start]
        for i in graph[start]:
            if not i in path:
                score_i, path_i = maxscore(i, current_path, score + graph[start][i])
                if score_i > best_score:
                    best_score = score_i
                    best_i = i
                    best_path = path_i
        if best_i is None:
            return score, current_path
        else:
            return best_score, best_path
    
    graph = {
        'a': {'b': 2, 'c': 4},
        'b': {'d': 5},
        'c': {'a': 1, 'e': 3},
        'd': {'f': 4},
        'e': {'b': 2, 'f': 3, 'g': 2},
        'f': {},
        'g': {}
    }
    
    print(maxscore('a', [], 0))
    

    Output:

    (18, ['a', 'c', 'e', 'b', 'd', 'f'])
    

    Notes:

    • In the code of the question caminho and parentals served the same function. Just having a list is easier to work with than with a string.
    • In the for-loop, we test each of the edges. If the end-node of the edge is not yet in the path, we calculate the maximum score following that edge. We have to compare each of the possible edges and keep the one with the best score. We can only return from maxscore() after testing all possible edges.
    • If no free edge was found, we just return the current score and path
    • If free edges were found, we return the score and path of the best edge

    PS: After revising the code, it can be shortened a bit. best_iis not really needed, and the test if best_i is None can be replaced by if best_path is None.

    Also, if you need the path in the string form, you can print("|".join(best_path)).