Search code examples
phppythonpath-findinga-star

A* Search - least number of hops?


I'm trying to create an A* pathfinding algorithm, however I'm having a little trouble getting off the ground with it. A little background:

I am by no means versed in pathfinding algorithms, however I did touch upon this subject a couple years ago (I've since forgotten everything I've learned). I play EVE Online, which is an online game about internet spaceships. The developers release data dumps for static information (items in game, solar system locations, etc). I am trying to find the shortest route from solar system A to solar system B.

Take a look at this map: http://evemaps.dotlan.net/map/UUA-F4 That is one region in the game, with each node being a system. I would like to compute the shortest distance between any two of those systems.

My issue: everything that I've read online about A* is talking about incorporating the distance between two nodes (for example, the distance between two cities) to help compute the shortest path. That doesn't help my case, as I'm more interested in the number of hops (node 1 > node 2 > node 3) rather than the distance between those hops. I do not know how to modify the A* algorithm to incorporate this.

The information that I have in the database: A list of all systems and their neighbors (so, systemX links with systemA and systemB) x, y, and z coordinates of all systems in a 3D grid

If anyone can point me in the right direction, that would be great. I'm looking to use this in PHP, however I've also started to work in Python a bit so that'll work too.

Example data can be provided on request if needed.

EDIT

As some have pointed out, the 'cost' associated with each jump would simply be 1. However, with A*, you also need a heuristic that estimates the distance from the current node to the target node. I'm not exactly sure how to go about determining this value, as I'm not sure of the remaining hops. As stated, I do have the 3D coordinates (x,y,z) for every node, but I'm not sure if this could give any insight as the physical distance between each node is not of concern. I do know that no path spans more than 99 hops.

EDIT 2

MySQL data for the example region.

to -> from data: http://pastebin.com/gTuwdr7h

System information (x,y,z cooridinates if needed): http://pastebin.com/Vz3FD3Kz


Solution

  • Take the upper part of the linked graph:

    enter image description here

    Assume that the lines represent 2 way (i.e., you can go to or from any linked node) and that the black lines are a 'cost' of 1 and the red lines are a 'cost' of 2.

    That structure can be represented by the following Python data structure:

    graph = {'Q-KCK3': {'3C-261':1, 'L-SDU7':1},
             'L-SDU7': {'Q-KCK3':1, '3C-261':1,'4-IPWK':1},
             '3C-261': {'4-IPWK':1,'9K-VDI':1,'L-SDU7':1,'U8MM-3':1},
             'U8MM-3': {'9K-VDI':1,'3C-261':1, '9K-VDI':1, 'Q8T-MC':2},
             'Q8T-MC': {'U8MM-3':2, 'H55-2R':1, 'VM-QFU':2},
             'H55-2R': {'Q8T-MC':1, '9XI-OX':1, 'A3-PAT':1, 'P6-DBM':1},
             'P6-DBM': {'A3-PAT':1, 'H55-2R':1},
             'A3-PAT': {'P6-DBM':1, 'H55-2R':1, '9XI-OX':1,'YRZ-E4':1},
             'YRZ-E4': {'A3-PAT':1}, 
             'VM-QFU': {'IEZW-V':1, 'PU-128':2},
             'IEZW-V': {'VM-QFU':1, 'PU-128':1, 'B-DX09':1},
             'PU-128': {'VM-QFU':1, 'B-DX09':1, 'IEZW-V':1},
             'B-DX09': {'IEZW-V':1, 'PU-128':1, '1TS-WIN':1},
             '1TS-WIN': {'B-DX09':1, '16-31U':1},
             '16-31U': {'1TS-WIN':1}
            }
    

    Now you can define a recursive function to navigate that data:

    def find_all_paths(graph, start, end, path=[]):
            path = path + [start]
            if start == end:
                return [path]
            if start not in graph:
                return []
            paths = []
            for node in graph[start]:
                if node not in path:
                    newpaths = find_all_paths(graph, node, end, path)
                    for newpath in newpaths:
                        paths.append(newpath)
            return paths       
    
    def min_path(graph, start, end):
        paths=find_all_paths(graph,start,end)
        mt=10**99
        mpath=[]
        print '\tAll paths:',paths
        for path in paths:
            t=sum(graph[i][j] for i,j in zip(path,path[1::]))
            print '\t\tevaluating:',path, t
            if t<mt: 
                mt=t
                mpath=path
    
        e1='\n'.join('{}->{}:{}'.format(i,j,graph[i][j]) for i,j in zip(mpath,mpath[1::]))
        e2=str(sum(graph[i][j] for i,j in zip(mpath,mpath[1::])))
        print 'Best path: '+e1+'   Total: '+e2+'\n'  
    

    Now demo:

    min_path(graph,'Q-KCK3','A3-PAT')
    min_path(graph,'Q-KCK3','16-31U')
    

    Prints:

        All paths: [['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'H55-2R', 'P6-DBM', 'A3-PAT'], ['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'H55-2R', 'A3-PAT'], ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'H55-2R', 'P6-DBM', 'A3-PAT'], ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'H55-2R', 'A3-PAT']]
            evaluating: ['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'H55-2R', 'P6-DBM', 'A3-PAT'] 7
            evaluating: ['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'H55-2R', 'A3-PAT'] 6
            evaluating: ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'H55-2R', 'P6-DBM', 'A3-PAT'] 8
            evaluating: ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'H55-2R', 'A3-PAT'] 7
    Best path: Q-KCK3->3C-261:1
    3C-261->U8MM-3:1
    U8MM-3->Q8T-MC:2
    Q8T-MC->H55-2R:1
    H55-2R->A3-PAT:1   Total: 6
    
        All paths: [['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'IEZW-V', 'B-DX09', '1TS-WIN', '16-31U'], ['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'IEZW-V', 'PU-128', 'B-DX09', '1TS-WIN', '16-31U'], ['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'PU-128', 'B-DX09', '1TS-WIN', '16-31U'], ['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'PU-128', 'IEZW-V', 'B-DX09', '1TS-WIN', '16-31U'], ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'IEZW-V', 'B-DX09', '1TS-WIN', '16-31U'], ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'IEZW-V', 'PU-128', 'B-DX09', '1TS-WIN', '16-31U'], ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'PU-128', 'B-DX09', '1TS-WIN', '16-31U'], ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'PU-128', 'IEZW-V', 'B-DX09', '1TS-WIN', '16-31U']]
            evaluating: ['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'IEZW-V', 'B-DX09', '1TS-WIN', '16-31U'] 10
            evaluating: ['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'IEZW-V', 'PU-128', 'B-DX09', '1TS-WIN', '16-31U'] 11
            evaluating: ['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'PU-128', 'B-DX09', '1TS-WIN', '16-31U'] 11
            evaluating: ['Q-KCK3', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'PU-128', 'IEZW-V', 'B-DX09', '1TS-WIN', '16-31U'] 12
            evaluating: ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'IEZW-V', 'B-DX09', '1TS-WIN', '16-31U'] 11
            evaluating: ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'IEZW-V', 'PU-128', 'B-DX09', '1TS-WIN', '16-31U'] 12
            evaluating: ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'PU-128', 'B-DX09', '1TS-WIN', '16-31U'] 12
            evaluating: ['Q-KCK3', 'L-SDU7', '3C-261', 'U8MM-3', 'Q8T-MC', 'VM-QFU', 'PU-128', 'IEZW-V', 'B-DX09', '1TS-WIN', '16-31U'] 13
    Best path: Q-KCK3->3C-261:1
    3C-261->U8MM-3:1
    U8MM-3->Q8T-MC:2
    Q8T-MC->VM-QFU:2
    VM-QFU->IEZW-V:1
    IEZW-V->B-DX09:1
    B-DX09->1TS-WIN:1
    1TS-WIN->16-31U:1   Total: 10
    

    If you want the minimum number of hops, just modify min_path to return the shortest list length rather than the minimum total cost of the hops. Or, make the cost of each hop 1.

    Have a look at my previous answer regarding trains.