深度优先算法(DFS)的python实现及骑士周游问题解析

背景: 骑士周游问题

在棋盘格里,马走日,遍历所有网格点,找到每个网格都走过,且只有一次的路径。
在这里插入图片描述
算法实现:
用于解决骑士周游问题的图搜索算法是深度优先搜索(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]

两者区别:
上述骑士周游问题的算法,其特点是每个顶点仅可访问一次;而通用深度优先算法是允许顶点被重复访问,更加符合寻优实际情形,可作为其它图算法的基础。

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨猪起飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值