简体   繁体   中英

Finding all combinations of paths from a graph with a given distance from the origin


I'm trying to find all combinations of paths from a graph with a given distance from the origin.

In fact, the new expansion of World of Warcraft (Legion) will introduce the Artifact system to the game.
It's a feature that you can level up, each level give you 1 rank and you can spend 1 point for each rank in a tree.
You can find a calculator of each artifact on WowHead and I will use this one as example : http://legion.wowhead.com/artifact-calc/rogue/subtlety/

Basically, the aim of the program would be :
"I give a rank, let's say 7, and it returns to me every path combinations from the graph which I had to spent 7 points to get there (ie, list of 7 unique nodes)".

When I saw the calculator, I thought that it was solvable by transposing it to a graph, so I made one to help me to get through : Graph

On the graph, I had to make little adjustments from the calculator, for example each trait which got 3 ranks had to be represented as 3 nodes linked each other. Also, for the traits which were unlocking 2 ways to continue, I had to represent them as 4 nodes to "emulate" the 3 nodes requirements to get through. (But we'll see after that is still an issue and it didn't really fixed the problem)

Then from there I tried to find a good way to list every possibilities, so I made a lot of research over the internet to find the best way to handle my issue.
I firstly tried to solve it using the Breadth-first search, but having the distance from the origin of each node didn't helped much. Then I tried to use the exhaustive search.
I'm currently using an adaption of the code posted there : "Finding all paths from a given graph" on CodeReview@StackEchange (can't post more than 2 links)

def paths(graph, v, lmax):
    """Generate the maximal cycle-free paths with a given maximum length lmax in 
    graph starting at v. Graph must be a mapping from vertices to collections of
    neighbouring vertices.

    >>> g = {1: [2, 3], 2: [3, 4], 3: [1], 4: []}
    >>> sorted(paths(g, 1, 3))
    [[1, 2, 3], [1, 2, 4], [1, 3]]
    >>> sorted(paths(g, 3, 4))
    [[3, 1, 2, 4]]

    Credit to Gareth Rees from StackExchange for the original code.
    """
    path = [v]                  # path traversed so far
    seen = {v}                  # set of vertices in path
    def search():
        dead_end = True
        if len(seen) < lmax:
            for neighbour in graph[path[-1]]:
                if neighbour not in seen:
                    dead_end = False
                    seen.add(neighbour)
                    path.append(neighbour)
                    yield from search()
                    path.pop()
                    seen.remove(neighbour)
        if dead_end:
            yield list(path)
    yield from search()

Then I make a function to sort the results and display only the one that had the required length.

def artifact(graph, maxrank, start):
    for i in range(1, maxrank+1):
        print("---------")
        print("Rank: " + str(i))
        print("---------")
        # Get all the Paths at "i" Rank
        RawPaths = sorted(paths(g, start, i), key=len)
        # Remove paths that doesn't satisfact our rank requirement and sort it
        ValidPaths = [sorted(j) for j in RawPaths if len(j) == i]
        # Remove duplicates
        UniquePaths = sorted([list(j) for j in set(map(tuple, ValidPaths))])
        # Display the Paths
        for j in range(len(UniquePaths)):
            PathString = "";
            for k in range(len(UniquePaths[j])):
                PathString += str(UniquePaths[j][k]) + " "
            print(PathString)
        print("")

From there, I built list of adjacent nodes (neighbors) of a portion of nodes from the graph. I can't post more than 2 link, but the subgraph end at 8/7/32/31 nodes from the graph linked earlier.

g = {
  1: [2, 38],
  2: [1, 3],
  3: [2, 4],
  4: [3, 5],
  5: [4, 6],
  6: [5, 7, 8],
  7: [6],
  8: [6],
 31: [33],
 32: [33],
 33: [31, 32, 34],
 34: [33, 35],
 35: [34, 36],
 36: [35, 37],
 37: [36, 38],
 38: [1, 37]
}

And then I called my function:

artifact(g, 8, 1)

But using this list, I was facing a major issue. In fact, the algorithm go straight until the end but it doesn't do any backtrack to reach the rank wanted (ie, after going all the way through 1 - 38 - 37 - 36 - 35 - 34 - 33 - 31, it wouldn't go back to 2 - 3 - ... if I had let's say 10 points to spend).
I was able to solve it for this subgraph by adding either as neighbour 2 to the 38, 37, ... branch or 38 to the 2, 3, ... branch.

So my list became :

g = {
  1: [2, 38],
  2: [1, 3, 38],
  3: [2, 4, 38],
  4: [3, 5, 38],
  5: [4, 6, 38],
  6: [5, 7, 8, 38],
  7: [6, 38],
  8: [6, 38],
 31: [2, 33],
 32: [2, 33],
 33: [2, 31, 32, 34],
 34: [2, 33, 35],
 35: [2, 34, 36],
 36: [2, 35, 37],
 37: [2, 36, 38],
 38: [1, 2, 37]
}

And then I was able to get everything I wanted for this part of the graph. Now I'm trying to extend my reasoning to the entire graph. But I'm failing hard due to main 2 issues :
- The 4 nodes to represent the traits with 3 ranks is working when I'm going in one direction, but if I have filled the entire branch and then I try to go back, I count the 4th node. (I could still make something in the artifact function to remove the 4th node but I don't think it's a good way to deal with it and should find a clever way to handle it.
- The trick that I used to link the first 2 branches isn't directly applicable to the whole graph. For example, following what I did, I would add 32 to the 29s neighbors since when I come from 35, 32 is accessible from 29. But, if I come from 28 and didn't added 27 to the path, then 32 is not normally reachable from 29. Then my paths became invalid.

I'm not sure if I can solve it this way, I hope you would be able to help me. Also, I have the feeling that my way of backtracking the search is not perfect. I also thought this :
As soon as I'm at the end of a branch, I would go back to the previous separation and explore from there. But as the node would have been already explored, it don't go the other way and stop there.

Finally, I'm up to any other way to handle my problem, I don't want especially to solve it using graph.
Maybe there is another way to solve it efficiently (like building the graph step by step and having a number of ranks to spend and spent them gradually while nodes are unlocked maybe ?).

Thank you in advance for your help and sorry for my English mistakes, I'm French :)

IIUC, you have an unweighted directed graph G, and you're looking for the set of all subgraphs H of G that have the following 2 properties:

  1. There is a path from the given origin vertex to every vertex in H (this implies that H itself is at least weakly connected)
  2. The total number (or weight) of edges is exactly some given k.

There's an easy algorithm for this, where you maintain the set of all subgraphs having total length exactly i, and on each iteration grow them to the set of all subgraphs having total length exactly i+1:

  1. Start with a single subgraph in the set S, consisting of just the origin vertex.
  2. For i from 0 to k:
    • Invariant: S contains all subgraphs of G that are weakly connected to the origin and have total length exactly i.
    • Set S' = {}.
    • For every subgraph H in S:
      • For every edge (u, v) with u in V(H) and v outside of V(H):
        • Form a subgraph H' consisting of H, plus the edge (u, v).
        • Add H' to the set S'. (If exactly this graph H' is already in S', do nothing. This will happen often. That's why it's best to use a data structure suited to sets for S' that takes care of this automatically, eg a hashtable or binary search tree.)
    • Set S = S'.

When the algorithm terminates, S will contain all subgraphs meeting requirements 1 and 2 above. In the worst case it will take m times as long as the number of different subgraphs in the output, where m is the number of edges in the graph. Note that there could be a large number of subgraphs in the output -- consider the complete graph on n vertices, and note that there are many more possible subgraphs than there are combinations of k items from n, and the latter is already big.

If you intend to take just one path, then you can try Dijkstra's Algorithm to get you the distance from the initial node to all other nodes. Then just iterate through them to return the ones with the distance you want.

If you want to try branching paths, this might become more difficult. My instinct is to run Dijkstra's on the other nodes and use some Dynamic Programming, but I'd bet there's an easier way; my computer-science-problem-solving skills are modest.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM