Áp dụng tìm kiếm mù giải bài toán đố chở thuyền




Bài toán: Người và sói
Trên một vùng đất nọ, có 3 người và 3 con sói cùng sinh sống. Bỗng một hôm, vì nơi đây gặp thiên tai, họ phải di chuyển đến vùng đất khác nằm ở bên kia con sông. Chỉ có một chiếc thuyền trên sông, và mỗi thuyền chứa tối đa 2 người, tối thiểu là 1 người. Có một điều nguy hiểm rằng ở vùng đất cũ khi mà số lượng sói lớn hơn số lượng người thì người sẽ bị ăn thịt. Bạn hãy đưa ra một cách mà có thể đưa người và sói sang sông một cách an toàn mà không gặp bất kì trở ngại nào.
Giải:
Ta chọn biến đổi bài toán về không gian trạng thái:
-        Dạng biểu diễn trạng thái: Cây tìm kiếm – cụ thể: Tìm kiếm mù
-        Trạng thái: Một bộ ba (a,b,k) trong đó
o   Hệ số a: Số lượng người ở vùng đất cũ
o   Hệ số b: Số lượng sói ở vùng đất cũ
o   Hệ số k: Trạng thái của chiếc thuyền (1 khi có thuyền để đi, 0 khi không có thuyền để đi)
ð  Trạng thái ban đầu (3,3,1)
ð  Trạng thái kết thúc (0,0,0)
-        Toán tử: Chiếc thuyền chở người qua
o   Cụ thể: Giả sử tại trang thái ban đầu thuyền chở 1 người 1 sói qua. Khi đó: (3, 3, 1) (1,1)à (2,2,0); Sau đó người chèo thuyền về, khi đó (2,2,0) (1,0)-> (3,2,1)
ð  Toán tử là một bộ số (a,b) đại diện cho số lượng người và sói ở trên thuyền, còn hành động đi hay về dựa vào hệ số k.
Chuyển đổi không gian tìm kiếm trạng thái về cây tìm kiếm:
Ví dụ với độ sâu d = 2

Lựa chọn loại tìm kiếm:
Ta chọn tìm kiếm theo chiều rộng hoặc tìm kiếu sâu lặp.
Ta không thể chọn tìm kiếm theo chiều sâu. Lí do: Sẽ có trường hợp lặp vô hạn. Đó là trường hợp một đối tượng đi thuyền qua bên kia, rồi đi thuyền quay trở lại chỗ cũ
ð  Ta lựa chọn tìm kiếm theo chiều rộng
*Thuật toán
Nhắc lại về tìm kiếm theo chiều rộng: Ta sẽ ưu tiên phát triển trạng thái nào sinh ra trước
Ta sử dụng phương pháp hàng đợi (queue) để xử lí bài toán
Cho một tập L chứa các đỉnh (trạng thái) của cây tìm kiếm. L = {(3, 1, 1)}
Trạng thái ở vị trí đầu tiên danh sách L gọi là u0
Bước 1: for do
               Bước 1.1: if (L = {}) then Trả kết quả không tìm thấy; Kết thúc;
Bước 1.2: Kiểm tra u0:
Bước 1.2.1: if (u0 có dạng (0, 0, 0)) then Trả kết quả; Kết thúc;
Bước 1.2.2: if (u0 có b>a) then xóa u0 và continue;
Bước 1.3: Phát triển u0 ra tập V gồm các trạng thái {v0, v1, …, vn-1}
Bước 1.4: Xóa trạng thái u0 ra khỏi danh sách L
Bước 1.5: Thêm tập V vào cuối danh sách L
Phân tích quá trình phát triển trạng thái u tại bước 1.3:
Xét ví dụ trên, trích:
o   “Cụ thể: Giả sử tại trang thái ban đầu thuyền chở 1 người 1 sói qua. Khi đó: (3, 3, 1) (1,1)à (2,2,0); Sau đó người chèo thuyền về, khi đó (2,2,0) (1,0)-> (3,2,1)”
Với (a, b, k) là trạng thái, và (x, y) là toán tử. Ta thấy rằng
(a, b, k) (x, y)-> (a-x, b-y, !k) khi k = 1
(a, b, k) (x, y)-> (a+x, b+y, !k) khi k = 0
ð  Đây là công thức toán tử cho quá trình phát triển trạng thái
Các toán tử sử dụng: 5 toán tử
(0, 1)(1, 0)(1, 1)(2, 0)(0, 2)
Tuy nhiên không phải lúc nào một trạng thái cũng có thể sử dụng cả 5 toán tử:
VD: (2, 1, 1) thì toán tử sử dụng là (0, 1)(1, 0)(1, 1)(2, 0)
        (2, 1, 0) thì toán tử sử dụng là (0, 1)(1,0)(1, 1)(0,2)
Dựa vào công thức toán tử cho quá trình phát triển trạng thái, ta rút ra được
(x,y) nhận khi
               x <= a và y <= b khi k = 1
               x <= (3 – a) và y <= (3 – b) khi k = 0;
Output: Kết quả ta tìm kiếm không phải là trạng thái cuối cùng, mà là một chuỗi trạng thái nối liền nhau bắt nguồn từ trạng thái đầu tiên đến trạng thái cuối cùng
Khi đã tới trạng thái cuối cùng, để biết được các trạng thái từ trạng thái đầu tiên đến trạng thái cuối cùng:
1>    Ta phải giữ lại danh sách trạng thái đã phát triển (Tạo một danh sách Q chứa các trạng thái đã phát triển)
2>    Ta phải đánh dấu được với mỗi trạng thái v nào đó sẽ có một trạng thái cha là u tương ứng
Ta sẽ sửa lại thuật toán như sau:
Bước 1.4: Chuyển trạng thái u0 vào trong tập T (thêm vào cuối tập T)
Bước 1.3: Định nghĩa lại một node nào đó là một đối tượng gồm 2 trường:
               + Trường 1 (me): Lưu trữ trạng thái hiện tại vi
+ Trường 2 (parent): Lưu trữ trạng thái cha, là trạng thái phát sinh ra trạng thái vi
               (Với trạng thái ban đầu, thì trường parent sẽ là null)
Như vậy, “Trả kết quả” ở đây sẽ trả về một chuỗi các trạng thái từ trạng thái bắt đầu đến trạng thái kết thúc, và sẽ được tạo như sau
Cho vfinal là trạng thái cuối cùng tìm được, với tập T = {q0, q1, …, q n-1}
Cho một danh sách R với giá trị ban đầu là {vfinal}, danh sách R là kết quả của bài toán
               Bước 1: Đặt t = vfinal
Bước 2: for tập T dò từ dưới lên trên
                              Bước 2.1: If t.parent == qi.me nào đó then
                                             Bước 2.1.1: Ghi lại qi.me vào R theo hướng chèn vào đầu danh sách
                                             Bước 2.1.2: Gán t = qi
Hoàn thiện bài toán!
Tổng kết lại các bước giải bài toán:
Một đỉnh (node) của cây tìm kiếm là một đối tượng xác định bởi 2 trạng thái
               + Trường 1 (me): Lưu trữ trạng thái hiện tại vi
+ Trường 2 (parent): Lưu trữ trạng thái cha, là trạng thái phát sinh ra trạng thái vi
Các bước giải bài toán:
Bước 1: for do
               Bước 1.1: if (L = {}) then Trả kết quả không tìm thấy; Kết thúc;
Bước 1.2: Kiểm tra u0:
Bước 1.2.1: if (u0 có dạng (0, 0, 0)) then Trả kết quả(u0); Kết thúc;
Bước 1.2.2: if (u0 có b>a) then xóa u0 và continue;
Bước 1.3: Phát triển u0 ra tập V gồm các trạng thái {v0, v1, …, vn-1}
Bước 1.4: Chuyển trạng thái u0 vào trong tập T (thêm vào cuối tập T)
Bước 1.5: Thêm tập V vào cuối danh sách L
Hàm trả kết quả như đã ghi ở trên.
Code mô tả bài toán:


Nội dung bài viết được tạo bởi Lê Công Diễn.

Người viết: Lê Công Diễn
Mang đi nhớ ghi nguồn




Nhận xét

Bài đăng phổ biến từ blog này

Deploy project Springboot MIỄN PHÍ sử dụng Render

Ứng dụng Mã hóa bất đối xứng (Asymmetric cryptography) vào Chữ ký số (Digital Signature)

API và HTTP - Một số khái niệm cơ bản cần biết về Web (Phần 2)