図-深さ優先探索
ナイト旅行の質問:
-
チェス盤の山では、「馬の散歩の日」のルールに従って、1つの正方形から始まるチェスの駒「馬」は、すべてのチェス盤を移動する必要があります。ちょうど一度、そのような一連の動きを「ツアー」と呼びます
-
グラフ検索アルゴリズムの使用は、ナイト問題を理解してプログラムするための最も簡単なソリューションの1つです。
-
解決:
-
まず、合法的な移動シーケンスがグラフとして表されます
- チェッカーボードを頂点として使用する
- 接続エッジとして「馬の散歩の日」ルールの手順に従ってください
- 各チェッカーボードのすべての合法的な動きが到達できるチェッカーボード図を確立します
-
グラフ検索アルゴリズムを使用して、長さが(row×column-1)のパスを検索します。このパスには、各頂点が1回だけ含まれます。
-
深さ優先探索の重要なアイデアは、騎士の旅を解決するためのものです
単一のブランチに沿った詳細な検索を続行できない場合(すべての法的な動きが歩いた場合)、パスの長さが所定の値に達していない場合(8×8チェス盤は63)、カラーマークはクリアされ、前のレイヤーは返された、詳細な調査を続けるためにブランチを変更する
-
前のレイヤーに戻るバックトラッキング操作の実装を容易にするために、パスを記録するスタックを導入します
-
-
騎士の旅の問題を解決するために使用されるグラフ検索アルゴリズムは、深さ優先探索です。
深さ優先探索は、ツリーの1つのブランチを可能な限り検索することです。問題の解決策を引き続き見つけることができない場合は、前のレイヤーに戻って次のブランチを検索してください。
DFSアルゴリズムは、各頂点に1回だけアクセスすることを特徴とするナイトトラベルの問題を解決するために使用されます。
別のDFSアルゴリズムはより一般的であり、頂点に繰り返しアクセスできるため、他のグラフアルゴリズムの基礎として使用できます。
def knightTour(n, path, u, limit):
"""
:param n: 层次
:param path: 路径
:param u: 当前顶点
:param limit: 搜索总深度
:return:
目前实现的算法,其复杂度为O(k^n),其中n是棋盘格数目
"""
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':
# 层次加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
上記のコードのハイライト:
1つはwhileループです
2番目は再帰呼び出しです
3つ目は、グレーと白を使用して1回の訪問のみを保証することです。
完全なコードは次のとおりです。
# coding: utf-8
# from . import graph_ccc
from GraphCode.graph_ccc import *
def genLegalMoves(x, y, bdsize):
newMoves = []
# 马走日8个格子
moveOffsets = [(-1, -2), (-1, 2), (-2, -1), (-2, 1),
(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 0 <= x < bdsize:
return True
else:
return False
# 构建走棋关系图
def knightGraph(bdsize):
ktGrapth = 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)
# 添加边和顶点
ktGrapth.addEdge(nodeId, nid)
return ktGrapth
def posToNodeId(row, col, bdsize):
"""
将坐标转化为id, row
row和col都是从0开始的
pos: (0,0)(0,1)(0,2)(0,3),(0,4)
id: 0 1 2 3 4
:param row:
:param col:
:param bdsize:
:return:
"""
return row * bdsize + col
def orderbyAvail(n):
resultList = []
for v in n.getConnections():
if v.getColor() == 'white':
c = 0
for w in v.getConnections():
if w.getColor() == 'white':
c += 1
resultList.append((c,v))
resultList.sort(key=lambda x:x[0])
return [y[1] for y in resultList]
def knightTour(n, path, u, limit):
"""
knightTour(0, [], 4, 63)
:param n: 层次, 是搜索树的当前深度
:param path: 路径, 是到目前为止访问到的顶点列表
:param u: 当前顶点, 是希望在图中访问的顶点
:param limit: 搜索总深度, 路径上的顶点总数
:return:
目前实现的算法,其复杂度为O(k^n),其中n是棋盘格数目
"""
u.setColor('gray')
# 当前顶点加入路径
path.append(u)
if n < limit:
# 对所有合法移动逐一深入
# nbrList = list(u.getConnections())
nbrList = list(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
if __name__ == '__main__':
g = knightGraph(8)
# for i in g:
# print(i)
path = []
startVertex = g.getVertex(4)
knightTour(0, path, startVertex, 63)
# print(path)
for node in path:
print(node.getId(), end=" ")
現在実装されているアルゴリズムの複雑さはO(kn)\ mathbf(O)\ left(\ mathbf(k)^(n)\ right)です。THE(kn)、ここで、nは次のようなチェッカーの数です。8 * 8チェス盤、nは64、kはブランチの平均数であり、8 * 8のそれぞれについて、ブランチの平均数は5です(つまり、各グリッド内を平均して移動する方法は5つあります)。
これは指数関数的な時間計算量のアルゴリズムであり、その検索プロセスはレベルnのツリーとして表されます。
-
指数関数的な時間計算量アルゴリズムでさえ、実際のパフォーマンスを大幅に向上させることができます
- 頂点の順序を特定の方法で配置するnbrListのスマートな構造により、8×8ボードの移動経路の検索時間を第2レベルに短縮できます。
-
この改善されたアルゴリズムは、Warnsdorffアルゴリズムと呼ばれます
一般的な深さ優先探索
-
深さ優先探索の一般的な目標は、グラフ上で可能な限り深く検索し、可能な限り多くの頂点を接続し、必要に応じて分岐し(ツリーを作成)、場合によっては「深さ優先フォレスト」と呼ばれる複数のツリーを作成することです。
-
深さ優先探索では、頂点の「precursor」属性を使用して、ツリーまたはフォレストを構築します。
- さらに、「検出時間」と「終了時間」の属性を設定する必要があります。前者は、最初の数ステップで頂点にアクセスすることを意味し(灰色に設定)、後者は、この探索の完了を意味します。最初の数ステップの頂点(黒に設定)
- これらの2つの新しい属性は、次のグラフアルゴリズムにとって非常に重要です。
-
DFSアルゴリズムを使用したグラフは、Graphのサブクラスとして実装されます。
- VertexはメンバーDiscoveryとFinishを追加します
- グラフグラフは、アルゴリズムによって実行されたステップ数を記録するためにメンバー時間を追加します
-
DFSによって構築されたツリーには、その頂点に「検出時間」と「終了時間」の属性があり、括弧に似ています。
- つまり、頂点の「検出時間」は、常にすべてのサブ頂点の「検出時間」よりも短くなります。
- 「終了時間」は、すべてのサブ頂点の「終了時間」よりも大きく、サブ頂点よりも早く検出され、後で探索が終了します。
-
DFSランタイムには2つの側面も含まれます
- dfs関数には2つのループがあり、それぞれが| V |回であるため、o(| V |)になります。
- dfsvisit関数のループは、現在の頂点に接続されている頂点で実行されます。再帰呼び出しは、頂点が白の場合にのみ行われるため、エッジごとに1つのステップのみが実行されるため、o(| E |)になります。
- BFSと同じo(| V | + | E |)を合計します
BFSは、キューを使用して、アクセスする頂点を格納します
DFSは再帰呼び出しを使用し、暗黙的にスタックを使用します
# coding: utf-8
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)