在棋盘格里,马走日,遍历所有网格点,找到每个网格都走过,且只有一次的路径。
算法实现:
用于解决骑士周游问题的图搜索算法是深度优先搜索(DFS),该算法是逐层建立搜索树,沿着树的单支尽量深入的向下搜索。连接尽量多的顶点,必要时可以进行分支。
深度优先搜索同样要用到顶点的“前驱”属性,来构建树或森林。另外需要设置“发现时间”和“结束时间”属性。
发现时间是在第几步访问到了这个顶点(设置灰色);结束时间是在第几步完成了此顶点的探索(设置黑色)。
通用的深度优先搜索算法代码:
# BFS采用队列存储待访问顶点
# DFS通过递归调用,隐式使用了栈
from pythonds.graphs import Graph
class DFSGraph(Graph):
def __init__(self):
super().__init__()
self.time = 0
def dfs(self):
for aVertex in self:
aVertex.setColor('white') # 颜色初始化
aVertex.setPred(-1)
for aVertex in self:
if aVertex.getColor() == 'white':
self.dfsvisit(aVertex) # 如果还有未包括的顶点,则建森林
def dfsvisit(self,startVertex):
startVertex.setColor('gray')
self.time += 1 # 算法的步数
startVertex.setDiscovery(self.time)
for nextVertex in startVertex.getConnections():
if nextVertex.getColor() == 'white':
nextVertex.setPred(startVertex)
self.dfsvisit(nextVertex) # 深度优先递归访问
startVertex.setColor('black') # 节点探索完毕,设为黑色
self.time += 1
startVertex.setFinish(self.time)
算法分析:
(1)DFS构建的树,其顶点的发现时间和结束时间属性,具有类似括号的性质。即一个顶点的发现时间总小于所有子顶点的发现时间,而结束时间则大于所有子顶点结束时间,比子顶点更早被发现,更晚被结束探索。
(2)DFS运行时间同样也包括了两方面:dfs函数中有两个循环,每个都是|V|次,所以是O(|V|),而dfsvisit函数中的循环则是对当前顶点所连接的顶点进行,而且仅有在顶点为白色的情况下才进行递归调用,所以对每条边来说只会运行一步,所以是O(|E|),加起来就是和BFS一样的O(|V|+|E|)。
骑士周游问题的算法实现:
def genLegalMoves(x,y,bdSize):
newMoves = []
moveOffsets = [(-1,-2),(-1,2),(-2,-1),(-2,1), # 马走日8个格子
(1,-2),(1,2),(2,-1),(2,1)]
for i in moveOffsets:
newX = x + i[0]
newY = y + i[1]
if legalCoord(newX,bdSize) and legalCoord(newY,bdSize):
newMoves.append((newX,newY))
return newMoves
def legalCoord(x,bdSize):
if x >= 0 and x < bdSize: # 确认不会走出棋盘
return True
else:
return False
def knightGraph(bdSize):
ktGraph = Graph()
for row in range(bdSize): # 遍历每个格子
for col in range(bdSize):
nodeId = posToNodeId(row,col,bdSize)
newPositions = genLegalMoves(row,col,bdSize) # 单步合法走棋
for e in newPositions:
nid = posToNodeId(e[0],e[1],bdSize)
ktGraph.addEdge(nodeId,nid) # 添加边及顶点
return ktGraph
def posToNodeId(row,col,bdSize):
return row*bdSize + col
def knightTour(n,path,u,limit): # n层次,path路径,u当前顶点,limit搜索总深度
u.setColor('gray')
path.append(u) # 当前顶点加入路径
if n < limit:
nbrList = list(u.getConnections()) # 对所有合法移动逐一深入
i = 0
done = False
while i < len(nbrList) and not done:
if nbrList[i].getColor() == 'white': # 选择白色未经过的顶点深入
done = knightTour(n+1,path,nbrList[i],limit) # 层次加1,递归深入
i = i + 1
if not done: # 无法完成总深度,回溯,试本层下一个节点
path.pop()
u.setColor('white')
else:
done = True
return done
算法分析:
其性能高度依赖于棋盘大小,目前实现的算法,其复杂度为O(k^n),其中,n是棋盘格数目。这是一个指数时间复杂度的算法,其搜索过程表现为一个层次为n的树。
算法改进
修改遍历下一格的次序,将u的合法移动目标棋盘格排序为:具有最少合法移动目标的格子优先搜索。采用先验知识来改进算法性能的做法,称作为“启发式规则heuristic”。启发式规则经常用于人工智能领域;可以有效地减小搜索范围、更快达到目标等;
python代码实现:
# 骑士周游算法改进代码:
def orderByAvail(n):
resList = []
for v in n.getConnections():
if v.getColor() == 'white':
c = 0
for w in v.getConnections():
if w.getColor() == 'white':
c = c + 1
resList.append((c,v))
resList.sort(key=lambda x: x[0])
return [y[1] for y in resList]
两者区别:
上述骑士周游问题的算法,其特点是每个顶点仅可访问一次;而通用深度优先算法是允许顶点被重复访问,更加符合寻优实际情形,可作为其它图算法的基础。