15 Jul 2013

Hexagon Network六边形网格距离计算

收藏到CSDN网摘


六边形网格(有时也叫蜂窝网络)在实际生活中有很多应用,例如无线基站定位,蜂窝(名字的由来),战棋游戏中人物的移动等.对于六边形网格的定位方法有很多,几何计算应该是最简单的.不过对于想要直观显示,计算的方式并不能说是最好的方法.曾经看到一个题目,对于一个从中心螺旋生长的蜂窝网格,求任意给定的2个cell的距离(看下图).此题如果是正方形网格,一个很简单的BFS(广度优先搜素)即可解决.但是对于蜂窝网格,如果不用几何的办法,最好是先定义网格cell,然后构造整个network,最后对于给定的cell按照广搜找到路径即可.




下面是python实现的六边形网格构造及寻路算法,可以给出任意指定的2个网格cell的最短距离与路径.
思路简单描述一下三个类(为了与python的list下标对应,所有的下标操作都从0开始):

cell类: 表示单个网格,nbs是它的邻居,从正上方开始顺时针分别是0-5

net类: 表示整个网络,按照给定的数字,可以生成包含该数字的最小网络(从中心向外一圈圈辐射).

layer是net类中比较重要的一个数据,保存了每一层的属性: [最小数字a,最大数字b,每边cell个数n].其中对照图可以发现,每一圈都可以看做是一个六边形,有六个顶点,六条边.
这个六边形的边上会有cell(从第2层开始),因此知道了层数L,该层的网格就是从a到b一共(b-a+1)=(n*6+6)=(L*6)个(第0层比较特殊,只有一个1).
边的编号与cell的邻居编号略有差别,从第0号顶点坐标开始编号,因此边与顶点的对应关系是:
e0: v5-v0; e1: v0-v1; ... e5: v4-v5

node类: 表示搜索路径时的节点,类似于单链表,用来从出口回溯到入口.每个节点都保存了父节点信息.

bfs函数: 就是最简单的广度优先搜素,如果某cell的邻居中有未访问过的cell,加入搜索队列,直到找到出口为止.由于net类初始化时使用了2个网格的最大下标,因此可以保证肯定能找到至少一条路径.

实现代码:
class cell(object):
    # hexagon cell
    def __init__(self,ind,neighbours=None):
        self.ind = ind
        if neighbours and len(neighbours)==6:
            self.nbs = neighbours
        else:
            self.nbs = [None]*6

    # start from top neighbour, clock wise direction
    def setnb(self,n,nb):
        self.nbs[n] = nb

    def __repr__(self):
        '''
            0 1 0
            6 * 2
            5 * 3
            0 4 0
        '''
        rpd = [x and (isinstance(x,cell) and x.ind or x) or 0 for x in self.nbs]
        ind = [[0,rpd[0],0],
               [rpd[5],self.ind,rpd[1]],
               [rpd[4],self.ind,rpd[2]],
               [0,rpd[3],0]]
        ma = max([max(x) for x in ind]) # max number
        return '\n[\n'+'\n'.join([' '.join([('{:'+str(len(str(ma)))+',d}').format(d) for d in x]) for x in ind])+'\n]\n'        
        
class net(object):
    # hexagon net start from centre
    def __init__(self,num):
        # each layer(like round circle): min_num,max_num,nums,edge_length
        # if consider each layer a bigger hexagon, then the vertices are same
        # as the cell definition. However, the edge is introduced for layers
        # (the layer id starts from 0), means the cell between vertices.
        # edge0: v5-v0 (up left); edge1: v0-v1 (up right)
        # edge2: v1-v2 (right); edge3: v2-v3 (down right)
        # edge4: v3-v4 (down left); edge5: v4-v5(left)
        self.layer = [(1,1,1,0)]
        # find the max number contains num
        i = 0
        self.max = 1
        new_min, new_max, new_len, new_num = 0,0,0,0
        while self.max<num:
            i += 1
            new_num = 6*i
            self.max += new_num
            new_min = self.layer[-1][1]+1
            new_max = new_min+new_num-1
            new_len = ((i-1)>0 and [i-1] or [0])[0]
            self.layer.append((new_min,new_max,new_num,new_len))
        # create the cells
        self.cells = [cell(i+1) for i in range(self.max)]
        self.setinfo()

    def link(self,c1,c2,a,b):
        # set neighbours of 2 cells
        # c1's a-th neighbour is c2
        # c2's b-th neighbour is c1
        c1.setnb(a,c2)
        c2.setnb(b,c1)
        
    def setinfo(self):
        # set up neighbours for all cell in the network layer by layer
        # only set to the inner one using double link, start from the edge0
        # layer id start from the second layer
        for lid in range(1,len(self.layer)):
            cur_ind = self.layer[lid][0] # start index
            # edge e0, only set 3,2,1
            for i in range(self.layer[lid][-1]): # loop number on the edge
                if cur_ind==self.layer[lid][0]: # the start number
                    self.link(self.cells[cur_ind-1],self.cells[self.layer[lid-1][1]-1],3,0)
                    self.link(self.cells[cur_ind-1],self.cells[self.layer[lid-1][0]-1],2,5)
                else: # the others
                    self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-1-1],3,0)
                    self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-1],2,5)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],1,4)
                cur_ind += 1
            # vertex v0, only set 3,2
            self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-1-1],3,0)
            self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],2,5)
            cur_ind += 1
            # edge e1, only set 4,3,2
            for i in range(self.layer[lid][-1]):
                self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-2-1],4,1)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-1-1],3,0)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],2,5)
                cur_ind += 1
            # vertex v1, only set 4,3
            self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-2-1],4,1)
            self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],3,0)
            cur_ind += 1
            # edge e2, only set 5,4,3
            for i in range(self.layer[lid][-1]):
                self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-3-1],5,2)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-2-1],4,1)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],3,0)
                cur_ind += 1
            # vertex v2, only set 5,4
            self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-3-1],5,2)
            self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],4,1)
            cur_ind += 1
            # edge e3, only set 0,5,4
            for i in range(self.layer[lid][-1]):
                self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-4-1],0,3)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-3-1],5,2)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],4,1)
                cur_ind += 1
            # vertex v3, only set 0,5
            self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-4-1],0,3)
            self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],5,2)
            cur_ind += 1
            # edge e4, only set 1,0,5
            for i in range(self.layer[lid][-1]):
                self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-5-1],1,4)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-4-1],0,3)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],5,2)
                cur_ind += 1
            # vertex v4, only set 1,0
            self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-5-1],1,4)
            self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],0,3)
            cur_ind += 1
            # edge e5, only set 2,1,0
            for i in range(self.layer[lid][-1]):
                self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-6-1],2,5)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-5-1],1,4)
                self.link(self.cells[cur_ind-1],self.cells[cur_ind+1-1],0,3)
                cur_ind += 1
            # vertex v5, only set 2,1
            self.link(self.cells[cur_ind-1],self.cells[cur_ind-(lid-1)*6-6-1],2,5)
            self.link(self.cells[cur_ind-1],self.cells[self.layer[lid][0]-1],1,4)
            cur_ind += 1                

    def get_cells(self):
        return self.cells
    
    def get_layer(self):
        return self.layer

    def __len__(self):
        return len(self.cells)

class node(object):
    # linked list to save search path
    def __init__(self,c,prev):
        self.p = prev
        self.n = c

    def __repr__(self):
        return 'Node:'+str(self.n)+'Prev:'+str(self.p)

def bfs(net,a,b):
    # breadth-first seach to find the shortedst path from one to another
    cells = net.get_cells()
    visited = []
    path = [node(cells[a-1],None)]
    cur = path.pop(0)
    while cur.n.ind!=b:
        visited.append(cur.n.ind)
        path.extend([node(x,cur) for x in cur.n.nbs if x and x.ind not in visited])
        cur = path.pop(0)
    # back to start
    num = 0
    path = []
    while cur.p:
        path.append(str(cur.n.ind))
        path.append('->')
        num += 1
        cur = cur.p
    path.append(str(cur.n.ind))
    print ' '.join(path[::-1]),'(',num,')'
    return num
   
# test cell
##a = cell(1)
##print a
##c = cell(10)
##a.setnb(0,c)
##print a
##b = cell(1,[2,3,4,5,6,7])
##print b
##b.setnb(2,20)
##print b  
  
# test net
##n = net(8)
##print len(n)
##print n.get_cells()
##print n.get_layer()

# test bfs function
##n = net(8)
##bfs(n,5,11)

def checkio(data):
    a, b = data
    n = net(max(a,b))
    return bfs(n,a,b)

#These "asserts" using only for self-checking and not necessary for auto-testing
if __name__ == '__main__':
    assert checkio([2, 9]) == 1, "First"
    assert checkio([9, 2]) == 1, "Reverse First"
    assert checkio([6, 19]) == 2, "Second, short way"
    assert checkio([5, 11]) == 3, "Third"
    assert checkio([13, 15]) == 2, "Fourth, One row"
    assert checkio([11, 17]) == 4, "Fifth, One more test"

测试结果:
>>> ================================ RESTART ================================
>>> 
2 -> 9 ( 1 )
9 -> 2 ( 1 )
6 -> 7 -> 19 ( 2 )
5 -> 1 -> 3 -> 11 ( 3 )
13 -> 14 -> 15 ( 2 )
11 -> 3 -> 1 -> 6 -> 17 ( 4 )
>>>

No comments :

Post a Comment