图的应用:骑士周游问题

1. 骑士周游问题

在一个国际象棋棋盘上,一个棋子“马”(骑士)按照“马走日”的规则,从一个格子出发,要走遍所有棋盘格恰好一次。把一个这样的走棋序列称为一次“周游”
在这里插入图片描述
采用图搜索算法是解决骑士周游问题最容易理解和编程的方案之一。解决方案分为两步:
(1)将合法走棋次序表示为一个图
(2)采用图搜索算法搜寻一个长度为(行*列-1)的路径,路径上包含每个顶点恰一次

1.1 构建骑士周游图

将棋盘和走棋步骤构建为图的思路
按照“马走日”规则的走棋步骤作为连接边,建立每一个棋盘格的所有合法走棋步骤能够到达的棋盘格关系图
在这里插入图片描述
如上图所示,骑士位于12格,他可以走的有21 ,23 ,19 ,9 ,3 ,1 ,5 ,15
合格的走棋位置函数

#合法走棋位置函数 
def getLegalMoves(x,y,bdSize):#x,y为骑士位置,bdSize为棋盘大小
    newMoves=[]
    moveOffsets=[(-1,-2),(-1,2),(-2,-1),(-2,1),(1,-2),(1,2),(2,-1),(2,1)]#马走日8个格子
    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):#bdSize为棋盘大小
    ktGraph=Graph()
    for row in range(bdSize):
        for col in range(bdSize):#遍历每个格子
            #单步合法走棋
            nodeId=posToNodeId(row,col,bdSize)
            newPositions=getLegalMoves(row,col,bdSize)
            for e in newPositions:
                nid=posToNodeId(e[0],e[1],bdSize)
                ktGraph.addEdge(nodeId,nid)#添加边及顶点
def posToNodeId(row,col,bdSize):
    return row*bdSize+col

1.2深度优先搜索(Depth First Search,DFS)

用于解决骑士周有问题的图搜索算法是深度优先搜索(Depth First Search,DFS)。深度优先搜索是沿着树的单支尽量深入向下搜索,如果到无法继续的程度还未找到问题解,就回溯上一层再搜索下一支。
深度优先搜索解决骑士周游的关键思路
如果沿着单支深入搜索到无法继续(所有合法移动都已经被走过了)且路径长度还没有达到预定值(8*8棋盘为63),那么久清楚颜色标记,返回上一层分支继续深入搜索。还需要引入一个栈来记录路径,并实施返回上一层的回溯操作。

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

下图为深度优先搜索的一个简单举例。如Figure3所示,从A点出发,首先移动到B点(Figure4),然后移动到C点(Figure5),所有合法点都走过,但仍未达到预定值,所以将C点恢复为白色,退回B点(Figure6),然后移动到D点(Figure7),E点(Figure8),F点(Figure9),C点(Figure10),搜索结束。
在这里插入图片描述

2. 骑士周游问题算法分析与改进

上述算法的性能高度依赖于棋盘的大小。对于55的棋盘,约1.5秒就可以得到一个周游路径,但对于88的棋盘,则要半个小时以上才能得到一个解。目前实现算法的复杂度为O(kn),其中n是指棋盘格数目。
算法改进——Warnsdorff算法
初始算法中nbrList直接以原始顺序来确定深度优先搜索的分支次序。新的算法仅修改了遍历下一格的次序:将u的合法移动目标棋盘格排序为具有最少合法移动目标的格子优先搜索。

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]

采用先验知识来改进算法性能的做法称为“启发式规则(heuristic)
启发式规则经常用于人工智能领域,可以有效地减小搜索范围,更快达到目标等等。如棋类程序算法会余弦存入棋谱、布阵口诀、高手习惯等“启发式规则”,能够在最短时间内从海量的棋局落子点搜索树中定位最佳落子。

3.通用的深度优先搜索

骑士周游问题是一种特殊的对图进行深度优先搜索。其目的是建立一个没有分支的最深的深度优先树,表现为一条线性的包含所有节点的退化树
一般的深度优先搜索目标是在图上进行尽量深的搜索,连接尽量多的顶点,必要时可进行分支(创建树)。有时候深度优先搜索会创建多棵树,称为“深度优先森林”。
深度优先搜索同样要用到顶点的“前驱”属性来构建树或森林。另外要设置“发现时间”和“结束时间”属性,前者是在第几步访问到这个顶点(设置灰色),后者是在第几步完成了此顶点探索(设置黑色)
带有DFS算法的图实现为Graph的子类:
顶点Vertex增加了成员Discovery(发现时间)及Finish(结束时间)
图Graph增加了成员time用于记录算法执行的步骤数目。

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('color')
            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)

下图为通用深度优先搜索的一个简单示例。从顶点A开始(Figure14),A的发现时间为1,然后到达B顶点,B的发现时间为2,之后到达C顶点,C的发现时间为3,由于C顶点之后无法继续探索,C顶点设为黑色,结束时间为4,然后退回B顶点,移动到D顶点,D的发现时间为5,之后到E点,发现时间为6,到F点,发现时间为7,由于F点之后无法继续探索,所以F点设为黑色,结束时间为8,退回E点,E点的结束时间为9,退回D点,D点的结束时间为10,然后退回B点,结束时间为11,最后退回A点,结束时间为12.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
DFS构建的树,其顶点的“发现时间”和“结束时间”属性具有类似括号的性质。即一个顶点的“发现时间”总小于所有子顶点的“发现时间”,而“结束时间”则大于所有子顶点“结束时间”,比子顶点更早被发现,更晚结束探索

通用的深度优先搜索算法分析

DFS运行时间同样包括了两方面:
dfs函数中油两个循环,每个都是|V|次,所以是O(|V|),而dfsvisit函数中的循环则是对当前顶点所连接的顶点进行,而且仅有在顶点为白色的情况下才进行递归调用,所以对每条边来说只会运行一步,所以是O(|E|)
DFS总的时间复杂度和BFS一样,都是O(|V|+|E|)

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值