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
Trang 1THUẬT TOÁN QUAY LUI (BACKTRACKING)
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ổng quá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 được chọ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ài toá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ọn một phần tử xi thuộc Si như một thành phần của
Trang 2bộ nghiệm Từ bộ (x1, x2, , xi) lặp lạ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ần tì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ọn cá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 được chọn không thực hiện được bằng
Trang 3việ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 khá 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ương phá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ật toá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 trọng sau:
* Tìm cách biểu diễn nghiệm của bài toán dưới dạng một dãy các đối tượng được chọn dần từng bước (x1, x2, , xn).
Trang 4* Xác định được tập Si các khả năng được chọn làm thành phần thứ i của nghiệm Chọn cách thích hợp để biểu diễn Si.
* 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.
Trang 5Ta lần lượt xét cách giải các dạng trên bằng phương pháp quay lui:
Trang 62 Dạng 1: Tìm tất cả các nghiệm
Trang 10Mô hình của thuật toán quay lui có thể mô tả như sau:
Trang 11Procedure Try(i: Integer); {Thủ tục này thử cho x i nhận lần lượt các giá trị mà nó có thể nhận}
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>
Trang 12Try(i + 1); {Gọi đệ quy để chọn tiếp xi+1}
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).
Trang 14Ý 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:
VÍ DỤ 2 TỔ HỢP (XÁC ĐỊNH TỔ HỢP CHẬP K CỦA TẬP
N PHẦN TỬ)
Input: file văn bản TOHOP.INP chứa hai số nguyên dương
n, k (1 ≤ k ≤ n ≤ 20 ) cách nhau ít nhất một dấu cách.
Output: file văn bản TOHOP.OUT ghi các tổ hợp chập k của
tập {1, 2, , n}
Trang 15TOHOP.INP TOHOP.OUT
5 3 {1,2,3}
{1,2,4} {1,2,5} {1,3,4} {1,3,5} {1,4,5} {2,3,4} {2,3,5}
Trang 17thoả mãn các điều kiện sau:
- 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ệt thứ 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ó: 1 ≤ x1 < x2<…< xk ≤ n, 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ét tấ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ả cá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ê
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 ≤ k ≤ n ≤ 20) cách nhau ít nhất một dấu cách.
Output: file văn bản CHINHHOP.OUT ghi các chỉnh hợp
Trang 19xk ), ở đâ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ều kiệ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.
Như 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ác giá 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.
VÍ DỤ 4 BÀI TOÁN PHÂN TÍCH SỐ
Trang 20Hã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
Trang 21Ý tưởng:
Ta sẽ lưu nghiệm trong mảng x, ngoài ra có một mảng
t Mảng t xây dựng như sau: ti sẽ là tổng các phần tử trong mảng x từ x1 đến xi: ti := x1 + x2 + + xi.
Khi 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
- Cuối cùng xét giá trị xi = n - ti-1 và in kết quả từ x1 đến xi.
VÍ DỤ 5 Trò chơi trên băng giấy (Hội thi GV Giỏi Hà Tĩnh 2012)
Trên một băng giấy người ta kẻ N ô vuông liên tiếp nhau theo chiều ngang Các ô vuông được đánh số từ 1 đến N theo chiều từ trái sang phải (1<N<50) Tại ô vuông số 1 có đặt một quân cờ Một nước đi trong trò chơi là dịch chuyển quân cờ về phía bên phải 1 hoặc 2 ô vuông.
Trang 22Hãy đếm và liệt kê tất cả các cách dịch chuyển quân cờ từ ô vuông số 1 tới ô vuông N (Ký hiệu: ký hiệu ‘O’ mô tả dịch chuyển quân cờ về phía bên phải 1
ô vuông, ký hiệu ‘T’ mô tả dịch chuyển quân cờ về phía bên phải 2 ô vuông).
Dữ liệu vào là tệp văn bản BANG.INP ghi số nguyên dương N.
Dữ liệu ra là tệp văn bản BANG.OUT có cấu trúc:
- Dòng thứ nhất ghi số nguyên M là số cách dịch chuyển quân cờ từ ô vuông 1 đến ô vuông N.
- M dòng tiếp theo mỗi dòng ghi một cách dịch chuyển trong số M cách di chuyển quân cờ từ ô số 1 tới ô số N.
BANG.INP BANG.OUT
OOO OT TO
*
Trang 23Input: 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
Trang 25Ví dụ một cách xếp với n = 5
Rõ ràng n quân hậu sẽ được đặt mỗi con một hàng
vì hậu ăn được ngang, ta gọi quân hậu sẽ đặt ở hàng 1 là quân hậu 1, quân hậu ở hàng 2 là quân hậu 2 quân hậu
ở hàng n là quân hậu n Vậy một nghiệm của bài toán sẽ
được biết khi ta tìm ra được vị trí cột của những quân hậu.
- Mảng b[2 2n] để đánh dấu một đường chéo.
- Mảng c[1 - n n – 1] để đánh dấu đường chéo còn lại.
Trang 26- 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ân hậ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).
cả các kết quả có thể có mà máy sinh ra
Trang 27INPUT: 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 28Ý tưởng:
Ta nhận thấy rằng mỗi nghiệm của bài toán chính là một cấu hình của tổ hợp chậ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ại nhữ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 30<Thông báo cấu hình tìm được>
Halt; {thoát khỏi chương trình}
End Else
Begin
Trang 31Try(i + 1); {Gọi đệ quy để chọn tiếp x i+1 }
+ Đ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ứ i như 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).
Trang 32+ 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ày thì 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ông chọ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ừng bướ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ọn nghiệ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ết luận vô nghiệm hay không vô nghiệm Nghĩa là đã đi theo mọi nhánh nhưng nhánh nào cũng đều không tới đích, do đó theo quy luật cứ quay lui mãi để tìm kiếm thì đến lú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 mớ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êm một bước kiểm tra xem chương trình có nghiệm hay không nữa
Trang 33Một máy ATM hiện có n (n ≤ 20) tờ tiền có mệnh giá t1, t2, t3,…., tn Hãy đưa ra cách trả tiền đúng bằng s.
Input: Tệp ATM.INP có dạng:
- Dòng đầu là hai số n và s.
- Dòng thứ hai gồm n số t1, t2, t3,…., tn
Output: Tệp ATM.OUT có dạng: Nếu có thể trả đúng s thì
đưa ra cách trả, nếu không ghi -1.
Ý tưởng:
Nghiệm của bài toán là một dãy nhị phân độ dài n, trong đó thành phần thứ i bằng 1 nếu tờ tiền thứ i được sử dụng để trả, bằng 0 trong trường hợp ngược lại.
X= (x1, x2, , xn) là nghiệm nếu: x t1× + × + + ×1 x2 t2 xn tn
= s Chương trình cần dùng biến kt để kiểm tra xem tìm
Trang 34được hay chưa, ban đầu kt:=False, nếu tìm được thì kt:=True và kết thúc chương trình mà không cần tìm tiếp nữa
Trang 354 Dạng 3 Tìm nghiệm tối ưu thoả mãn điều kiện
4.1 Nghiệm tối ưu
Một trong những bài toán đặt ra trong thực tế là việc
tìm ra một nghiệm thoả mãn một số điều kiện nào đó, là nghiệm tốt nhất theo một chỉ tiêu cụ thể Tuy nhiên cũng
cần phải nói rằng trong nhiều trường hợp chúng ta chưa thể xây dựng một thuật toán nào thực sự hữu hiệu để giải bài toán, mà cho tới nay việc tìm nghiệm của chúng
vẫn phải dựa trên mô hình liệt kê toàn bộ các cấu hình có
thể và đánh giá, tìm ra cấu hình tốt nhất
Mô hình thuật toán quay lui là tìm kiếm trên 1 cây phân cấp Nếu giả thiết rằng ứng với mỗi nút tương ứng với một giá trị được chọn cho xi sẽ ứng với chỉ 2 nút tương ứng với 2 giá trị mà xi+1 có thể nhận thì cây n cấp sẽ có tới 2n nút lá, con số này lớn hơn rất nhiều lần so với dữ liệu đầu vào n.
Chính vì vậy mà nếu như ta có thao tác thừa trong việc chọn xi thì sẽ phải trả giá rất lớn về chi phí thực thi thuật toán bởi quá trình tìm kiếm lòng vòng vô nghĩa trong các bước chọn kế tiếp xi+1, xi+2, Khi đó, một vấn đề
Trang 36đặt ra là trong quá trình liệt kê lời giải ta cần tận dụng những thông tin đã tìm được để loại bỏ sớm những phương án chắc chắn không phải tối ưu Kỹ thuật đó gọi
là kỹ thuật đánh giá nhánh cận trong tiến trình quay lui.
Trang 374.2 Kỹ thuật nhánh cận
Dựa trên mô hình thuật toán quay lui,
ta xây dựng mô hình sau:
Trang 38If (Việc thử trên vẫn còn hi vọng tìm ra cấu hình tốt
hơn CAUHINH) then
If (xi là phần tử cuối cùng
trong cấu hình) then
CAUHINH>
Trang 39<Ghi nhận việc thử xi = j (nếu cần)>;
Try(i + 1); {Gọi đệ quy, chọn tiếp xi+1}
<Bỏ ghi nhận việc thử cho xi = j (nếu cần)>;
Trang 41hơn cấu hình CAUHINH thì thử giá trị khác ngay mà không cần phải gọi đệ quy tìm tiếp hay ghi nhận kết quả, vì nếu
có tiếp tục thì việc làm đó là vô nghĩa và làm cho chi phí của bài toán càng lớn Nghiệm của bài toán sẽ được làm tốt dần, bởi khi tìm ra một cấu hình mới (tốt hơn CAUHINH), ta không in kết quả ngay mà sẽ cập nhật CAUHINH bằng cấu hình mới vừa tìm được.
Trang 42VÍ DỤ 1 BÀI TOÁN MÁY RÚT TIỀN TỰ ĐỘNG ATM 2
Một máy ATM hiện có n (n ≤ 20) tờ tiền có mệnh giá t1, t2, t3,…., tn Hãy tìm cách trả ít tờ nhất với số tiền đúng bằng s.
Input: Tệp ATM.INP có dạng:
- Dòng đầu là hai số n và s.
- Dòng thứ hai gồm n số t1, t2, t3,…., tn
Output: Tệp ATM.OUT có dạng: Nếu có thể trả đúng s thì
đưa ra số tờ ít nhất cần trả và đưa ra cách trả, nếu không ghi -1.
Trang 43Như ta đã biết nghiệm của bài toán là một dãy nhị phân độ dài n, giả sử đã xây dựng được k thành phần (x1, x2, , xk ), đã trả được tong và sử dụng c tờ Để đánh giá được các nghiệm mở rộng của (x1, x2, , xk ), ta nhận thấy:
tong s
tong s
mà lớn hơn hoặc bằng số tờ của cách trả tốt nhất hiện có thì không cần mở rộng các nghiệm (x1, x2, , xk ) nữa.
VÍ DỤ 2 BÀI TOÁN NGƯỜI DU LỊCH
Cho n thành phố đánh số từ 1 đến n và m tuyến đường giao thông hai chiều giữa chúng, mạng lưới giao thông này được cho bởi bảng C cấp n x n, ở đây Cij = Cji = Chi phí đi đoạn đường trực tiếp từ thành phố i đến thành