算法学习-深度优先搜索

概念介绍

本博客在学习北京大学陈斌老师《数据结构与算法》MOOC课程中总结反思形成。

骑士周游问题:

8 × 8 8 \times 8 8×8的国际象棋棋盘上按照"马"走日的规则,从一个格子出发,要走遍所有棋盘格所有恰好一次(周游)。

骑士周游问题的解决步骤:

  1. 构建骑士周游图;

  2. 采用“深度优先搜索 DFS”,来搜寻有效的骑士周游路径;

具体来讲,首先将合法走棋次序表示为一个图;然后采用图搜索算法搜寻一个长度为(行×列-1)的路径,路径上包含每个顶点恰一次

深度优先搜索算法

深度优先搜索是沿着树的单支尽量深入向下搜索。

如果到无法继续的程度还未找到问题解就回溯上一层再搜索下一支

代码解析

建立骑士周游图

建立骑士周游图步骤

  1. 将棋盘格作为顶点;

  2. 按照“马走日”规则的走棋步骤作为连接边;

  3. 建立每一个棋盘格的所有合法走棋步骤能够到达的棋盘格关系图。

from pythonds.graphs.adjGraph import Graph

def genLegalMoves(x, y, bdsize):
    newMoves = []
    # 马走日8个格子
    movesOffsets = [(-1, -2), (-1, 2), (-2, -1), (-2, 1),
                    (1, -2), (1, 2), (2, -1), (2, 1)]
    for i in movesOffsets:
        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, bdszie):
    if x >= 0 and x < bdszie:
        return True
    else:
        return False

def posToNodeId(row, col, bdsize):
    return row * bdsize + col

# 构建走棋关系图
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

针对骑士周游问题的DFS

目前实现的算法,其复杂度为 O ( k n ) O(k^n) O(kn)

# DFS 算法
# n,层次;path,路径;u,当前顶点;limit,搜索总深度
def knightTour(n, path, u, limit):
    u.setColor('gray')
    # 当前顶点加入路径
    path.append(u)
    if n < limit:
        # 对所有合法移动逐一深入
        nbrList = list(u.getConnections())
        # nbrList = orderByAvail(u)
        i = 0
        done = False
        while i < len(nbrList) and not done:
            # 选择白色未经过的顶点深入
            if nbrList[i].getColor() == 'white':
                # 层次+1 ,递归深入
                done = knightTour(n + 1, path, nbrList[i], limit)
            i = i + 1
            # 都无法完成总深度,回溯,尝试本层下一个顶点
        if not done:
            path.pop()
            u.setColor('white')
    else:
        done = True
    return done

image-20211008235847702

DFS改进:Warnsdorff算法

指数时间复杂度算法可以在实际性能上加以大幅度改进:

  • 对nbrList的灵巧构造,以特定方式排列顶点访问次序
  • 采用先验的知识来改进算法性能的做法,称作为“启发式规则heuristic”,可以有效地减小搜索范围、更快达到目标等等
# 骑士周游算法改进
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]

通用的深度优先搜索

  1. 通用的深度优先搜索目标是在图上进行尽量深的搜索,连接尽量多的顶点,必要时可以进行分支(创建树)

  2. 另外要设置“发现时间”和“结束时间”属性;体现为(self.dtime和self.ftime,可以在pythonds.graphs.adjGraph模块查看)
    • 前者是在第几步访问到这个顶点(设置灰色)
    • 后者是在第几步完成此顶点探索(设置黑色)

# 通用的深度优先搜索算法
# BFS采用队列存储待访问顶点
# DFS则是通过递归调用,隐式使用了栈
class DFSGraph(Graph):
    def __init__(self):
        # 细节:supre()
        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)

image-20211008191607051

调用方式

if __name__ == '__main__':
    # 棋盘格行列数
    NN = 6
    kgtgraph = knightGraph(NN)

    resPath = []
    start = kgtgraph.getVertex(4)
    knightTour(n=0, path=resPath, u=start, limit=NN * NN - 1)

    for i in range(len(resPath)):  # 输出路径
        if i != len(resPath) - 1:
            print(resPath[i].id, end=' ->')
        else:
            print(resPath[i].id)

DFS算法分析

[DFS可视化代码参考][https://blog.csdn.net/weixin_42353399/article/details/103380708]

  • dfs函数中有两个循环,每个都是|V|次,所以是$O(|V|) $;

  • dfsvisit函数中的循环则是对当前顶点所连接的顶点进行,而且仅有在顶点为白色的情况下才进行递归调用,所以对每条边来说只会运行一步,所以是 O ( ∣ E ∣ ) ; O(|E|); O(E)

  • DFS算法复杂度加起来就是和BFS一样的 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

儒雅的钓翁

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

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

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

打赏作者

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

抵扣说明:

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

余额充值