Theo dõi các quốc gia đã truy cập trong Tìm kiếm đầu tiên theo chiều rộng


10

Vì vậy, tôi đã cố gắng thực hiện BFS trên câu đố Sliding Blocks (loại số). Bây giờ điều chính tôi nhận thấy là nếu bạn có một 4*4bảng, số lượng trạng thái có thể lớn đến 16!mức tôi không thể liệt kê tất cả các trạng thái trước đó.

Vì vậy, câu hỏi của tôi là làm thế nào để tôi theo dõi các tiểu bang đã truy cập? (Tôi đang sử dụng một bảng lớp, mỗi thể hiện của lớp chứa một mẫu bảng duy nhất và được tạo bằng cách liệt kê tất cả các bước có thể từ bước hiện tại).

Tôi đã tìm kiếm trên mạng và dường như họ không quay lại bước vừa hoàn thành trước đó, NHƯNG chúng ta cũng có thể quay lại bước trước bằng một tuyến đường khác và sau đó lại liệt kê lại tất cả các bước đã được truy cập trước đó. Vậy làm thế nào để theo dõi các quốc gia đã truy cập khi tất cả các tiểu bang chưa được liệt kê? (so sánh các trạng thái đã có với bước hiện tại sẽ tốn kém).


1
Lưu ý bên lề: Tôi không thể nghĩ ra một Stack thích hợp hơn để đăng câu hỏi này. Tôi biết chi tiết triển khai thường không được chào đón trong Stack này.
DuttaA

2
Imo đây là một câu hỏi tuyệt vời cho SE: AI bởi vì nó không chỉ là về triển khai, mà còn là khái niệm. Chưa kể, câu hỏi đã thu hút 4 câu trả lời hợp pháp trong vài giờ. (Lấy quyền tự do chỉnh sửa tiêu đề để tìm kiếm và tạo thẻ BFS)
DukeZhou

Câu trả lời:


8

Bạn có thể sử dụng một set(theo nghĩa toán học của từ này, tức là một bộ sưu tập không thể chứa các bản sao) để lưu trữ các trạng thái mà bạn đã thấy. Các hoạt động bạn sẽ cần để có thể thực hiện trên này là:

  • yếu tố chèn
  • kiểm tra nếu các yếu tố đã có trong đó

Khá nhiều ngôn ngữ lập trình nên đã có hỗ trợ cho cấu trúc dữ liệu có thể thực hiện cả hai thao tác này trong thời gian không đổi ( ). Ví dụ:O(1)

  • set trong Python
  • HashSet trong Java

Thoạt nhìn, có vẻ như việc thêm tất cả các trạng thái bạn từng thấy vào một tập hợp như thế này sẽ rất tốn kém về trí nhớ, nhưng nó không quá tệ so với bộ nhớ bạn cần cho biên giới của bạn; nếu hệ số phân nhánh của bạn là , biên giới của bạn sẽ tăng thêm b - 1 phần tử cho mỗi nút mà bạn truy cập (xóa 1 nút khỏi biên giới để "truy cập" nó, thêm b người kế thừa / con mới), trong khi đó, bộ của bạn sẽ chỉ tăng thêm 1 nút trên mỗi nút truy cập.bb11b1

Trong mã giả, một bộ như vậy (hãy đặt tên cho nó closed_set, để phù hợp với mã giả trên wikipedia có thể được sử dụng trong Tìm kiếm đầu tiên theo chiều rộng như sau:

frontier = First-In-First-Out Queue
frontier.add(initial_state)

closed_set = set()

while frontier not empty:
    current = frontier.remove_next()

    if current == goal_state:
        return something

    for each child in current.generate_children()
        if child not in closed_set:    // This operation should be supported in O(1) time regardless of closed_set's current size
            frontier.add(child)

    closed_set.add(current)    // this should also run in O(1) time

(một số biến thể của mã giả này cũng có thể hoạt động và hiệu quả hơn hoặc ít hơn tùy thuộc vào tình huống; ví dụ: bạn cũng có thể lấy closed_settất cả các nút mà bạn đã thêm trẻ em vào biên giới, và sau đó hoàn toàn tránh generate_children()cuộc gọi nếu currentđã có trong closed_set.)


Những gì tôi mô tả ở trên sẽ là cách tiêu chuẩn để xử lý vấn đề này. Theo trực giác, tôi nghi ngờ một "giải pháp" khác có thể là luôn luôn ngẫu nhiên hóa thứ tự của một danh sách mới của các quốc gia kế nhiệm trước khi thêm chúng vào biên giới. Bằng cách này, bạn không tránh khỏi vấn đề thỉnh thoảng thêm các trạng thái mà trước đó bạn đã mở rộng ra biên giới, nhưng tôi nghĩ rằng nó sẽ làm giảm đáng kể nguy cơ bị mắc kẹt trong các chu kỳ vô hạn.

Hãy cẩn thận : Tôi không biết bất kỳ phân tích chính thức nào về giải pháp này chứng tỏ rằng nó luôn luôn tránh các chu kỳ vô hạn. Nếu tôi cố gắng "chạy" cái này qua đầu, bằng trực giác, tôi nghi ngờ nó nên hoạt động tốt và nó không cần thêm bộ nhớ. Có thể có những trường hợp cạnh mà tôi không nghĩ đến ngay bây giờ, vì vậy nó cũng đơn giản là không hoạt động, giải pháp tiêu chuẩn được mô tả ở trên sẽ là đặt cược an toàn hơn (với chi phí bộ nhớ nhiều hơn).


1
Tôi sẽ có thể làm điều đó nhưng thời gian so sánh sẽ bắt đầu tăng theo cấp số nhân
DuttaA

3
SO(1)S

1
@DuttaA Tôi đã thêm một số mã giả để mô tả chính xác cách sử dụng bộ này, hy vọng điều đó có thể làm rõ điều gì đó. Lưu ý rằng chúng ta không bao giờ lặp lại toàn bộ closed_set, kích thước của nó sẽ không bao giờ ảnh hưởng đến thời gian tính toán (tiệm cận) của chúng ta.
Dennis Soemers

1
Trên thực tế tôi đã làm điều đó bằng c ++ Tôi không có ý tưởng nào về việc băm ... Đoán xem bây giờ tôi sẽ sử dụng python ... Cảm ơn câu trả lời
DuttaA

3
@DuttaA Trong C ++, có lẽ bạn muốn sử dụng std :: unordered_set
Dennis Soemers

16

Câu trả lời của Dennis Soemers là chính xác: bạn nên sử dụng Hashset hoặc cấu trúc tương tự để theo dõi các trạng thái đã truy cập trong Tìm kiếm đồ thị BFS.

Tuy nhiên, nó không hoàn toàn trả lời câu hỏi của bạn. Bạn nói đúng, trong trường hợp xấu nhất, BFS sau đó sẽ yêu cầu bạn lưu trữ 16! điểm giao. Mặc dù thời gian chèn và kiểm tra trong bộ sẽ là O (1), bạn vẫn sẽ cần một lượng bộ nhớ vô lý.

Để khắc phục điều này, đừng sử dụng BFS . Nó không thể khắc phục được cho tất cả nhưng vấn đề đơn giản nhất, bởi vì nó đòi hỏi cả thời gian và bộ nhớ theo cấp số nhân trong khoảng cách đến trạng thái mục tiêu gần nhất.

Một thuật toán hiệu quả hơn nhiều bộ nhớ là lặp đi lặp lại sâu . Nó có tất cả các thuộc tính mong muốn của BFS, nhưng chỉ sử dụng bộ nhớ O (n), trong đó n là số lần di chuyển để đạt được giải pháp gần nhất. Có thể vẫn mất một lúc, nhưng bạn sẽ đạt giới hạn bộ nhớ rất lâu trước các giới hạn liên quan đến CPU.

Vẫn tốt hơn, phát triển một heuristic cụ thể miền và sử dụng tìm kiếm A * . Điều này sẽ yêu cầu bạn chỉ kiểm tra một số lượng rất nhỏ các nút và cho phép tìm kiếm hoàn thành trong một cái gì đó gần với thời gian tuyến tính hơn.


2
16!

@DennisSoemers là chính xác..Bạn cũng đúng..Tôi chỉ đang cố gắng trau dồi kỹ năng của mình ... Tôi sẽ chuyển sang các phương pháp tìm kiếm nâng cao hơn sau này
DuttaA

Có trường hợp nào BFS có thể trả về các giải pháp địa phương chấp nhận được không? (Tôi đang xử lý nhiều hơn 81 "hiệu suất chung yếu trên một loạt các cấu trúc liên kết trò chơi không thể đoán trước.)
DukeZhou

2
@DukeZhou BFS thường chỉ được sử dụng khi tìm kiếm một giải pháp hoàn chỉnh. Để ngăn chặn sớm, bạn cần một hàm ước tính chất lượng tương đối của các giải pháp từng phần khác nhau, nhưng nếu bạn có chức năng như vậy, có lẽ bạn chỉ có thể sử dụng A *!
John Doucette

Thay vì nói "số lần di chuyển trong trò chơi", tôi khuyên bạn nên "số lần di chuyển tối thiểu để đạt được mục tiêu". Tôi nghĩ về số lần di chuyển trong trò chơi là mỗi lần di chuyển đưa bạn từ một trong số 16! trạng thái cho bất kỳ khác, đó là bộ nhớ nhiều hơn nhiều so với sử dụng sâu lặp đi lặp lại.
Không phải

7

Mặc dù các câu trả lời được đưa ra nói chung là đúng, nhưng một BFS trong câu đố 15 không chỉ hoàn toàn khả thi, nó đã được thực hiện vào năm 2005! Bài viết mô tả cách tiếp cận có thể được tìm thấy ở đây:

http://www.aaai.org/Papers/AAAI/2005/AAAI05-219.pdf

Một vài điểm chính:

  • Để thực hiện việc này, cần có bộ nhớ ngoài - đó là BFS đã sử dụng ổ cứng để lưu trữ thay vì RAM.
  • Thực tế chỉ có 15! / 2 trạng thái, vì không gian trạng thái có hai thành phần không thể truy cập lẫn nhau.
  • Điều này hoạt động trong câu đố gạch trượt vì không gian trạng thái phát triển rất chậm từ cấp này sang cấp khác. Điều này có nghĩa là tổng bộ nhớ cần thiết cho bất kỳ mức nào nhỏ hơn nhiều so với kích thước đầy đủ của không gian trạng thái. (Điều này tương phản với một không gian trạng thái như Rubik's Cube, nơi không gian trạng thái phát triển nhanh hơn nhiều.)
  • Bởi vì câu đố gạch trượt không bị ảnh hưởng, bạn chỉ phải lo lắng về các bản sao trong lớp hiện tại hoặc trước đó. Trong một không gian được định hướng, bạn có thể tạo các bản sao trong bất kỳ lớp tìm kiếm nào trước đó, điều này làm cho mọi thứ phức tạp hơn nhiều.
  • Trong tác phẩm gốc của Korf (được liên kết ở trên), họ không thực sự lưu trữ kết quả tìm kiếm - tìm kiếm chỉ tính toán có bao nhiêu trạng thái ở mỗi cấp. Nếu bạn muốn lưu trữ các kết quả đầu tiên, bạn cần một cái gì đó như WMBFS ( http://www.cs.du.edu/~sturtevant/ con / bfs_min_write.pdf )
  • Có ba cách tiếp cận chính để so sánh các trạng thái từ các lớp trước khi các trạng thái được lưu trữ trên đĩa.
    • Đầu tiên là phân loại dựa trên. Nếu bạn sắp xếp hai tệp kế thừa, bạn có thể quét chúng theo thứ tự tuyến tính để tìm các bản sao.
    • Thứ hai là dựa trên hàm băm. Nếu bạn sử dụng hàm băm để nhóm người kế vị thành các tệp, bạn có thể tải các tệp nhỏ hơn không gian trạng thái đầy đủ để kiểm tra các bản sao. (Lưu ý rằng có hai hàm băm ở đây - một để gửi trạng thái cho tệp và một để phân biệt các trạng thái trong tệp đó.)
    • Thứ ba là cấu trúc phát hiện trùng lặp. Đây là một hình thức phát hiện dựa trên hàm băm, nhưng nó được thực hiện theo cách có thể kiểm tra các bản sao ngay lập tức khi chúng được tạo thay vì sau khi tất cả chúng được tạo.

Có rất nhiều điều để nói ở đây, nhưng (các) bài viết ở trên cung cấp nhiều chi tiết hơn.


Đó là một câu trả lời tuyệt vời..nhưng không dành cho những người như tôi :) ... Tôi không phải là chuyên gia lập trình viên ..
DuttaA

Làm thế nào vô hướng sẽ giúp bạn tránh trùng lặp trong các lớp khác? Chắc chắn bạn sẽ có thể quay lại một nút trong một lớp khác bằng cách di chuyển 3 ô trong một vòng tròn. Nếu bất cứ điều gì, được chỉ dẫn sẽ giúp bạn tránh trùng lặp, bởi vì nó hạn chế hơn. Bài báo được liên kết nói về phát hiện trùng lặp, nhưng hoàn toàn không đề cập đến hướng dẫn hoặc không được hướng dẫn, dường như cũng không đề cập đến việc tránh trùng lặp ở các cấp độ khác nhau (nhưng tôi có thể đã bỏ lỡ điều đó trong lần quét rất ngắn của mình).
Không phải

@NotThatGuy Trong một đồ thị không xác định, cha mẹ và con cái cách nhau tối đa 1 độ sâu mà chúng được tìm thấy trong BFS. Điều này là bởi vì một khi một cái được tìm thấy, cạnh không được bảo đảm rằng cái kia sẽ được tìm thấy ngay sau đó. Nhưng, trong biểu đồ có hướng, trạng thái ở độ sâu 10 có thể tạo ra trẻ em ở độ sâu 2, bởi vì trẻ ở độ sâu 2 không cần phải quay lại trạng thái khác (điều này sẽ làm cho độ sâu 3 thay vì độ sâu 10) .
Nathan S.

@NotThatGuy Nếu bạn di chuyển 3 ô trong một vòng tròn bạn tạo một chu kỳ, nhưng BFS sẽ khám phá điều đó theo cả hai hướng, vì vậy nó sẽ không thực sự đưa bạn trở lại độ sâu nông hơn. Ngói trượt 3x2 đầy đủ được hiển thị trong bản demo này và bạn có thể theo dõi các chu kỳ để xem chúng diễn ra như thế nào: moveai.com/SAS/IDA
Nathan S.

1
sự tuyệt vời Chào mừng bạn đến với SE: AI!
DukeZhou

3

Trớ trêu thay câu trả lời là "sử dụng bất cứ hệ thống nào bạn muốn." Một hashset là một ý tưởng tốt. Tuy nhiên, nó chỉ ra rằng mối quan tâm của bạn về việc sử dụng bộ nhớ là không có cơ sở. BFS rất tệ trong các loại vấn đề này, nó giải quyết vấn đề này cho bạn.

Hãy xem xét rằng BFS của bạn yêu cầu bạn giữ một chồng các trạng thái chưa được xử lý. Khi bạn tiến vào câu đố, các trạng thái bạn đối phó ngày càng trở nên khác biệt, do đó bạn có thể thấy rằng mỗi lớp BFS của bạn nhân số lượng trạng thái để xem xét khoảng 3.

Điều này có nghĩa là, khi bạn xử lý lớp cuối cùng của BFS, bạn phải có ít nhất 16! / 3 trạng thái trong bộ nhớ. Bất kỳ cách tiếp cận nào bạn đã sử dụng để đảm bảo phù hợp với bộ nhớ sẽ đủ để đảm bảo danh sách được truy cập trước đó của bạn cũng phù hợp với bộ nhớ.

Như những người khác đã chỉ ra, đây không phải là thuật toán tốt nhất để sử dụng. Sử dụng một thuật toán phù hợp hơn cho vấn đề.


2

Bài toán 15 câu đố được chơi trên một bảng 4 x 4. Việc thực hiện điều này trong sourcecode được thực hiện từng bước. Lúc đầu, bản thân công cụ trò chơi phải được lập trình. Điều này cho phép chơi trò chơi bởi một nhà điều hành con người. Trò chơi 15 câu đố chỉ có một yếu tố miễn phí và trên yếu tố này, các hành động được thực thi. Công cụ trò chơi chấp nhận bốn lệnh có thể: trái, phải, lên và xuống. Các hành động khác không được phép và chỉ có thể điều khiển trò chơi bằng các hướng dẫn này.

Lớp tiếp theo để chơi trò chơi là GUI. Điều này rất quan trọng, vì nó cho phép kiểm tra công cụ trò chơi và cố gắng giải quyết trò chơi bằng tay. Ngoài ra, GUI rất quan trọng vì chúng ta cần tìm ra các heuristic tiềm năng. Và bây giờ chúng ta có thể nói về chính AI. AI phải gửi lệnh đến công cụ trò chơi (trái, phải, lên và xuống). Một cách tiếp cận ngây thơ cho người giải sẽ là thuật toán tìm kiếm vũ phu, có nghĩa là AI đang gửi các lệnh ngẫu nhiên cho đến khi đạt được trạng thái mục tiêu. Một ý tưởng nâng cao hơn là triển khai một số loại cơ sở dữ liệu mẫu làm giảm không gian trạng thái. Bề rộng tìm kiếm đầu tiên không trực tiếp là một heuristic, nhưng nó là một sự khởi đầu. Nó là bằng nhau để tạo ra một biểu đồ để kiểm tra các chuyển động có thể theo cách theo thời gian.

Theo dõi các trạng thái hiện có có thể được thực hiện với một biểu đồ. Mỗi trạng thái là một nút, có id và id cha. AI có thể thêm và xóa các nút trong biểu đồ và trình hoạch định có thể giải quyết biểu đồ để tìm đường dẫn đến mục tiêu. Từ góc độ lập trình, một công cụ trò chơi của 15 câu đố là đối tượng và danh sách nhiều đối tượng là một danh sách mảng. Chúng được lưu trữ trong một lớp biểu đồ. Nhận ra điều này trong mã nguồn là một chút khó khăn, thường thì thử nghiệm đầu tiên sẽ thất bại và dự án sẽ tạo ra rất nhiều lỗi. Để quản lý sự phức tạp, một dự án như vậy thường được thực hiện trong một dự án học thuật, điều đó có nghĩa, đó là một chủ đề để viết một bài báo về nó có thể có 100 trang trở lên.


1

Phương pháp tiếp cận trò chơi

16!

Tuy nhiên, những sự thật tầm thường đó không thích hợp nếu mục tiêu là hoàn thành câu đố trong vài chu kỳ tính toán ít nhất. Bề rộng tìm kiếm đầu tiên không phải là một cách thực tế để hoàn thành một câu đố di chuyển trực giao. Chi phí rất cao cho lần tìm kiếm đầu tiên sẽ chỉ cần thiết nếu số lần di chuyển là rất quan trọng vì một số lý do.

Trình tự phụ

Hầu hết các đỉnh đại diện cho các trạng thái sẽ không bao giờ được truy cập và mỗi trạng thái được truy cập có thể có từ hai đến bốn cạnh đi. Mỗi khối có một vị trí ban đầu và một vị trí cuối cùng và bảng là đối xứng. Sự tự do lựa chọn lớn nhất tồn tại khi không gian mở là một trong bốn vị trí ở giữa. Ít nhất là khi không gian mở là một trong bốn vị trí góc.

Hàm chênh lệch (lỗi) hợp lý chỉ đơn giản là tổng của tất cả các chênh lệch x cộng với tổng của tất cả các chênh lệch y và một số đại diện theo phương pháp heuristist trong số ba mức tự do di chuyển tồn tại do vị trí của không gian mở (giữa, cạnh , góc).

Mặc dù các khối có thể tạm thời di chuyển khỏi các điểm đến của chúng để hỗ trợ chiến lược hoàn thành cần một chuỗi các bước di chuyển, hiếm khi có một chiến lược như vậy vượt quá tám lần di chuyển, tạo ra, trung bình, 5.184 hoán vị mà các trạng thái cuối cùng có thể được so sánh sử dụng hàm chênh lệch ở trên.

Nếu không gian trống và vị trí của khối 1 đến 15 được mã hóa dưới dạng một mảng của nibble, chỉ cần các phép toán cộng, trừ và bit khôn ngoan, làm cho thuật toán nhanh. Lặp đi lặp lại tám chiến lược vũ phu có thể được lặp đi lặp lại cho đến khi chênh lệch giảm xuống không.

Tóm lược

Thuật toán này không thể quay vòng vì luôn có ít nhất một trong số các hoán vị của tám lần di chuyển làm giảm chênh lệch, bất kể trạng thái ban đầu, ngoại trừ trạng thái bắt đầu đã hoàn tất.

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.