1

I am attempting to find any Hamiltonian paths in a given grid, which contains obstacles at various nodes. My issue is that my code has been running for days now and has yet to come to an end. While this problem is in the NP-Complete region, from what I am seeing I am not sure that a lack of enough time is my issue.

My approach has been, in python, to use recursion to perform a Depth First Search through all of the possible orders of left, right, up, and down movements that could be made through the grid. I have researched other methods to Hamiltonian path problems, but they were more complicated for what I have done and I did not think a small grid would need them

The following is the grid I am searching. 0 is an open node, 1 is an obstacle, and S is the start.

[0,0,0,0,0,0,0,0,0,0,0,0,0]
[0,0,0,0,0,0,1,0,0,0,0,0,0]
[0,0,0,0,0,0,0,0,0,0,0,0,0]
[0,0,0,1,0,0,1,0,1,0,0,0,0]
[0,0,0,0,0,0,0,0,0,0,0,0,S]
[0,0,0,0,0,0,0,0,0,0,0,0,0]
[0,0,0,0,0,0,1,0,0,0,0,0,0]
[0,0,0,0,0,0,0,0,0,0,0,0,0]
[0,0,0,0,0,0,0,0,0,0,0,0,0]

Here is an example output of the running function's current grid, with the 1s now also representing visited nodes.

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1]
[1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0]
[1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0]
[1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1]

However, even at approximately 50000 steps per second, the code never seems to stop examining the bottom right corner. For example, the two 0s at nodes (3,1) and (3,2) have never been reached.

So this leaves me with some questions: Is this just a standard symptom of NP-Hard, even though I am only attempting a 13x9 grid? Am I reaching a python recursion limit, causing my code to endlessly rerun the same DFS branches? Or is there something else I am missing?

This is a simplified version of my search method:

#examines options of steps at current marker cell and iterates path attempts
def tryStep():
    global path #list of grid movements
    global marker #current cell in examination

    set marker to traveled        
    if whole grid is Traveled:
        print path data
        end program

    #map is incomplete, advance a step
    else:
     if can move Up:
       repeat tryStep()
     if can move left:
       repeat tryStep()
     if can move down:
       repeat tryStep()
     if can move right:
       repeat tryStep()

    #Failure condition reached, must backup a step
    set marker cell to untraveled
    if length of path is 0:
        print 'no path exists'
        end program
    last = path.pop()
    if last == "up":
        move marker cell down

    if last == "left":
        move marker cell right

    if last == "down":
        move marker cell up

    if last == "right":
        move marker cell left
    return

Therefore, the code should iterate through all possible paths through the grid, until one forms a Hamiltonian path. For reference here is my actual code I am running:

    '''
Created on Aug 30, 2014

@author: Owner
'''
#Search given grid for hamiltonian path
import datetime

#takes grid cord and returns value
def getVal(x,y):
    global mapWidth
    global mapHeight
    global mapOccupancy

    if (((x < mapWidth) and (x >-1)) and (y < mapHeight and y >-1)):
        return mapOccupancy[y][x]
    else:
        #print "cell not in map"
        return 1
    return

#sets given coord cell value to visted
def travVal(x,y):
    global mapWidth
    global mapHeight
    global mapOccupancy
    if (((x < mapWidth) and (x >-1)) and ((y < mapHeight) and (y >-1)))== True:
        mapOccupancy[y][x] = 1
    else:
        #print "cell not in map"
        return 1
    return

#sets given coord cell value to open    
def clearVal(x,y):
    if (((x < mapWidth) and (x > -1)) and ((y < mapHeight) and (y > -1)))==True:
        mapOccupancy[y][x] = 0
    else:
        #print "cell not in map"
        return 1
    return

#checks if entire map has been traveled 
def mapTraveled():
    isFull = False
    endLoop= False
    complete = True
    for row in mapOccupancy:
        if endLoop ==True:
            isFull = False
            complete = False
            break
        for cell in row:
            if cell == 0:
                complete = False
                endLoop = True
                break
    if complete == True:
        isFull = True
    return isFull


 #examines options of steps at current marker cell and iterates path attempts
def tryStep():
    global path
    global marker
    global goalCell
    global timeEnd
    global timeStart
    global printCount
    travVal(marker[0],marker[1])
    printCount += 1

    #only print current map Occupancy every 100000 steps
    if printCount >= 100000:
        printCount = 0
        print ''
        print marker
        for row in mapOccupancy:
            print row

    if mapTraveled():
        print 'map complete'
        print "path found"
        print marker
        print path
        for row in mapOccupancy:
            print row
        print timeStart
        timeEnd= datetime.datetime.now()
        print timeEnd
        while True:
            a=5

    #if map is incomplete, advance a step
    else:

        #Upwards
        if getVal(marker[0],marker[1]-1) == 0:
            marker = [marker[0],marker[1]-1]
            #print "step: " + str(marker[0])+' '+ str(marker[1])
            path.append('up')
            tryStep()
        #left wards
        if getVal(marker[0]-1,marker[1]) == 0:
            marker = [marker[0]-1,marker[1]]
            #print "step: " + str(marker[0])+' '+ str(marker[1])
            path.append('left')
            tryStep()

        # down wards
        if getVal(marker[0],marker[1]+1) == 0:
            marker = [marker[0],marker[1]+1]
            #print "step: " + str(marker[0])+' '+ str(marker[1])
            path.append('down')
            tryStep()

        #right wards
        if getVal(marker[0]+1,marker[1]) == 0:
            marker = [marker[0]+1,marker[1]]
           # print "step: " + str(marker[0])+' '+ str(marker[1])
            path.append('right')
            tryStep()

    #Failure condition reached, must backup steps
    clearVal(m[0],m[1])

    last = path.pop()
    #print 'backing a step from:'
    #print last
    if last == "up":
        marker = [marker[0],marker[1]+1]

    if last == "left":
        marker = [marker[0]+1,marker[1]]

    if last == "down":
        marker = [marker[0],marker[1]-1]

    if last == "right":
        marker = [marker[0]-1,marker[1]]


    return


if __name__ == '__main__':
    global timeStart
    timeStart = datetime.datetime.now()
    print timeStart

    global timeEnd
    timeEnd= datetime.datetime.now()

    global printCount
    printCount = 0


    global mapHeight
    mapHeight = 9

    global  mapWidth 
    mapWidth =13

    #occupancy grid setup
    r0= [0,0,0,0,0,0,0,0,0,0,0,0,0]
    r1= [0,0,0,0,0,0,1,0,0,0,0,0,0]
    r2= [0,0,0,0,0,0,0,0,0,0,0,0,0]
    r3= [0,0,0,1,0,0, 1 ,0,1,0,0,0,0]
    r4= [0,0,0,0,0,0,0,0,0,0,0,0, 0]
    r5= [0,0,0,0,0,0,0,0,0,0,0,0,0]
    r6= [0,0,0,0,0,0,1,0,0,0,0,0,0]
    r7= [0,0,0,0,0,0,0,0,0,0,0,0,0]
    r8= [0,0,0,0,0,0,0,0,0,0,0,0,0]



    global  mapOccupancy
    mapOccupancy = [r0,r1,r2,r3,r4,r5,r6,r7,r8]

    #record of current trail attempt
    global path
    path = []
    #marker for iterating through grid
    global marker
    start = [12,4]
    #start =[0,2]
    m = start
    global goalCell
    goalCell = [6,3]

    print marker

    tryStep()

    #no path avalible if this point is reached
    print'destination is unreachable'
    print 'last path: '
    for row in mapOccupancy:
        print row
    print path
    print m
    print mapOccupancy
    print timeStart
    timeEnd= datetime.datetime.now()
    print timeEnd
  • If you hit the recursion limit you get an Exception, so unless you quietly ignore these this shouldn't be the case here. – uselpa Sep 07 '14 at 21:21
  • 2^(13*9) seems to be a very rough but a reasonable estimate on how many different possible paths there are. It's a very large number. – wookie919 Sep 07 '14 at 21:27

1 Answers1

0

Quick and very rough calculation, giving an estimation of the problem complexity.

You have total 13*9 == 117 nodes, 5 of them are walls, which leaves 112 open nodes. Each open node has from 2 to 4 neighbours, let's say on average they have 3 neighbours (it's actually underestimation). Which means that the number of paths you should check is about 3^112 ≈ 2.7*10^53.

Of course sometimes you'll cut your search earlier, but estimation remains: the amount of paths is huge, so it makes no sense to check them all using brute force backtracking.

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • Thanks, that is good to know. Is there any possible estimates for if I work on adding pruning methods, such as searching for isolated cells? – user3412893 Sep 08 '14 at 15:36
  • @user3412893 Maybe there are, but it's a different question which actually should be asked not on Stack Overflow but rather on http://programmers.stackexchange.com. – Anton Savin Sep 08 '14 at 21:50