マリオAIコンペティション のメンバーが何をしているかを見ていましたが、彼らの何人かはA *(A-Star)Pathing Algorithmを利用してかなりきちんとしたマリオボットを構築しました。
( マリオA *ボットの動作のビデオ )
私の質問は、A-Starはダイクストラと比較してどうですか?それらを見渡すと、それらは似ているように見えます。
なぜ誰かが一方を他方よりも使用するのでしょうか?特にゲーム内のパスのコンテキストでは?
ダイクストラは、A *の特殊なケースです(ヒューリスティックがゼロの場合)。
ソースから各ノードへの実際のコスト値であるf(x)=g(x)
という1つのコスト関数があります。
実際のコストのみを考慮して、ソースから他のすべてのノードへの最短パスを見つけます。
2つのコスト関数があります。
g(x)
:ダイクストラと同じ。ノードに到達するための実際のコストx
。h(x)
:ノードx
から目標ノードまでの概算コスト。これはヒューリスティック関数です。このヒューリスティック関数は、コストを過大評価してはなりません。つまり、ノードx
から目標ノードに到達するための実際のコストは、h(x)
以上である必要があります。これは許容ヒューリスティックと呼ばれます。各ノードの総コストは、f(x)=g(x)+h(x)
によって計算されます
A *検索は、有望と思われる場合にのみノードを展開します。他のすべてのノードに到達するのではなく、現在のノードから目標ノードに到達することに焦点を合わせます。ヒューリスティック関数が許容される場合、最適です。
したがって、ヒューリスティック関数が将来のコストを概算するのに適している場合、ダイクストラよりもはるかに少ないノードを探索する必要があります。
前のポスターが言ったことに加えて、ダイクストラにはヒューリスティックがなく、各ステップで最小コストでエッジを選択するため、グラフをより多く「カバー」する傾向があります。そのため、ダイクストラはA *よりも便利です。良い例は、複数の候補ターゲットノードがあるが、どれが最も近いかわからない場合です(A *の場合、複数の候補ノードごとに1回実行する必要があります)。
ダイクストラのアルゴリズムは、経路探索には決して使用されません。適切なヒューリスティック(通常はゲーム、特に2Dの世界では簡単)を思い付くことができれば、A *の使用は簡単です。サーチスペースによっては、メモリの使用量が少ないため、反復深化A *が望ましい場合があります。
ダイクストラは、A *の特殊なケースです。
ダイクストラは、開始ノードから他のすべてのノードまでの最小コストを見つけます。 A *は、開始ノードから目標ノードまでの最小コストを見つけます。
ダイクストラのアルゴリズムは、パス検索には決して使用されません。 A *を使用すると、適切なヒューリスティックを作成できます。サーチスペースによっては、メモリ使用量が少ないため、反復A *の方が適しています。
ダイクストラのアルゴリズムのコードは次のとおりです。
// A C / C++ program for Dijkstra's single source shortest path algorithm.
// The program is for adjacency matrix representation of the graph
#include <stdio.h>
#include <limits.h>
// Number of vertices in the graph
#define V 9
// A utility function to find the vertex with minimum distance value, from
// the set of vertices not yet included in shortest path tree
int minDistance(int dist[], bool sptSet[])
{
// Initialize min value
int min = INT_MAX, min_index;
for (int v = 0; v < V; v++)
if (sptSet[v] == false && dist[v] <= min)
min = dist[v], min_index = v;
return min_index;
}
int printSolution(int dist[], int n)
{
printf("Vertex Distance from Source\n");
for (int i = 0; i < V; i++)
printf("%d \t\t %d\n", i, dist[i]);
}
void dijkstra(int graph[V][V], int src)
{
int dist[V]; // The output array. dist[i] will hold the shortest
// distance from src to i
bool sptSet[V]; // sptSet[i] will true if vertex i is included in shortest
// path tree or shortest distance from src to i is finalized
// Initialize all distances as INFINITE and stpSet[] as false
for (int i = 0; i < V; i++)
dist[i] = INT_MAX, sptSet[i] = false;
// Distance of source vertex from itself is always 0
dist[src] = 0;
// Find shortest path for all vertices
for (int count = 0; count < V-1; count++)
{
// Pick the minimum distance vertex from the set of vertices not
// yet processed. u is always equal to src in first iteration.
int u = minDistance(dist, sptSet);
// Mark the picked vertex as processed
sptSet[u] = true;
// Update dist value of the adjacent vertices of the picked vertex.
for (int v = 0; v < V; v++)
// Update dist[v] only if is not in sptSet, there is an Edge from
// u to v, and total weight of path from src to v through u is
// smaller than current value of dist[v]
if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX
&& dist[u]+graph[u][v] < dist[v])
dist[v] = dist[u] + graph[u][v];
}
// print the constructed distance array
printSolution(dist, V);
}
// driver program to test above function
int main()
{
/* Let us create the example graph discussed above */
int graph[V][V] = {{0, 4, 0, 0, 0, 0, 0, 8, 0},
{4, 0, 8, 0, 0, 0, 0, 11, 0},
{0, 8, 0, 7, 0, 4, 0, 0, 2},
{0, 0, 7, 0, 9, 14, 0, 0, 0},
{0, 0, 0, 9, 0, 10, 0, 0, 0},
{0, 0, 4, 14, 10, 0, 2, 0, 0},
{0, 0, 0, 0, 0, 2, 0, 1, 6},
{8, 11, 0, 0, 0, 0, 1, 0, 7},
{0, 0, 2, 0, 0, 0, 6, 7, 0}
};
dijkstra(graph, 0);
return 0;
}
A *アルゴリズムのコードは次のとおりです。
class Node:
def __init__(self,value,point):
self.value = value
self.point = point
self.parent = None
self.H = 0
self.G = 0
def move_cost(self,other):
return 0 if self.value == '.' else 1
def children(point,grid):
x,y = point.point
links = [grid[d[0]][d[1]] for d in [(x-1, y),(x,y - 1),(x,y + 1),(x+1,y)]]
return [link for link in links if link.value != '%']
def manhattan(point,point2):
return abs(point.point[0] - point2.point[0]) + abs(point.point[1]-point2.point[0])
def aStar(start, goal, grid):
#The open and closed sets
openset = set()
closedset = set()
#Current point is the starting point
current = start
#Add the starting point to the open set
openset.add(current)
#While the open set is not empty
while openset:
#Find the item in the open set with the lowest G + H score
current = min(openset, key=lambda o:o.G + o.H)
#If it is the item we want, retrace the path and return it
if current == goal:
path = []
while current.parent:
path.append(current)
current = current.parent
path.append(current)
return path[::-1]
#Remove the item from the open set
openset.remove(current)
#Add it to the closed set
closedset.add(current)
#Loop through the node's children/siblings
for node in children(current,grid):
#If it is already in the closed set, skip it
if node in closedset:
continue
#Otherwise if it is already in the open set
if node in openset:
#Check if we beat the G score
new_g = current.G + current.move_cost(node)
if node.G > new_g:
#If so, update the node to have a new parent
node.G = new_g
node.parent = current
else:
#If it isn't in the open set, calculate the G and H score for the node
node.G = current.G + current.move_cost(node)
node.H = manhattan(node, goal)
#Set the parent to our current item
node.parent = current
#Add it to the set
openset.add(node)
#Throw an exception if there is no path
raise ValueError('No Path Found')
def next_move(pacman,food,grid):
#Convert all the points to instances of Node
for x in xrange(len(grid)):
for y in xrange(len(grid[x])):
grid[x][y] = Node(grid[x][y],(x,y))
#Get the path
path = aStar(grid[pacman[0]][pacman[1]],grid[food[0]][food[1]],grid)
#Output the path
print len(path) - 1
for node in path:
x, y = node.point
print x, y
pacman_x, pacman_y = [ int(i) for i in raw_input().strip().split() ]
food_x, food_y = [ int(i) for i in raw_input().strip().split() ]
x,y = [ int(i) for i in raw_input().strip().split() ]
grid = []
for i in xrange(0, x):
grid.append(list(raw_input().strip()))
next_move((pacman_x, pacman_y),(food_x, food_y), grid)
ダイクストラは、開始ノードから他のすべてのノードまでの最小コストを見つけます。 A *は、開始ノードから目標ノードまでの最小コストを見つけます。
したがって、必要なのは、あるノードから別のノードまでの最小距離だけである場合、ダイクストラの効率は低下すると思われます。
A *はダイクストラのガイド付きバージョンと考えることができます。つまり、すべてのノードを探索する代わりに、ヒューリスティックを使用して方向を選択します。
より具体的に言うと、優先度キューを使用してアルゴリズムを実装している場合、訪問しているノードの優先度は、コスト(以前のノードのコスト+ここに到達するためのコスト)とここからのヒューリスティック推定値の関数になります目標に。ダイクストラでは、優先度はノードへの実際のコストによってのみ影響を受けます。いずれの場合も、停止基準は目標に到達しています。
ダイクストラのアルゴリズムは、最短経路を確実に見つけます。一方、A *はヒューリスティックに依存します。このため、A *はダイクストラのアルゴリズムよりも高速であり、優れたヒューリスティックがあれば良好な結果が得られます。
Astarの psuedocode を見ると:
foreach y in neighbor_nodes(x)
if y in closedset
continue
一方、 Dijkstra について同じものを見ると:
for each neighbor v of u:
alt := dist[u] + dist_between(u, v) ;
そのため、Astarはノードを2回以上評価しません。
ノードを一度見るだけで十分だと考えているため、
ヒューリスティックに。
ダイクストラのアルゴリズムであるOTOHは、
ノードが再びポップアップします。
どのshouldを使うと、Astarが高速になり、パス検索に適したものになります。
ダイクストラのアルゴリズムは間違いなく完全で最適であり、常に最短経路を見つけることができます。ただし、主に複数の目標ノードを検出するために使用されるため、時間がかかる傾向があります。
一方、A* search
はヒューリスティックな値に関係します。ヒューリスティック値は、目標に近づくマンハッタン距離など、より近い目標に到達するように定義できます。ヒューリスティックな要因に応じて、最適または完全になります。目標ノードが1つしかない場合は、間違いなく高速です。
A *では、各ノードについて、それらの発信接続を確認します。
新しいノードごとに、このノードへの接続の重みと前のノードに到達する必要があるコストに応じて、これまでの最低コスト(csf)を計算します。
さらに、新しいノードからターゲットノードまでのコストを見積もり、これをcsfに追加します。これで、推定総コスト(など)が得られました。 (etc = csf +ターゲットまでの推定距離)次に、新しいノードから最も低いノードなどを選択します。
新しいノードのいずれかがターゲットになるまで、前と同じようにします。
ダイクストラの動作はほぼ同じです。ターゲットまでの推定距離は常に0であり、ターゲットが新しいノードの1つであるだけでなく、最も低いcsfを持つものでもある場合、アルゴリズムは最初に停止します。
A *は通常dijstraよりも高速ですが、常にそうであるとは限りません。ビデオゲームでは、多くの場合、「ゲームに十分近い」アプローチを採用しています。したがって、A *からの「十分に近い」最適なパスで通常は十分です。