อัลกอริทึมและ A-Star ของ Dijkstra เปรียบเทียบกันอย่างไร


154

ฉันกำลังดูว่าพวกผู้ชายในการแข่งขัน Mario AIทำอะไรและบางคนก็สร้างบอทมาริโอที่สวย ๆ โดยใช้อัลกอริธึม Pathing A * (A-Star)

ข้อความแสดงแทน
( วิดีโอของ Mario A * Bot In Action )

คำถามของฉันคือ A-Star เปรียบเทียบกับ Dijkstra อย่างไร มองไปที่พวกเขาพวกเขาดูเหมือนคล้ายกัน

ทำไมบางคนถึงใช้อีกอันหนึ่ง? โดยเฉพาะอย่างยิ่งในบริบทของเส้นทางในเกม?



@SLaks A * ใช้หน่วยความจำมากกว่า dijkstra หรือไม่ ทำไมจึงเป็นเช่นนั้นถ้าเส้นทางที่มีแนวโน้มเท่านั้นที่โหนดในขณะที่ dijkstra พยายามพวกมันทั้งหมด?
Poutrathor

คำตอบ:


177

Dijkstra เป็นกรณีพิเศษสำหรับ A * (เมื่อฮิวริสติกเป็นศูนย์)


1
ใน dijkstra เราแค่พิจารณาระยะทางจากแหล่งกำเนิดใช่มั้ย และจุดสุดยอดขั้นต่ำจะถูกนำมาพิจารณาด้วยหรือไม่
Kraken

4
ฉันคิดว่า A * เป็นกรณีพิเศษสำหรับ Dijkstra ที่พวกเขาใช้การวิเคราะห์พฤติกรรม ตั้งแต่ Dijkstra อยู่ที่นั่นเป็นครั้งแรก
Madmenyo

46
@MennoGouw: ได้รับการพัฒนาอัลกอริทึมของ Dijkstra ก่อน แต่มันเป็นกรณีพิเศษของอัลกอริทึมทั่วไป A * มันไม่ได้ผิดปกติเลย (ในความเป็นจริงอาจเป็นบรรทัดฐาน) สำหรับกรณีพิเศษที่จะถูกค้นพบก่อนแล้วจึงถูกทำให้เป็นแบบทั่วไป
Pieter Geerkens

1
คำตอบที่ดีสำหรับทุกคนที่รู้จักการวิเคราะห์พฤติกรรม)
lindhe

1
A * และการใช้ฮิวริสติกจะถูกกล่าวถึงอย่างดีในหนังสือ AI ของ Norvig และ Russel
BoltzmannBrain

113

Dijkstra:

f(x)=g(x)มันมีฟังก์ชั่นค่าใช้จ่ายใดอย่างหนึ่งซึ่งเป็นค่าใช้จ่ายที่แท้จริงจากแหล่งที่แต่ละโหนด:
พบเส้นทางที่สั้นที่สุดจากต้นทางไปยังโหนดอื่นโดยพิจารณาจากราคาจริงเท่านั้น

การค้นหา *:

มันมีฟังก์ชั่นค่าใช้จ่ายสอง

  1. g(x): เหมือนกับ Dijkstra xต้นทุนที่แท้จริงในการเข้าถึงโหนด
  2. h(x): ราคาโดยประมาณจากโหนดxถึงโหนดเป้าหมาย มันเป็นฟังก์ชั่นแก้ปัญหา ฟังก์ชันฮิวริสติกนี้ไม่ควรประเมินค่าสูงไป นั่นหมายถึงต้นทุนที่แท้จริงไปยังโหนดเป้าหมายเข้าถึงจากโหนดควรจะมากกว่าหรือเท่ากับx h(x)มันเรียกว่าฮิวริสติกที่ยอมรับได้

ต้นทุนรวมของแต่ละโหนดคำนวณโดย f(x)=g(x)+h(x)

การค้นหา * จะขยายเฉพาะโหนดหากดูเหมือนว่ามีแนวโน้ม มันมุ่งเน้นที่จะเข้าถึงโหนดเป้าหมายจากโหนดปัจจุบันไม่ใช่เพื่อเข้าถึงโหนดอื่น ๆ มันเป็นสิ่งที่ดีที่สุดถ้าฟังก์ชั่นการแก้ปัญหาเป็นที่ยอมรับ

ดังนั้นหากฟังก์ชันฮิวริสติกของคุณดีพอที่จะประมาณค่าใช้จ่ายในอนาคตได้คุณจะต้องสำรวจโหนดน้อยกว่า Dijkstra


20

สิ่งที่ผู้โพสต์ก่อนหน้าพูดพร้อมด้วยเนื่องจาก Dijkstra ไม่มีการวิเคราะห์พฤติกรรมและในแต่ละขั้นตอนจะเลือกขอบที่มีค่าใช้จ่ายน้อยที่สุดจึงมีแนวโน้มที่จะ "ครอบคลุม" กราฟของคุณมากขึ้น เพราะ Dijkstra นั้นมีประโยชน์มากกว่า A * ตัวอย่างที่ดีคือเมื่อคุณมีโหนดเป้าหมายของผู้สมัครหลายคน แต่คุณไม่รู้ซึ่งเป็นโหนดที่ใกล้เคียงที่สุด (ในกรณี A * คุณจะต้องเรียกใช้หลายครั้ง: หนึ่งครั้งสำหรับแต่ละโหนดที่สมัคร)


17
หากมีโหนดเป้าหมายที่เป็นไปได้หลายโหนดใคร ๆ ก็สามารถเปลี่ยนฟังก์ชั่นการทดสอบเป้าหมายเพื่อรวมโหนดทั้งหมดได้ ด้วยวิธีนี้ A * จะต้องเรียกใช้เพียงครั้งเดียว
Brad Larsen

9

อัลกอริทึมของ Dijkstra จะไม่ถูกใช้สำหรับการหาเส้นทาง การใช้ A * นั้นไม่ใช่เรื่องง่ายหากคุณสามารถสร้างฮิวริสติกที่เหมาะสมได้ (โดยปกติแล้วจะง่ายสำหรับเกมโดยเฉพาะในโลก 2 มิติ) ขึ้นอยู่กับพื้นที่การค้นหาบางครั้ง Iterative Deepening A * เป็นที่นิยมเพราะใช้หน่วยความจำน้อย


5
ทำไม Dijkstra ถึงไม่เคยถูกใช้ในการหาเส้นทาง? คุณสามารถทำอย่างละเอียด?
KingNestor

2
เพราะถึงแม้ว่าคุณจะสามารถเรียนรู้วิธีแก้ปัญหาด้วยหมัดได้คุณจะทำได้ดีกว่า Dijkstra บางครั้งแม้ว่ามันจะไม่สามารถยอมรับได้ มันขึ้นอยู่กับโดเมน Dijkstra จะไม่ทำงานในสถานการณ์ที่มีหน่วยความจำต่ำในขณะที่ IDA * จะใช้งาน
Shaggy Frog

ฉันพบสไลด์ที่นี่: webdocs.cs.ualberta.ca/~jonathan/PREVIOUS/Courses/657/Notes/ …
davidtbernal

7

Dijkstra เป็นกรณีพิเศษสำหรับ A *

Dijkstra ค้นหาต้นทุนขั้นต่ำจากโหนดเริ่มต้นไปยังส่วนอื่น ๆ ทั้งหมด A * ค้นหาต้นทุนขั้นต่ำจากโหนดเริ่มต้นไปยังโหนดเป้าหมาย

อัลกอริทึมของ Dijkstra จะไม่ถูกใช้สำหรับการค้นหาเส้นทาง การใช้ A * สามารถเกิดขึ้นได้กับฮิวริสติกที่เหมาะสม การทำซ้ำ A * นั้นดีกว่าเพราะขึ้นอยู่กับพื้นที่การค้นหาเนื่องจากใช้หน่วยความจำน้อยกว่า

รหัสสำหรับอัลกอริทึมของ Dijkstra คือ:

// 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)

การข้ามเพื่อนบ้านที่อยู่ในเซตปิดแล้วจะให้ผลที่ไม่ดี ลองใช้ในกราฟนี้ (เป็นตัวอย่างวิดีโอ youtube ไม่ต้องสนใจภาษา) จะให้คำตอบที่ผิด
itsjwala

5

Dijkstra ค้นหาต้นทุนขั้นต่ำจากโหนดเริ่มต้นไปยังส่วนอื่น ๆ ทั้งหมด A * ค้นหาต้นทุนขั้นต่ำจากโหนดเริ่มต้นไปยังโหนดเป้าหมาย

ดังนั้นจึงดูเหมือนว่า Dijkstra จะมีประสิทธิภาพน้อยลงเมื่อคุณต้องการระยะทางต่ำสุดจากโหนดหนึ่งไปอีกโหนดหนึ่ง


2
นี่ไม่เป็นความจริง. Standard Dijkstra ใช้เพื่อให้เส้นทางที่สั้นที่สุดระหว่างสองจุด
Emil

3
โปรดอย่าเข้าใจผิด Dijkstra's ให้ผลลัพธ์จาก s ถึงจุดยอดอื่น ๆ ทั้งหมด ดังนั้นจึงทำงานได้ช้าลง
Ivan Voroshilin

ฉันสองความคิดเห็น @Emil สิ่งที่คุณต้องทำคือหยุดเมื่อลบโหนดปลายทางออกจากลำดับความสำคัญและคุณมีเส้นทางที่สั้นที่สุดจากต้นทางไปยังปลายทาง นี่เป็นอัลกอริทึมดั้งเดิมจริง ๆ แล้ว
seteropere

แม่นยำยิ่งขึ้น: หากระบุเป้าหมาย Dijkstra จะค้นหาเส้นทางที่สั้นที่สุดไปยังโหนดทั้งหมดที่อยู่บนเส้นทางที่สั้นกว่าเส้นทางไปยังเป้าหมายที่ระบุ วัตถุประสงค์ของฮิวริสติกใน A * ก็เพื่อตัดเส้นทางเหล่านี้บางส่วน ประสิทธิผลของการแก้ปัญหากำหนดจำนวนที่ถูกตัด
Waylon Flinn

@seteropere แต่จะเกิดอะไรขึ้นถ้าโหนดปลายทางของคุณคือโหนดสุดท้ายที่ถูกค้นหา มีประสิทธิภาพน้อยลงอย่างแน่นอนเนื่องจากการฮิวริสติกของ A * และการเลือกโหนดลำดับความสำคัญเป็นสิ่งที่ช่วยให้แน่ใจว่าการค้นหาโหนดปลายทางไม่ใช่โหนดสุดท้ายในรายการ
Knight0fDragon

5

คุณสามารถพิจารณา Dijkstra เวอร์ชัน A * ความหมายแทนที่จะสำรวจทุกโหนดคุณจะใช้ฮิวริสติกเพื่อเลือกทิศทาง

หากคุณใช้อัลกอริทึมกับคิวลำดับความสำคัญลำดับความสำคัญของโหนดที่คุณกำลังเยี่ยมชมจะเป็นฟังก์ชั่นของต้นทุน (โหนดก่อนหน้านี้ค่าใช้จ่าย + ค่าใช้จ่ายที่จะได้รับที่นี่) และการประเมินฮิวริสติกจากที่นี่ ไปสู่เป้าหมาย ขณะที่อยู่ใน Dijkstra ลำดับความสำคัญจะได้รับผลกระทบจากต้นทุนที่เกิดขึ้นจริงกับโหนดเท่านั้น ไม่ว่าในกรณีใดเกณฑ์การหยุดจะถึงเป้าหมาย


2

อัลกอริทึมของ Dijkstra ค้นหาเส้นทางที่สั้นที่สุดอย่างแน่นอน ในทางกลับกัน A * ขึ้นอยู่กับฮิวริสติก ด้วยเหตุนี้ A * จึงเร็วกว่าอัลกอริทึมของ Dijkstra และจะให้ผลลัพธ์ที่ดีหากคุณมีฮิวริสติกที่ดี


4
A * ให้ผลลัพธ์เหมือนกับ Dijkstra แต่เร็วกว่าเมื่อคุณใช้ฮิวริสติกที่ดี อัลกอริทึม * กำหนดเงื่อนไขบางประการสำหรับการทำงานอย่างถูกต้องเช่นระยะทางโดยประมาณระหว่างโหนดปัจจุบันและโหนดสุดท้ายควรต่ำกว่าระยะทางจริง
Alexandru

4
A * รับประกันว่าจะให้เส้นทางที่สั้นที่สุดเมื่อฮิวริสติกยอมรับได้ (ดูถูกดูแคลนเสมอ)
Robert

1

ถ้าคุณดูpsuedocodeสำหรับ Astar:

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) ;

ดังนั้นประเด็นก็คือแอสตาร์จะไม่ประเมินโหนดมากกว่าหนึ่งครั้ง
เนื่องจากเชื่อว่าการมองที่โหนดหนึ่งครั้งนั้นเพียงพอแล้วเนื่องจาก
การวิเคราะห์พฤติกรรม

OTOH อัลกอริทึมของ Dijkstra ไม่ได้อายที่จะแก้ไขตัวเองในกรณี
ที่โหนดปรากฏขึ้นอีกครั้ง

ซึ่งน่าจะทำให้ Astar เร็วขึ้นและเหมาะสำหรับการค้นหาเส้นทาง


7
สิ่งนี้ไม่เป็นความจริง: A * สามารถดูโหนดได้มากกว่าหนึ่งครั้ง ในความเป็นจริง Dijkstra เป็นกรณีพิเศษของ A * ...
เอมิล

2
ตรวจสอบสิ่งนี้เพื่อความกระจ่าง: stackoverflow.com/questions/21441662/…
spiralmoon

อัลกอริธึมการค้นหาทั้งหมดมี "ชายแดน" และ "ชุดเยี่ยมชม" อัลกอริทึมทั้งสองแก้ไขเส้นทางไปยังโหนดเมื่ออยู่ในชุดเยี่ยมชม: โดยการออกแบบพวกเขาย้ายโหนดจากชายแดนไปยังชุดเยี่ยมชมตามลำดับความสำคัญ ระยะทางที่รู้จักกันน้อยที่สุดไปยังโหนดสามารถอัปเดตได้เฉพาะเมื่ออยู่ในแนวชายแดน Dijkstra's เป็นรูปแบบการค้นหาที่ดีที่สุดเป็นอันดับแรกและโหนดจะไม่ถูกตรวจสอบอีกครั้งเมื่อวางในชุด "เยี่ยมชม" A * ใช้คุณสมบัตินี้ร่วมกันและจะใช้ตัวประมาณค่าเสริมเพื่อเลือกโหนดที่ชายแดนเพื่อจัดลำดับความสำคัญ en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pygosceles

0

ใน A * สำหรับแต่ละโหนดคุณตรวจสอบการเชื่อมต่อขาออกสำหรับโหนดเหล่านั้น
สำหรับแต่ละโหนดใหม่คุณคำนวณต้นทุนต่ำสุดจนถึง (csf) ขึ้นอยู่กับน้ำหนักของการเชื่อมต่อกับโหนดนี้และค่าใช้จ่ายที่คุณต้องไปถึงโหนดก่อนหน้า
นอกจากนี้คุณประมาณค่าใช้จ่ายจากโหนดใหม่ไปยังโหนดเป้าหมายและเพิ่มสิ่งนี้ใน csf ตอนนี้คุณมีค่าใช้จ่ายทั้งหมดโดยประมาณ (ฯลฯ ) (etc = csf + ระยะทางโดยประมาณไปยังเป้าหมาย) ถัดไปที่คุณเลือกจากโหนดใหม่โหนดที่มีค่าต่ำสุด ฯลฯ
ทำเช่นเดียวกันก่อนหน้านี้จนกว่าโหนดใหม่จะเป็นเป้าหมาย

Dijkstra ทำงานได้เกือบเหมือนกัน ยกเว้นว่าระยะทางโดยประมาณไปยังเป้าหมายจะเป็น 0 เสมอและอัลกอริทึมจะหยุดก่อนเมื่อเป้าหมายไม่ได้เป็นเพียงหนึ่งในโหนดใหม่แต่ยังเป็นโหนดที่มี csf ต่ำสุด

A * โดยปกติจะเร็วกว่า dijstra ถึงแม้ว่ามันจะไม่เป็นเช่นนั้นเสมอไป ในวิดีโอเกมคุณมักจะใช้วิธี "ใกล้พอสำหรับเกม" ดังนั้นเส้นทางที่ดีที่สุด "ใกล้พอ" จาก A * มักจะเพียงพอ


-1

อัลกอริทึมของ Dijkstra นั้นสมบูรณ์และแน่นอนที่สุดซึ่งคุณจะพบเส้นทางที่สั้นที่สุดเสมอ อย่างไรก็ตามมีแนวโน้มที่จะใช้เวลานานกว่าเนื่องจากส่วนใหญ่จะใช้เพื่อตรวจหาโหนดเป้าหมายหลายโหนด

A* searchในทางกลับกันมีค่าฮิวริสติกซึ่งคุณสามารถกำหนดให้ถึงเป้าหมายที่ใกล้กว่าเช่นระยะทางแมนฮัตตันสู่เป้าหมาย มันสามารถเป็นได้ทั้งที่ดีที่สุดและสมบูรณ์ซึ่งขึ้นอยู่กับปัจจัยการเรียนรู้ มันจะเร็วขึ้นแน่นอนถ้าคุณมีโหนดเป้าหมายเดียว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.