六边形网格(有时也叫蜂窝网络)在实际生活中有很多应用,例如无线基站定位,蜂窝(名字的由来),战棋游戏中人物的移动等.对于六边形网格的定位方法有很多,几何计算应该是最简单的.不过对于想要直观显示,计算的方式并不能说是最好的方法.曾经看到一个题目,对于一个从中心螺旋生长的蜂窝网格,求任意给定的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