Cong ty Cong Nghe Tin hoc Nha truong http://www.vnschool.net
Duyệt từ 2 phía
31/07/2010

Duyệt từ 2 phía là 1 cách duyệt toàn bộ có tốc độ có thể chấp nhận được.

Duyệt từ 2 phía ứng dụng trong 1 số bài toán biến đổi, cho 2 trạng thái đầu và cuối và tập các biến đổi. Yêu cầu sử dụng các phép biến đổi đó để chuyển trạng thái đầu về trạng thái cuối.


Phương pháp : sử dụng các phép biến đổi, biến đổi trạng thái đầu để tạo ra tất cả các khả năng có thể có sau 1 số bước. ( có thể khai báo CONST hoặc tính toán trong chương trình tuỳ thuộc vào bộ nhớ là chủ yếu ), lưu lại tất cả các trạng thái này. Sử dụng các phép biến đôỉ ngược lại biến đổi trạng thái đích ngược trở lại. Được trạng thái nào thì so sánh với các trạng thái được lưu.


Phân tích thuật toán : Ta đi vào 1 trường hợp đặc biệt, giả sử cây biến đổi là 1 cây nhị phân đầy đủ hoàn chỉnh. Các nút đôi một khác nhau. Như vậy sau 2*N bước biến đôỉ, ta các số các trạng thái có thể sinh ra ( các trạng thái lá) là 2^(2N). Bây giờ ta xét phương pháp duyệt từ 2 phía : Tối ưu nhất là ta duỵêt N bước có thể biến đổi. Vậy số trạng thái ( số nút lá) là 2^N. Lưu lại tất cả các trạng thái này. Duỵêt ngược lại từ trạng thái đích : mất thêm 2^N trạng thái nữa. Duỵêt lần 2, ta cài bằng Stack ( tự quản lí hay đệ qui đều được ). So sánh các trạng thái với nhau, nếu có 1 thuật toán so sánh tốt, ta chỉ cần độ phức tạp tính toán là 2^(N+1), độ phức tạp bộ nhớ là (2^N + N). Còn thuật toán ban đầu là 2^(2N) cho độ phức tạp thuật toán, 2N cho độ phức tạp bộ nhớ.


Các phương pháp so sánh : tôi đã nói ở trên, đây là 1 bài toán khó vì phải sử dụng khá nhiếu kiến thức từ các mảng khác nhau trong Tin học. Chủ yếu là ở phần so sánh các trạng thái. Nếu ta không có thuật toán so sánh tốt thì phài so sánh toàn bộ.Độ phức tạp so sánh lúc đó là 2^N Độ phức tạp lúc ấy sẽ là 2^N*2^N=2^(2N) bằng thuật toán ban đầu. Sau đây là 1 số phương pháp so sánh tốt : sử dụng tìm kiếm nhị phân kết hợp với phương pháp mã hoá.

Các bước thực hiện : tìm các mã hoá mỗi trạng thái với 1 giá trị nguyên A nào đó. Các trạng thái mã hoá này sẽ được sắp xếp lại theo giá trị các khoá. Khi ta có 1 trạng thái ngược ( là trạng thái sinh ra sau khi ta sinh ngược từ giá trị đích). ta cũng mã hoá ra số B. Sau đó tìm kiêm giá trị B xem đã xuất hiện chưa. Ở đây, có 2 các tìm là :

1. Nếu max(A) nhỏ, ta sẽ dùng xử lí bít để đánh dấu, sau đó tìm bằng 1 phép kiểm tra. Độ phức tạp cho phép kiểm tra này là O(1).

2. Nếu max(A) lớn, ta sort lại các giá trị khoá A này theo giá trị. ( thực ra còn 1 cách khác là sort lại các trạng thái mà không cần mã hoá. Thường là sort theo thứ tự từ điển.). Sau đó ta dùng phương pháp tìm kiếm nhị phân để tìm trong dãy khoá đã được sort. Độ phức tạp là O(lg(2^N)=N).

Như vậy là độ phức tạp nếu dùng cách 2 là (2^N)*N rất nhỏ so với 2^(2N).


Các phương pháp mã hoá :

Các phương pháp mã hoá thường phải thoả mãn 1 số tính chất sau đây :

1. Tính duy nhất : ( điều kiện bắt buộc ). Với mỗi trạng thái chỉ sinh ra duy nhất 1 khoá và với 1 khoá chỉ cho duy nhất 1 trạng thái.

2. Tính đơn giản : ( không bắt buộc ) Cách mã hoá phải càng đơn giản càng tốt.

3. Tính suy ngược ( không bắt buộc ): Với 1 khoá ta có thể tính ra trạng thái.


Nói cụ thể các phương pháp mã hoá cần 1 bài viết khác đầy đủ hơn. Bạn có thể tham khảo trong các số báo trước về cách nén dữ liệu.Sau đây chỉ nói đến 1 vài ví dụ đơn giản để các bạn dễ hình dung.

1. Mã hoá xâu nhị phân.

2. Mã hoá Hoán vị.

3. Mã hoá tổ hợp.

4. Mã hoá chỉnh hợp….


Sau đây là 1 bài toán và phân tích thuật toán để làm rõ thêm về pp Duyệt từ 2 phía.


Đề bài :

ĐẨY QUÂN


Tên chương trình:MOVE.PAS

Trò chơi Dao do Jeff Pickering và Ben van Burkirk đề xuất năm 1999. Đó là trò chơi 2 người. Ở đây chúng ta xét phương án cải tiến thành trò một người chơi. Xét bảng kích thước 4x4 ô, trên đó có 4 quân cờ trắng và 4 quân cờ đen. Ban đầu các quân cờ được đặt một cách ngẫu nhiên. Người chơi phải đưa bảng các quân cờ về trạng thái cuối cho trước theo các quy tắc di chuyển sau:

• Đi lần lượt quân trắng- quân đen, bắt đầu từ quân trắng,

• Quân có thể đi theo hàng ngang, hàng dọc hoặc chéo cho tới khi bị chặn bởi quân đã có hay mép bàn cờ, không được ăn hoặc nhảy qua quân khác,

• Ở mỗi bước phải di chuyển quân màu tương ứng, không được bỏ qua nước đi.

Ví dụ, các nước đi sau là hợp lệ:

Trong trường hợp này, 4 nước đi đã được thực hiện.

Tuy vậy, ta cũng có thể nhận được kết quả cuối như trên chỉ với 3 nước đi:

Yêu cầu: Cho trạng thái đầu và cuối của bàn cờ. Hãy xác định số bước đi tối thiểu để từ trạng thái đầu nhận được trạng thái cuối.

Dữ liệu: Vào từ file văn bản MOVE.INP:

• Dòng chứa số nguyên t - số lượng Tests,

• Thông tin về mỗi Test được đưa trên 8 dòng, 4 dòng đầu mô tả trạng thái đầu của bảng, 4 dòng sau – mô tả trạng thái cuối. Các bảng được mô tả theo dòng, từ trên xuống dưới, mỗi dòng được mô tả bằng một xâu 4 ký tự, trong đó w chỉ quân trắng, b – quân đen và * - vị trí trống.

Kết quả: Đưa ra file văn bản MOVE.OUT đưa ra số lượng bước đi tối thiểu cho từng Test, mỗi kết quả đưa ra trên một dòng.

Ví dụ:

Phương pháp tôi không nói lại cụ thể, tôi chỉ phân tích những chỗ cần thiết để làm rõ thêm.


Các phép biến đổi :

Biến đổi xuôi :

Người chơi phải đưa bảng các quân cờ về trạng thái cuối cho trước theo các quy tắc di chuyển sau:

• Đi lần lượt quân trắng- quân đen, bắt đầu từ quân trắng,

• Quân có thể đi theo hàng ngang, hàng dọc hoặc chéo cho tới khi bị chặn bởi quân đã có hay mép bàn cờ, không được ăn hoặc nhảy qua quân khác,

• Ở mỗi bước phải di chuyển quân màu tương ứng, không được bỏ qua nước đi.

Từ các qui tắc biến đổi trên ta suy ra các qui tắc biến đổi ngược lại :

1. Tại mỗi lượt đi phải kiểm tra xem bước đi là đi quân đen hay quân trắng.

2. Y như qui tắc 2 ở trên, có thể mọi người sẽ không hiểu tại sao đi ngược lại lại giống như trên, nhưng hãy xem lại ví dụ trên và kiểm tra ngược lại xem : Ngoài bước đi đầu tiên không theo qui tắc, còn lại đều theo qui tắc. Do các trạng thái ta lưu lại đều có được sau 1 số phép biến đổi và không là bước đi đầu tiên nên qui tắc trên luôn đúng.

3. Qui tắc 3 cũng tương tự.

Như vậy ta đã xây dựng xong bộ qui tắc biến đổi ngược lại.



Phương pháp mã hoá :

Ta sẽ dùng phương pháp gần như mã hoá tổ hợp.

Tổng số lượng các trạng thái có thể sinh ra là tổ hợp chập 4 của 16 nhân với tổ hợp chập 4 của 12 và bằng 16!/(4!4!8!)=900900 có thể làm theo cách 1 ( khai báo động và kết hợp xử lí bit- nói trong turbo pascal) . Ta sẽ xét thêm cách 2 là lưu lại các giá trị sau đó sắp xếp lại, may mắn là giá trị này trong phạm vi Longint.

Khi cho 1 trạng thái ví dụ :

w**b

*wb*

*bw*

b**w


Tôi làm như sau :

Lọc những ô trắng ra, ghi lại toạ độ của ô đó, toạ độ đó ghi từ 1..16. như vậy ta có dãy 1, 6, 11, 16. Tính gía trị tổ hợp của dãy trên, Gọi là A.

Lọc những ô đen ra, làm như trên nhưng chỉ tính từ 1..12 bỏ qua các ô trắng, ta có dãy 3,5,8,10. Tính giá trị tổ hợp của dãy trên, gọi là B.

Mặc dù lúc tính số lượng cấu hình, ta dùng phép nhân 2 giá trị A*B nhưng khi tính ra giá trị của 1 cấu hình ta không thể nhân được. Lí do là vì nếu ta nhân ta không đảm bảo được giá trị duy nhất. Ví dụ : 6*8=12*4.
Để giả quyết chỗ này, ta làm như sau :

Đặt giá trị Const M=495 là giá trị max của B. Khi nhận được 2 số A,B ta tính giá trị X của cấu hình như sau : X=(A-1)*M+(B-1); Giá trị này đảm bảo duy nhất. Như vậy, khi cho giá trị X ta làm như sau :

A:=(X div M) +1; B:=(X mod M) +1;

Từ A,B ta suy ra cấu hình.

Từ A suy ra các ô màu trắng, Từ B suy ra các ô màu đen nhưng khi tính ra trạng thái phải bỏ các ô màu trắng đi.

Như vậy đã giải quyết xong phần mã hoá dữ liệu. Khi tính ra X rồi ta đánh đấu bằng Bit. Nói tổng quát hơn, ta coi như không thể đánh dấu được. Khi đó ta sẽ sinh ra 1 tập hợp các khoá. Sắp xếp các khoá này. Có thể sẽ có 1 số khoá lặp nhau. Nhưng ta không cần quan tâm. Sau đây tôi xin giới thiệu cách tôi sử dụng để sinh ra tập các khoá này.


const max = {tuỳ bạn } ;

type ar = array[0..max] of longint;

var save : ar;

temp : ^ar;

qua_gioi_han : boolean;


procedure Bien_doi(K: Longint);

var i:longint;

begin

trangthai(k); {thủ tục từ khoá k sinh ra trạng thái tương ứng với khoá đó}

while Con_bien_doi_thuan_duoc do

begin

Bien_doi_thuan;

inc(save[0]);

save[0]:=mahoa;

if save[0]>max then

begin

qua_gioi_han:=true;

exit;

end;

end;

end;


procedure DFS;

var i : integer;

begin

while true do

begin

for i:=1 to temp^[0] do

begin

Bien_doi(temp^[i]);

if qua_gioi_han then break;

end;

if not qua_gioi_han then temp^:=save;

end;

save:=temp^;

end;


Với chương trình trên, bạn sẽ lưu lại khoảng max các khoá.



Trong lần duyệt thứ 2, theo tôi nên cài duyệt với độ sâu hạn chế ( bạn có thể tìm hiểu kĩ hơn trong bài báo của thầy Trần Đỗ Hùng).


Cài đặt cụ thể tôi xin không nêu ra. Hi vọng các bạn sẽ có các cải tiến, sáng tạo mới hay hơn.
URL của bài viết này::http://www.vnschool.net/modules.php?name=News&file=article&sid=4291
© Cong ty Cong Nghe Tin hoc Nha truong contact: sales@schoolnet.vn