Làm thế nào để A * tìm đường hoạt động?


67

Tôi muốn hiểu ở cấp độ cơ bản theo cách mà hoạt động tìm đường A * hoạt động. Bất kỳ mã hoặc triển khai mã psuedo cũng như trực quan hóa sẽ hữu ích.


Đây là một bài viết nhỏ với một GIF hoạt hình hiển thị Thuật toán Dijkstra của chuyển động.
Ólafur Chờ đợi

Các trang A * của Amit là một giới thiệu tốt cho tôi. Bạn có thể tìm thấy rất nhiều hình ảnh trực quan tốt khi tìm kiếm Thuật toán AStar trên youtube.
jdeseno

Tôi đã bị nhầm lẫn bởi một số giải thích về A * trước khi tôi tìm thấy hướng dẫn tuyệt vời này: chính sáchmanac.org / trò chơi / StarstarTutorial.htmlm Tôi chủ yếu đề cập đến điều đó khi tôi viết một triển khai A * trong ActionScript: newarteest.com/flash /astar.html
jhocking

4
-1 wikipedia có bài viết về A * với lời giải thích, mã nguồn, trực quan và ... . Một số câu trả lời ở đây có Liên kết ngoài từ trang wiki đó.
dùng712092

4
Ngoài ra, vì đây là một chủ đề khá phức tạp rất được các nhà phát triển trò chơi quan tâm, tôi nghĩ chúng tôi muốn thông tin ở đây. Tôi nhớ lại Joel từng nói rằng anh ấy muốn StackOverflow trở thành hit hàng đầu khi mọi người lập trình google.
jhocking

Câu trả lời:


63

Khước từ

Có hàng tấn ví dụ mã và giải thích về A * được tìm thấy trực tuyến. Câu hỏi này cũng đã nhận được rất nhiều câu trả lời tuyệt vời với rất nhiều liên kết hữu ích. Trong câu trả lời của tôi, tôi sẽ cố gắng cung cấp một ví dụ minh họa về thuật toán, có thể dễ hiểu hơn mã hoặc mô tả.


Thuật toán của Dijkstra

Để hiểu A *, tôi khuyên bạn trước tiên hãy xem thuật toán của Dijkstra . Hãy để tôi hướng dẫn bạn qua các bước thuật toán của Dijkstra sẽ thực hiện cho tìm kiếm.

Nút bắt đầu của chúng tôi là Avà chúng tôi muốn tìm đường dẫn ngắn nhất tới F. Mỗi cạnh của biểu đồ có chi phí di chuyển được liên kết với nó (ký hiệu là các chữ số màu đen bên cạnh các cạnh). Mục tiêu của chúng tôi là đánh giá chi phí di chuyển tối thiểu cho mỗi đỉnh (hoặc nút) của biểu đồ cho đến khi chúng tôi đạt được nút mục tiêu.

Minh họa của Dijkstra, phần 1

Đây là điểm khởi đầu của chúng tôi. Chúng tôi có một nút danh sách để kiểm tra, danh sách này hiện tại là:

{ A(0) }

Acó chi phí 0, tất cả các nút khác được đặt thành vô cùng (trong một triển khai điển hình, đây sẽ là một cái gì đó int.MAX_VALUEtương tự hoặc tương tự).

Minh họa của Dijkstra, phần 2

Chúng tôi lấy nút có chi phí thấp nhất từ ​​danh sách các nút của chúng tôi (vì danh sách của chúng tôi chỉ chứa A, đây là ứng cử viên của chúng tôi) và truy cập tất cả các hàng xóm của nó. Chúng tôi đặt chi phí của mỗi người hàng xóm là:

Cost_of_Edge + Cost_of_previous_Node

và theo dõi nút trước đó (hiển thị dưới dạng chữ nhỏ màu hồng bên dưới nút). Abây giờ có thể được đánh dấu là đã giải quyết (màu đỏ) để chúng ta không truy cập lại. Danh sách các ứng cử viên của chúng tôi bây giờ trông như thế này:

{ B(2), D(3), C(4) }

Minh họa của Dijkstra, phần 3

Một lần nữa, chúng tôi lấy nút có chi phí thấp nhất từ ​​danh sách của chúng tôi ( B) và đánh giá các nút lân cận. Đường dẫn đến Dđắt hơn chi phí hiện tại D, do đó đường dẫn này có thể bị loại bỏ. Esẽ được thêm vào danh sách ứng cử viên của chúng tôi, hiện tại trông như thế này:

{ D(3), C(4), E(4) }

Minh họa của Dijkstra, phần 4

Nút tiếp theo để kiểm tra là bây giờ D. Kết nối Ccó thể bị loại bỏ, vì đường dẫn không ngắn hơn chi phí hiện có. Chúng tôi đã tìm thấy một con đường ngắn hơn Emặc dù, do đó chi phí cho Evà nút trước đó của nó sẽ được cập nhật. Danh sách của chúng tôi bây giờ trông như thế này:

{ E(3), C(4) }

Minh họa của Dijkstra, phần 5

Vì vậy, như chúng tôi đã làm trước đây, chúng tôi kiểm tra nút với chi phí thấp nhất từ ​​danh sách của chúng tôi, bây giờ E. Echỉ có một hàng xóm chưa được giải quyết, đó cũng là nút đích. Chi phí để đạt được nút đích được đặt thành 10và nút trước đó của nó thành E. Danh sách các ứng cử viên của chúng tôi bây giờ trông như thế này:

{ C(4), F(10) }

Minh họa của Dijkstra, phần 6

Tiếp theo chúng tôi kiểm tra C. Chúng tôi có thể cập nhật chi phí và nút trước đó cho F. Vì danh sách của chúng tôi hiện có Fnút với chi phí thấp nhất, chúng tôi đã hoàn thành. Đường dẫn của chúng ta có thể được xây dựng bằng cách quay lại các nút ngắn nhất trước đó.


Thuật toán A *

Vì vậy, bạn có thể tự hỏi tại sao tôi giải thích Dijkstra cho bạn thay vì thuật toán A * ? Chà, sự khác biệt duy nhất là cách bạn cân nhắc (hoặc sắp xếp) các ứng cử viên của bạn. Với Dijkstra, đó là:

Cost_of_Edge + Cost_of_previous_Node

Với A * đó là:

Cost_of_Edge + Cost_of_previous_Node + Estimated_Cost_to_reach_Target_from(Node)

Trường hợp Estimated_Cost_to_reach_Target_fromthường được gọi là một chức năng Heuristic . Đây là một hàm sẽ cố gắng ước tính chi phí để đạt được nút đích. Một chức năng heuristic tốt sẽ đạt được rằng ít nút hơn sẽ phải được truy cập để tìm mục tiêu. Trong khi thuật toán của Dijkstra sẽ mở rộng ra tất cả các phía, A * sẽ (nhờ tìm kiếm heuristic) theo hướng của mục tiêu.

Trang của Amit về heuristic có một cái nhìn tổng quan tốt về các heuristic thông thường.


2
Điều đáng chú ý là các heuristic sẽ không luôn thúc đẩy tìm kiếm để tìm ra con đường tốt nhất. Ví dụ: nếu heuristic của bạn là khoảng cách đến mục tiêu, nhưng tuyến đường khả thi nằm quanh rìa bản đồ - trong trường hợp này, tìm kiếm sẽ tìm kiếm toàn bộ bản đồ trước khi nó đi đúng tuyến đường. Chắc chắn sau đó, bạn phải suy nghĩ, có điều gì tôi không nhận được? Điều này không hiệu quả! - điều cần hiểu là mục đích của một heuristic là cắt giảm tìm kiếm trong các trường hợp MOST, và công việc của bạn là tìm ra một giải pháp tốt nhất cho tất cả các nhu cầu cụ thể của bạn.
SirYakalot

2
@AsherEinhorn Nó vẫn sẽ tốt hơn (hoặc trong trường hợp xấu nhất bằng) so với tìm kiếm không có thông tin như Djikstra.
bummzack

vâng vâng, bạn hoàn toàn đúng. Có lẽ tôi đã không rõ ràng, ví dụ mà tôi đã nói trong phần bình luận ở trên là một trường hợp lý thuyết 'trường hợp xấu nhất' đối với A * với điều đó heuristic NHƯNG đó là những gì Dijkstra sẽ làm MỌI thời gian. Hầu hết thời gian A * sẽ tốt hơn ngay cả với một heuristic rất đơn giản. Quan điểm của tôi lúc đầu là heuristic có thể gây nhầm lẫn bởi vì 'khoảng cách đến mục tiêu' không phải lúc nào cũng có ý nghĩa đối với mọi kịch bản - vấn đề là nó hầu hết đều phù hợp.
SirYakalot

Ngoài ra, hãy xem điều này: qiao.github.io/PathFinding.js/visual
David Chouinard

Câu trả lời này có thể sử dụng một đề cập đến những gì làm cho một heuristic được chấp nhận, theo nghĩa đảm bảo rằng A * sẽ tìm thấy con đường ngắn nhất. (Nói ngắn gọn: Để được chấp nhận, heuristic không bao giờ được đánh giá quá cao khoảng cách thực tế đến mục tiêu. Các heuristic không được chấp nhận đôi khi có thể hữu ích, nhưng chúng có thể khiến A * quay trở lại các đường dưới tối ưu.)
Ilmari Karonen

26

Tìm kiếm đường dẫn * là tìm kiếm loại đầu tiên tốt nhất sử dụng phương pháp phỏng đoán bổ sung.

Điều đầu tiên bạn cần làm là phân chia khu vực tìm kiếm của bạn. Đối với lời giải thích này, bản đồ là một lưới ô vuông, bởi vì hầu hết các trò chơi 2D sử dụng lưới ô vuông và vì đơn giản để hình dung. Tuy nhiên, xin lưu ý rằng khu vực tìm kiếm có thể được chia nhỏ theo bất kỳ cách nào bạn muốn: có thể là lưới hex hoặc thậm chí là các hình dạng tùy ý như Rủi ro. Các vị trí bản đồ khác nhau được gọi là "các nút" và thuật toán này sẽ hoạt động bất cứ khi nào bạn có một loạt các nút để đi qua và có các kết nối được xác định giữa các nút.

Dù sao, bắt đầu từ một gạch bắt đầu nhất định:

  • 8 ô xung quanh ô bắt đầu được "tính điểm" dựa trên a) chi phí di chuyển từ ô hiện tại sang ô tiếp theo (thường là 1 cho chuyển động ngang hoặc dọc, sqrt (2) cho chuyển động chéo).

  • Mỗi ô sau đó được gán một điểm "heuristic" bổ sung - một xấp xỉ giá trị tương đối của việc di chuyển đến mỗi ô. Các phương pháp phỏng đoán khác nhau được sử dụng, đơn giản nhất là khoảng cách đường thẳng giữa tâm của gạch đã cho và gạch kết thúc.

  • Ngói hiện tại sau đó được "đóng" và tác nhân di chuyển đến ô lân cận đang mở, có điểm di chuyển thấp nhất và điểm heuristic thấp nhất.

  • Quá trình này được lặp lại cho đến khi đạt được nút mục tiêu hoặc không còn nút mở nào nữa (nghĩa là tác nhân bị chặn).

Để biết sơ đồ minh họa các bước này, hãy tham khảo hướng dẫn tốt cho người mới bắt đầu này .

Có một số cải tiến có thể được thực hiện, chủ yếu là cải thiện heuristic:

  • Có tính đến sự khác biệt địa hình, độ gồ ghề, độ dốc, vv

  • Đôi khi cũng rất hữu ích khi thực hiện "quét" trên lưới để chặn các khu vực trên bản đồ không phải là đường dẫn hiệu quả: ví dụ hình chữ U đối diện với tác nhân. Nếu không có thử nghiệm quét, đầu tiên, nhân viên sẽ vào U, quay lại, sau đó rời đi và đi vòng quanh rìa của U. Một tác nhân thông minh "thực sự" sẽ lưu ý bẫy hình chữ U và chỉ cần tránh nó. Quét có thể giúp mô phỏng điều này.


1
Một giải thích với biểu đồ, nút, cạnh, sẽ rõ ràng hơn chỉ là về gạch. Điều này không giúp hiểu rằng dù cấu trúc không gian của trò chơi của bạn là gì, bạn có thể áp dụng thuật toán tương tự như khi bạn có thông tin vị trí được liên kết với nhau trong không gian này.
Klaim

Tôi sẽ tranh luận rằng sẽ thực sự ít rõ ràng hơn, bởi vì nó khó hình dung hơn. Nhưng vâng, lời giải thích này nên đề cập rằng lưới gạch không cần thiết; thực tế, tôi sẽ chỉnh sửa điểm đó.
jhocking

14

Nó còn lâu mới tốt nhất, nhưng đây là một triển khai tôi đã làm về A * trong C ++ vài năm trước.

Có lẽ tốt hơn là tôi chỉ cho bạn các tài nguyên hơn là cố gắng giải thích toàn bộ thuật toán. Ngoài ra, khi bạn đọc qua bài viết wiki, hãy chơi với bản demo và xem liệu bạn có thể hình dung được nó đang hoạt động như thế nào không. Để lại một bình luận nếu bạn có một câu hỏi cụ thể.

  1. A * trên Wikipedia
  2. Trình diễn A * Java

4
Ví dụ Python của bạn là trong C ++.
Buns nhôm

@finish - Thật tốt khi thấy ai đó bắt được điều đó! Các hoạt động hàng ngày xoay quanh Python những ngày này. Cảm ơn!
David McGraw

3
Ví dụ về C ++ của bạn cũng có thể là C.
deceleratedcaviar

4
Ví dụ này cũng có thể nằm trong trình biên dịch chương trình cho tất cả các cấu trúc mà nó có. Nó thậm chí không phải là A *, đây là câu trả lời được chấp nhận như thế nào?

4
Xin lỗi, nó không ngang tầm, đó là một trong những lần thử mã hóa đầu tiên của tôi khi tôi bắt đầu. Hãy đóng góp một cái gì đó cho các bình luận / chỉnh sửa bài đăng để chia sẻ giải pháp của riêng bạn.
David McGraw

6

Bạn có thể thấy bài viết của ActiveTut về Tìm kiếm đường dẫn hữu ích. Nó vượt qua cả Thuật toán của A * và Dijkstra và sự khác biệt giữa chúng. Nó hướng đến các nhà phát triển Flash, nhưng nó sẽ cung cấp một số hiểu biết tốt về lý thuyết ngay cả khi bạn không sử dụng Flash.


4

Một điều quan trọng cần hình dung khi giao dịch với Thuật toán của A * và Dijkstra là A * được định hướng; nó cố gắng tìm con đường ngắn nhất đến một điểm cụ thể bằng cách "đoán" hướng nhìn nào. Thuật toán của Dijkstra tìm thấy con đường ngắn nhất đến / every / point.


1
Đây không thực sự là một mô tả chính xác về sự khác biệt giữa A * và Dijkstra. Đúng là Dijkstra giải quyết nguồn đơn cho tất cả các điểm, nhưng khi được sử dụng trong các trò chơi, nó thường bị cắt ngay khi bạn tìm thấy đường dẫn đến mục tiêu. Sự khác biệt thực sự giữa hai là A * được thông báo bởi heuristic và có thể tìm thấy mục tiêu đó với ít chi nhánh hơn.

Để thêm vào lời giải thích của Joe: A * cũng sẽ tìm thấy đường dẫn đến tất cả các điểm, nếu bạn cho phép, nhưng trong các trò chơi, chúng tôi thường muốn dừng lại sớm. A * hoạt động giống như thuật toán của Dijsktra, ngoại trừ heuristic giúp sắp xếp lại các nút để khám phá các đường dẫn hứa hẹn nhất trước tiên. Bằng cách đó, bạn thường có thể dừng lại sớm hơn so với thuật toán của Dijkstra. Ví dụ: nếu bạn muốn tìm một đường dẫn từ trung tâm bản đồ đến phía đông, thuật toán của Dijkstra sẽ khám phá như nhau theo mọi hướng và dừng lại khi tìm thấy phía đông. A * sẽ dành nhiều thời gian đi về phía đông hơn phía tây và đến đó sớm hơn.
amitp

3

Vì vậy, giống như một tuyên bố đầu tiên, A * là một thuật toán khám phá đồ thị. Thông thường trong các trò chơi, chúng tôi sử dụng gạch hoặc hình học thế giới khác làm biểu đồ, nhưng bạn có thể sử dụng A * cho những thứ khác. Hai thuật toán ur cho truyền tải đồ thị là tìm kiếm theo chiều sâu và tìm kiếm theo chiều rộng. Trong DFS, bạn luôn luôn khám phá đầy đủ chi nhánh hiện tại của mình trước khi nhìn vào anh chị em của nút hiện tại và trong BFS, bạn luôn luôn nhìn vào anh chị em trước rồi đến trẻ em. A * cố gắng tìm một điểm giữa giữa những nơi bạn khám phá một nhánh (giống như DFS) khi bạn đang tiến gần đến mục tiêu mong muốn nhưng đôi khi dừng lại và thử anh chị em nếu nó có kết quả tốt hơn ở nhánh đó. Toán học thực tế là bạn giữ một danh sách các nút có thể để khám phá tiếp theo nơi mỗi nút có "lòng tốt" điểm số cho thấy mức độ gần gũi (theo một cách hiểu trừu tượng nào đó) với mục tiêu, điểm thấp hơn sẽ tốt hơn (0 có nghĩa là bạn đã tìm thấy mục tiêu). Bạn chọn sử dụng tiếp theo bằng cách tìm mức tối thiểu của điểm cộng với số nút cách xa gốc (thường là cấu hình hiện tại hoặc vị trí hiện tại trong tìm đường). Mỗi lần bạn khám phá một nút, bạn thêm tất cả các con của nó vào danh sách này và sau đó chọn nút mới tốt nhất.


3

Ở mức độ trừu tượng, A * hoạt động như thế này:

  • Bạn coi thế giới là một số nút riêng biệt được kết nối, vd. một lưới, hoặc một biểu đồ.
  • Để tìm một con đường xuyên qua thế giới đó, bạn cần tìm một danh sách các 'nút' liền kề trong không gian đó, dẫn từ đầu đến mục tiêu.
  • Cách tiếp cận ngây thơ sẽ là thế này: tính toán mọi hoán vị có thể có của các nút bắt đầu bằng nút bắt đầu và kết thúc ở nút cuối và chọn giá rẻ nhất. Điều này rõ ràng sẽ mất mãi mãi trên tất cả nhưng không gian nhỏ nhất.
  • Do đó, các phương pháp thay thế cố gắng sử dụng một số kiến ​​thức về thế giới để đoán xem những hoán vị nào đáng để xem xét trước tiên và để biết liệu một giải pháp nhất định có thể bị đánh bại hay không. Ước tính này được gọi là heuristic.
  • A * đòi hỏi một heuristic được chấp nhận . Điều này có nghĩa là nó không bao giờ đánh giá quá cao.
    • Một heuristic tốt cho các vấn đề tìm đường là khoảng cách Euclide vì chúng ta biết rằng con đường ngắn nhất giữa 2 điểm là một đường thẳng. Điều này không bao giờ đánh giá quá cao khoảng cách trong các mô phỏng trong thế giới thực.
  • A * bắt đầu bằng nút bắt đầu và thử các hoán vị liên tiếp của nút đó cộng với từng hàng xóm và hàng xóm của neighbour, v.v., sử dụng phương pháp phỏng đoán để quyết định hoán vị nào sẽ thử tiếp theo.
  • Ở mỗi bước, A * nhìn vào con đường hứa hẹn nhất từ ​​trước đến nay và chọn nút lân cận tiếp theo có vẻ là 'tốt nhất', dựa trên quãng đường di chuyển cho đến nay, và ước tính của heuristic về việc sẽ đi được bao xa từ đó nút.
  • Bởi vì heuristic không bao giờ đánh giá quá cao và khoảng cách di chuyển cho đến nay được biết là chính xác, nó sẽ luôn chọn bước tiếp theo lạc quan nhất.
    • Nếu bước tiếp theo đạt được mục tiêu, bạn biết rằng nó đã tìm ra con đường ngắn nhất từ ​​vị trí cuối cùng, bởi vì đây là dự đoán lạc quan nhất về những cái hợp lệ còn lại.
    • Nếu nó không đạt được mục tiêu, nó sẽ là một điểm có thể để khám phá sau này. Thuật toán bây giờ chọn khả năng hứa hẹn nhất tiếp theo, vì vậy logic trên vẫn được áp dụng.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.