SKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kêSKKN Ứng dụng thuật toán quay lui giải bài toán liệt kê
Trang 1PHẦN I MỞ ĐẦU 1
1.1 Lý do chọn đề tài: 1
1.2 Mục đích, nhiệm vụ của việc thực hiện đề tài nghiên cứu: 1
1.3 Đối tượng, thời gian và phương pháp nghiên cứu: 2
1.3.1 Đối tượng nghiên cứu 2
1.3.2 Thời gian nghiên cứu 2
1.3.3 Phương pháp nghiên cứu 2
PHẦN II NỘI DUNG 3
2.1 Phương pháp (thuật toán quay lui Backtracking) 3
2.2 Dạng 1: Tìm tất cả các nghiệm 4
2.3 Dạng 2 : Tìm một nghiệm 21
2.3 Dạng 3 Tìm nghiệm tối ưu thoả mãn điều kiện 29
2.3.1 Nghiệm tối ưu 29
2.3.2 Kỹ thuật nhánh cận 30
PHẦN III KẾT LUẬN VÀ KIẾN NGHỊ 40
3.1 KẾT LUẬN 40
3.2 KIẾN NGHỊ 41
Trang 2PHẦN I
MỞ ĐẦU1.1 Lý do chọn đề tài:
Với mỗi bài toán trong Tin học thường có rất nhiều phương pháp giải, nhưngtìm được phương pháp giải tối ưu không phải là vấn đề đơn giản Để giải một bàitoán thường phải xác định được:
- Bài toán thuộc lớp bài toán nào
- Sử dụng phương pháp tối ưu nào để giải nó
Có những bài toán yêu cầu phải liệt kê nghiệm theo một điều kiện nào đó Vớilớp bài toán này sử dụng thuật toán quay lui để giải quyết sẽ dễ dàng và đơn giản hơn
các phương pháp khác (phương pháp “sinh” cũng giải được một số bài toán liệt kê cấu hình) Vì vậy, tôi đề xuất sáng kiến “Ứng dụng thuật toán quay lui giải bài toán liệt kê” Tuy “thuật toán quay lui” là không mới, không tối ưu trong việc giải
quyết một số bài toán nào đó mà phương pháp khác cũng giải được, nhưng cũng cónhiều bài toán mà chỉ có thuật toán này mới giải quyết được nó một cách dễ dàng.Những bài toán dùng phương pháp quay lui để giải gặp rất nhiều trong các kỳ thi họcsinh giỏi tỉnh, Tin học trẻ không chuyên cũng như học sinh giỏi quốc gia trong nhiềunăm
Ý tưởng cơ bản của “thuật toán quay lui” là liệt kê hết tất cả các khả năng có
thể, hay còn gọi là phương pháp vét cạn, duyệt hết cấu hình
1.2 Mục đích, nhiệm vụ của việc thực hiện đề tài nghiên cứu:
Nhằm giúp giáo viên và học sinh khi đứng trước một bài toán, xác định được
là bài toán đó có thể áp dụng được thuật toán quay lui hay không? Và cách giải cụthể như thế nào? Từ đó tôi đề ra mục đích, nhiệm vụ của việc thực hiện đề tài nhưsau:
- Giới thiệu khái niệm “thuật toán quay lui (Backtracking)”, những ưu điểm,hạn chế của thuật toán
- Giới thiệu một số bài toán điển hình trong lớp bài toán
Vì thế sáng kiến có các nội dung sau:
Trang 3Mục 1 Phương pháp quay lui (backtracking).
Mục 2 Dạng 1: Tìm tất cả các nghiệm
Mục 3 Dạng 2: Tìm một nghiệm
Mục 4 Dạng 3: Tìm nghiệm tối ưu thoả mãn điều kiện
Với mỗi dạng có các ví dụ điển hình, ý tưởng và chương trình tham khảo
1.3 Đối tượng, thời gian và phương pháp nghiên cứu:
1.3.1 Đối tượng nghiên cứu:
Sáng kiến kinh nghiệm “Ứng dụng thuật toán quay lui giải bài toán liệt kê” có
đối tượng nghiên cứu là các bài toán giải bằng thuật toán quay lui
1.3.2 Thời gian nghiên cứu:
“Thuật toán quay lui” được nghiên cứu trong quá trình học tập và giảng dạy,bồi dưỡng các đội tuyển học sinh giỏi
1.3.3 Phương pháp nghiên cứu:
Chủ yếu là nghiên cứu đề tài, tham khảo tài liệu, ý kiến đóng góp của đồng nghiệp và qua thực tiễn giảng dạy
Trang 4PHẦN II NỘI DUNG 2.1 Phương pháp (thuật toán quay lui Backtracking)
Trong các kỹ thuật cơ bản để thiết kế thuật toán, quay lui là một trong những kỹthuật quan trọng nhất vì nó cho phép giải một lớp các bài toán khá lớn có dạng tổngquát như sau:
Tìm một (hoặc tất cả) bộ nghiệm (x1, x2, , xn) thỏa mãn điều kiện F nào đó,trong đó các thành phần xi được chọn từ một tập hữu hạn Di với mọi i = 1, 2, , n
Tư tưởng của phương pháp quay lui như sau :
- Ta xây dựng bộ nghiệm từng bước, bắt đầu từ thành phần nghiệm x1 đượcchọn trong các giá trị có thể S1 = D1
- Giả sử đã chọn được các thành phần x1, x2, , xi-1, từ các điều kiện của bàitoán ta sẽ xác định được tập Si gồm các giá trị được chọn cho thành phần nghiệm xi.Tập Si là tập con của Di và phụ thuộc vào các thành phần x1, x2, , xi-1 đã chọn Chọnmột phần tử xi thuộc Si như một thành phần của bộ nghiệm Từ bộ (x1, x2, , xi) lặplại quá trình trên để tiếp tục mở rộng nghiệm cho thành phần xi+1 Nếu không chọnđược thành phần nào của xi+1 (do Si+1 rỗng) thì ta quay lại chọn một phần tử khác của
Si làm thành phần nghiệm xi (ý nghĩa của quay lui là ở bước này)
- Trong quá trình mở rộng nghiệm ta luôn kiểm tra nghiệm thành phần có phải
là nghiệm của bài toán hay không Nếu chỉ cần một nghiệm thì ta dừng lại, nếu cầntìm tất cả các nghiệm thì quá trình tìm nghiệm chỉ dừng khi tất cả các khả năng chọncác thành phần nghiệm đã vét cạn
Quay lui là một trong các phương pháp vét cạn trong đó các giá trị nghiệm đượcchọn không thực hiện được bằng cách duyệt tuyến tính Điểm tốt của thuật toán quaylui so với vét cạn tuyến tính là hạn chế bớt các nhánh phải duyệt mà theo nhữngnhánh đó không tìm được lời giải thể hiện ở việc xây dựng các tập giá trị có thể Si
khi tìm thành phần nghiệm xi và quay lui khi không mở rộng thành phần nghiệm xi+1 Tuy nhiên hạn chế của phương pháp này là phải duyệt qua nhiều khả năng nên độphức tạp của chương trình thường ở mức giai thừa hay hàm mũ nên tốc độ tính toán
Trang 5khá lâu trong trường hợp kích thước của dữ liệu vào khá lớn Để khắc phục hạn chếnày người ta tìm cách hạn chế các khả năng không đưa đến kết quả bằng phươngpháp nhánh cận, tuy nhiên lớp bài toán dùng được phương pháp này không nhiều.
Đa số các bài toán vét cạn sử dụng phương pháp duyệt đệ quy để xét hết mọi khảnăng có thể có nghiệm theo nguyên tắc "thử sai" và quay lui Về tư tưởng, các thuậttoán vét cạn rất đơn giản nhưng ứng dụng vào việc giải các bài toán cần có những kỹthuật nhất định
Để giải bài toán bằng thuật toán quay lui cần thực hiện các công việc quan
* Tìm các điều kiện để các nghiệm đã chọn là các nghiệm của bài toán
Một số lưu ý khi xây dựng thuật toán quay lui:
* Phải duyệt qua mọi phương án của bài toán có thể chứa nghiệm (vét cạn)
* Tránh trường hợp duyệt trùng lặp các khả năng đã duyệt
Để giải các bài toán bằng thuật toán quay lui, thông thường ta thường dùng thủtục đệ quy Try(i : Integer) để chọn thành phần nghiệm xi
Có ba dạng cơ bản trong các thuật toán quay lui:
Dạng 1: Tìm tất cả các nghiệm.
Dạng 2: Tìm một nghiệm.
Dạng 3: Tìm nghiệm tối ưu thỏa mãn điều kiện.
Ta lần lượt xét cách giải các dạng trên bằng phương pháp quay lui:
2.2 Dạng 1: Tìm tất cả các nghiệm
Mô hình của thuật toán quay lui có thể mô tả như sau:
Procedure Try(i: Integer); {Thủ tục này thử cho xi nhận lần lượt các giá trị mà nó có thể nhận}
Begin
for <mọi giá trị j có thể gán cho xi> do
Trang 6<Thử cho xi := j>;
If <xi là phần tử cuối cùng trong cấu hình> then
<Thông báo cấu hình tìm được>
Else
Begin
<Ghi nhận việc cho xi nhận giá trị j (Nếu cần)>;
Try(i + 1); {Gọi đệ quy để chọn tiếp xi+1}
<Nếu cần, bỏ ghi nhận việc thử xi := j, để thử giá trị khác>;
VÍ DỤ 1 LIỆT KÊ DÃY NHỊ PHÂN ĐỘ DÀI N
Input: file văn bản NHIPHAN.INP chứa số nguyên dương N (N<=30).
Output: file văn bản NHIPHAN.OUT ghi các dãy nhị phân, mỗi dãy trên một dòng.
Ý tưởng:
Biểu diễn dãy nhị phân độ dài N dưới dạng (x1,
x2, ., xn) Ta sẽ liệt kê các dãy này bằng cách thử
dùng các giá trị {0, 1} gán cho xi Với mỗi giá trị thử
gán cho xi lại thử các giá trị có thể gán cho xi+1…
Chương trình liệt kê bằng thuật toán quay lui có thể
viết:
Trang 7If i = n then Incauhinh {Nếu i = n thì Incauhinh}
Else Try(i + 1); {Nếu i chưa phải là phần tử cuối thì tìm tiếp x i+1 }
Trang 8Nghiệm của bài toán tìm các tổ hợp chập k của n phải thoả mãn các điều kiệnsau:
- Là một vectơ X= (x1, x2,…, xk);
- Xi lấy giá trị trong tập {1,2,3,…,n};
- Ràng buộc: xi < xi+1 với mọi giá trị i từ 1 đến k-1 (vì tập hợp không phân biệtthứ tự phần tử nên ta sắp xếp các phần tử theo thứ tự tăng dần)
Ta có: 1x1x2<…< xkn, do đó xi được chọn là xi-1 + 1 xi n - k + i(1 i k) ở đây ta giả thiết có thêm một số x0 = 0 khi xét i = 1 Như vậy ta sẽ xéttất cả các cách chọn x1 từ 1 (= x0 + 1) đến n - k + 1, với mỗi giá trị đó, xét tiếp tất cả{1,2},{1,3},{1,4},{1,5},{2,3}, {2,4}, {2,5}, {3,4}, {3,5},{4,5};
Trang 9các cách chọn x2 từ x1 + 1 đến n - k + 2, cứ như vậy khi chọn được đến xk thì ta
có một cấu hình cần liệt kê
Trang 10Assign(g, 'TOHOP.INP'); Reset(g);
Assign(f, 'TOHOP.OUT'); ReWrite(f);
Readln(g,n, k);
x[0] := 0; Try(1); Close(g); Close(f);
End
{============================}
VÍ DỤ 3 LIỆT KÊ CÁC CHỈNH HỢP KHÔNG LẶP CHẬP K
Input: file văn bản CHINHHOP.INP chứa hai số nguyên dương n, k (1 kn
đưa về liệt kê các cấu hình (x1, x2, , xk),
ở đây các xi S và khác nhau đôi một
Nghiệm của bài toán tìm các chỉnh
hợp không lặp chập k của tập n số nguyên từ 1 đến n là vectơ X thoả mãn điềukiện:
- Là một vectơ X = (x1, x2,…, xk);
- Xi lấy giá trị trong tập {1,2,3,…,n};
- Ràng buộc: các giá trị xi đôi một khác nhau, tức là xi ≠ xj với mọi i≠ j
Trang 11Như vậy thủ tục Try(i) - xét tất cả các khả năng chọn xi - sẽ thử hết các giá trị từ
1 đến n, mà các giá trị này chưa bị các phần tử đứng trước chọn Muốn xem cácgiá trị nào chưa được chọn ta sử dụng kỹ thuật dùng mảng đánh dấu:
- Khởi tạo một mảng c1, c2, , cn kiểu logic Ở đây ci cho biết giá trị i có còn
tự do hay đã bị chọn rồi Ban đầu khởi tạo tất cả các phần tử mảng c là TRUE cónghĩa là các phần tử từ 1 đến n đều tự do
Trang 12If c[j] then {Chỉ xét những giá trị j còn tự do}
Assign(g, 'CHINHHOP.INP'); Reset(g);
Assign(f, 'CHINHHOP.OUT'); ReWrite(f);
Readln(g,n,k);
FillChar(c, SizeOf(c), True); {Tất cả các số đều chưa bị chọn}
Try(1); {Thử các cách chọn giá trị của x 1 }
Close(g); Close(f);
End
{==========================}
Nhận xét: khi k = n thì đây là chương trình liệt kê hoán vị.
VÍ DỤ 4 BÀI TOÁN PHÂN TÍCH SỐ
Hãy tìm tất cả các cách phân tích số n thành tổng của các số nguyên dương,các cách phân tích là hoán vị của nhau chỉ tính là 1 cách
Input: file văn bản PHANTICH.INP chứa số nguyên dương n (n 30).
Output: file văn bản PHANTICH.OUT ghi các cách Ý tưởng số n.
Trang 13Khi liệt kê các dãy x có tổng các
phần tử đúng bằng n, để tránh sự trùng lặp ta đưa thêm ràng buộc xi-1 xi Khi ti =
Trang 15VÍ DỤ 5 BÀI TOÁN XẾP N QUÂN HẬU
Xét bàn cờ tổng quát kích thước n x n Một quân hậu trên bàn cờ có thể ănđược các quân khác nằm tại các ô cùng hàng, cùng cột hoặc cùng đường chéo.Hãy tìm cách xếp n quân hậu trên bàn cờ sao cho không quân nào ăn quân nào
Input: file văn bản QUEENS.INP chứa số nguyên dương n 12.
Output: file văn bản QUEENS.OUT, mỗi dòng ghi một cách đặt n quân hậu.
tìm ra được vị trí cột của những quân hậu.
Trang 16- Mảng c[1 - n n – 1] để đánh dấu đường chéo còn lại.
- Ban đầu cả 3 mảng đánh dấu đều mang giá trị TRUE (Các cột và đường chéo đều
tự do), nếu phần tử nào được chọn thì cả 3 đường chéo nhận giá trị bằng FALSE
* Khi chọn vị trí cột j cho quân hậu thứ i, thì ta phải chọn ô (i, j) không bị các quânhậu đặt trước đó ăn, tức là phải chọn cột j còn tự do Điều này có thể kiểm tra (aj
= bi+j = ci-j = TRUE)
b: array[2 2 * max] of Boolean;
c: array[1 - max max - 1] of Boolean;
{============================}
Procedure khoitao;
Begin
Readln(f,n);
FillChar(a, SizeOf(a), True);
FillChar(b, SizeOf(b), True);
FillChar(c, SizeOf(c), True);
Trang 17for i := 1 to n do Write(f,'(', i, ', ', x[i], '); '); Writeln(f);
Trang 18Có N người (đánh số từ 1 đến N) tham gia một đợt xổ số điện toán Mỗi ngườinhận được một thẻ gồm M ô (đánh số từ 1 đến M) Người chơi được chọn K ô trong
số các ô đã cho bằng cách đánh dấu các ô được chọn Sau đó các thẻ này được đưavào máy tính để xử lý Máy tính chọn ra K ô ngẫu nhiên (gọi là các ô kết quả) vàchấm điểm từng thẻ dựa vào kết quả đã sinh Cứ mỗi ô chọn đúng với ô kết quả thìthẻ chơi được tính 1 điểm Giả thiết biết các ô chọn cũng như các điểm tương ứngcủa từng thẻ chơi, hãy xác định tất cả các kết quả có thể có mà máy sinh ra
INPUT: Dữ liệu vào đọc từ file văn bản XOSO.INP gồm:
- Dòng đầu ghi các số N, M, K
- Dòng thứ i trong N dòng tiếp ghi thẻ chơi của người i gồm K+1 số: K số đầu là các số hiệu của các ô chọn, cuối cùng là điểm tương ứng
OUPUT: Ghi kết quả ra file văn bản XOSO.OUT, mỗi dòng là một kết quả gồm K
số ghi số hiệu các ô mà máy đã sinh
Trang 19chập K của M phần tử Ta áp dụng thuật toán quay lui để duyệt mọi cấu hình tổ hợp
để tìm ra cấu hình thoả mãn Tuy nhiên để giảm bớt số lần duyệt ta cần phải loạinhững thẻ mà chúng có tổng điểm bằng 0 và cần đánh dấu những thẻ đã được chọn
Dùng mảng kt[0 51] of Boolean để phân biệt giữa ô có điểm và những ôkhông có điểm
Nếu kt[i]= False thì cho biết thẻ thứ i không có điểm
Còn logic[i,j] = True cho ta biết người thứ i đánh dấu vào thứ j của thẻ
Trang 20{đánh dấu tất cả các dòng có điểm, nếu dòng nào có điểm thì chấp nhận j= True;}
Function Chapnhan(j: Byte): Boolean;
Trang 22<Thông báo cấu hình tìm được>
Halt; {thoát khỏi chương trình}
End Else
Trang 23<Ghi nhận việc cho xi nhận giá trị j (Nếu cần)>;
Try(i + 1); {Gọi đệ quy để chọn tiếp xi+1}
<Nếu cần, bỏ ghi nhận việc thử xi := j, để thử giá trị khác>;
+ Điều kiện cần để một khả năng được chấp nhận ở bước thứ i là bước i+1 cũng
có khả năng chấp nhận một nghiệm của nó và bước thứ i chưa phải bước cuối cùng
Vì vậy có thể nhanh chóng tới đích nếu đưa ra qui luật chọn nghiệm của bước thứ inhư sau :
Ở bước thứ i ta sẽ chọn nghiệm nào mà theo nó đưa ta tới bước i+1 có ít khảnăng chấp nhận nhất (nghĩa là bước thứ i+1 vẫn có khả năng chọn nghiệm của nó,nhưng số nghiệm ít)
+ Một cách khác: Khi chấp nhận một khả năng chọn nghiệm cho bước thứ i, cóthể sẽ tác động tới trạng thái bài toán Vì vậy ta tính toán trước nếu chọn nghiệm nàythì trạng thái bài toán có thay đổi quá mức giới hạn cho phép hay không? Nghĩa là cóvượt qua cận trên hoặc cận dưới của bài toán hay không ? Nếu vượt qua thì ta khôngchọn nghiệm ấy Trong nhiều bài toán những cận này cũng thu hẹp dần theo từngbước, nếu ta tìm được sự thay đổi của cận theo từng bước thì các khả năng chọnnghiệm ngày càng hẹp dần, bài toán nhanh chóng kết thúc
Một khó khăn khác của loại toán hiện 1 nghiệm là: trường hợp bài toán vônghiệm cần viết chương trình như thế nào? Phải duyệt hết mọi khả năng mới rõ kếtluận vô nghiệm hay không vô nghiệm Nghĩa là đã đi theo mọi nhánh nhưng nhánhnào cũng đều không tới đích, do đó theo quy luật cứ quay lui mãi để tìm kiếm thì đếnlúc nào đó dẫn đến tình trạng phải trở về ô xuất phát Vậy khi gặp ô chọn nghiệm
Trang 24mới trùng với ô xuất phát thì bài toán vô nghiệm Vậy trong thủ tục Try(i) ta thêmmột bước kiểm tra xem chương trình có nghiệm hay không nữa
Trang 25VÍ DỤ 1 TÌM ĐƯỜNG ĐI TRONG MÊ CUNG
Mê cung gồm N phòng (N<100) có các hành lang nối với nhau đó là nơi trúngụ của quái vật Minotau (Nửa bò, nửa người) Ban ngày quái vật thường ra khỏi mêcung phun lửa giết chóc tàn phá với sức mạnh không ai địch nổi Ban đêm quái vậtngủ trong mê cung và hòn than lửa của nó được cất ở phòng “Dich”; ai lấy được hònthan lửa ấy thì chinh phục được quái vật Theo lời thỉnh cầu của công chúa Arian,anh hùng Têđê nhận lời sẽ vào mê cung thu phục quái vật Têđê xuất phát từ phòng
XP và quyết định dùng thuật toán tìm kiếm bằng vét cạn và quay lui (cùng cuộn chỉcủa nàng Arian tặng chàng để quay lui thuận tiện) Trong mê cung tối om dày đặcphòng và hành lang, chàng đã tìm được được phòng “Dich” và thu phục quái vật
Hãy lập trình hiện đường đi của Têđê
Input: File ‘MECUNG.TXT’ tổ chức như sau:
+ Dòng đầu là 3 số N XP Dich
+ N dòng tiếp theo: Dòng thứ i: Đầu tiên là số i (1≤ i ≤ N) tiếp theo là các số j (hai
số liền nhau cách nhau ít nhất 1 khoảng trống) thể hiện có hành lang một chiều từphòng i sang phòng j
Output: Đường đi của Têđê: liệt kê lần lượt các phòng chàng sẽ đi qua (không kể
những đoạn phải quay lại)
Ý tưởng:
Mảng A thể hiện các đường đi một chiều từ phòng i đến phòng j
Mảng T dùng để tính tổng số đường đi từ phòng xuất phát đến phòng i
Mảng D để đánh dấu xem phòng i đã được đi qua hay chưa
Mảng KQ để lưu lại đường đi