Tải bản đầy đủ (.doc) (45 trang)

SKKN Ứng dụng thuật toán quay lui giải bài toán liệt kê

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (212.09 KB, 45 trang )

MỤC LỤC


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


PHẦN I
MỞ ĐẦU
1.1 Lý do chọn đề tài:
Với mỗi bài tốn trong Tin học thường có rất nhiều phương pháp giải, nhưng
tì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ài
tốn thường phải xác định được:
- Bài tốn thuộc lớp bài tốn nào.
- Sử dụng phương pháp tối ưu nào để giải nó.
Có những bài tốn u cầu phải liệt kê nghiệm theo một điều kiện nào đó. Với
lớp bài tốn này sử dụng thuật tố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 tốn liệt kê
cấu hình). Vì vậy, tơi đề xuất sáng kiến “Ứng dụng thuật tốn quay lui giải bài
tốn liệt kê”. Tuy “thuật tố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 tốn nào đó mà phương pháp khác cũng giải được, nhưng cũng có
nhiều bài tốn mà chỉ có thuật tốn này mới giải quyết được nó một cách dễ dàng.
Những bài tốn dùng phương pháp quay lui để giải gặp rất nhiều trong các kỳ thi học
sinh giỏi tỉnh, Tin học trẻ khơng chun cũng như học sinh giỏi quốc gia trong nhiều
năm.
Ý tưởng cơ bản của “thuật tố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 tốn, xác định được
là bài tốn đó có thể áp dụng được thuật tố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 tốn quay lui (Backtracking)”, những ưu điểm,
hạn chế của thuật tốn.
- Giới thiệu một số bài tốn điển hình trong lớp bài tốn.
Vì thế sáng kiến có các nội dung sau:
Trường THPT Chuyên Bảo Lộc

1

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


Mụ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 tốn quay lui giải bài tốn liệt kê” có
đối tượng nghiên cứu là các bài tốn giải bằng thuật tốn quay lui.
1.3.2 Thời gian nghiên cứu:
“Thuật tốn quay lui” được nghiên cứu trong q 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.


Trường THPT Chuyên Bảo Lộc

2

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


PHẦN II
NỘI DUNG
2.1 Phương pháp (thuật tốn quay lui Backtracking)
Trong các kỹ thuật cơ bản để thiết kế thuật tố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 tốn khá lớn có dạng tổng
qt 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

tốn ta sẽ xác định được tập S i gồm các giá trị được chọn cho thành phần nghiệm x i.
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 bộ nghiệm. Từ bộ (x1, x2, ..., xi) lặp
lại q trình trên để tiếp tục mở rộng nghiệm cho thành phần x i+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 q trình mở rộng nghiệm ta ln kiểm tra nghiệm thành phần có phải
là nghiệm của bài tố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ì q 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 cách duyệt tuyến tính. Điểm tốt của thuật tốn quay
lui 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ững
nhá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ể S i
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.

Trường THPT Chuyên Bảo Lộc

3

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


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 tố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 tốn dùng được phương pháp này khơng nhiều.
Đa số các bài tố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 ngun tắc "thử sai" và quay lui. Về tư tưởng, các thuật
tố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 tốn cần có những kỹ
thuật nhất định.

Để giải bài tốn bằng thuật tố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 tố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).
* 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 tốn.
Một số lưu ý khi xây dựng thuật tốn quay lui:
* Phải duyệt qua mọi phương án của bài tố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 tốn bằng thuật tố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 tố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 tố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}
Trường THPT Chuyên Bảo Lộc
Trương Nguyễn Nha Trang
4


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt



Begin
for <mọi giá trị j có thể gán cho xi> do
Begin
<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>;
End;
End;
End;
Thuật tốn quay lui thường bắt đầu bằng lời gọi Try(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ố ngun 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 tốn quay lui có thể
viết:


Trường THPT Chuyên Bảo Lộc

5

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


Chương trình:
Program daynhiphan;
Const max = 30;
Var
x: array[1..max] of Integer;
n: Integer;
f:text;
{========================}
Procedure Incauhinh;
Var

{In cấu hình tìm được}

i: Integer;

Begin
for i := 1 to n do Write(f,x[i]); Writeln(f);
End;
{========================}
Procedure Try(i: Integer);


{Thử các cách chọn xi}

Var
j: Integer;
Begin
for j := 0 to 1 do

{Xét các giá trị có thể gán cho xi}

Begin
x[i] := j;

{Thử đặt xi=j}

If 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 xi+1}

End;
End;
{========================}
Begin
Assign(f,’nhiphan.inp’); Reset(f);
Readln(f,n);


Close(f);

Trường THPT Chuyên Bảo Lộc

6

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


Assign(f,’nhiphan.out’);
ReWrite(f);
Try(1); Close(f);

{Thử các cách chọn giá trị x1}

End.
{========================}
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ố ngun 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}
Ý tưởng:
Một tổ hợp chập k của n là một tập con k phần tử của tập n phần tử. Chẳng hạn
{1,2},{1,3},{1,4},{1,5},{2,3}, {2,4}, {2,5}, {3,4}, {3,5},{4,5};
tập {1,2,3,4,5} có các tổ hợp chập 2 là:
Vì trong tổ hợp khơng phân biệt thứ tự nên tập {1,2} cũng là tập {2,1} do đó ta
coi chúng chỉ là một tổ hợp.

Để đơn giản ta chỉ xét bài tốn tìm các tổ hợp của tập các số ngun từ 1 đến n.
Đối với một tập hữu hạn bất kỳ, bằng cách đánh số thứ tự của các phần tử, ta cũng
đưa được về bài tốn đối với tập các số ngun từ 1 đến n.
Nghiệm của bài tốn tìm các tổ hợp chập k của n phải thoả 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
Trường THPT Chuyên Bảo Lộc

7

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệ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ê.
Chương trình:

TOHOP.INP


TOHOP.OUT

Program tohop;

5 2

{1,2}

Const

{1,3}

max = 20;

{1,4}

Var

{1,5}

x: array[0..max] of Longint;

{2,3}

n, k: LONGINT; f,g:Text;

{2,4}

{============================}

Procedure Incauhinh;

{2,5}

{In ra tập con {x1, x2, ..., xk}}

{3,4}

Var
i: Integer;
Begin
Write(f,'{');
for i := 1 to k - 1 do Write(f,x[i],',');
Writeln(f,x[k],'}');
End;
{============================}
Procedure Try(i: Integer);

{Thử các cách chọn giá trị cho x[i]}

Var
j: Integer;
Begin
for j := x[i - 1] + 1 to n - k + i do
Begin
x[i] := j;
If i = k then Incauhinh
Trường THPT Chuyên Bảo Lộc

8


Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


Else Try(i + 1);
End;
End;
{============================}
Begin
Assign(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ố ngun 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 khơng lặp chập k của tập
{1, 2,.., n}.

Ý tưởng:
Để liệt kê các chỉnh hợp khơng lặp
chập k của tập S = {1, 2, ..., n} ta có thể
đư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 tốn tìm các chỉnh


CHINHHOP.INP

CHINHHOP.OUT

32

12
13
21
23
31

hợp khơng lặp chập k của tập n số ngun 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ừ
Trường THPT Chuyên Bảo Lộc

9

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệ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.
Chương trình:
Program Chinhhop;
Const
max = 20;
Var
x: array[1..max] of Integer;
c: array[1..max] of Boolean;
n, k: Integer;
f,g:Text;
{==========================}
Procedure Incauhinh;

{Thủ tục in cấu hình tìm được}

Var
i: Integer;
Begin
for i := 1 to k do
Write(f,x[i],' '); Writeln(f);
End;
{==========================}
Procedure Try(i: Integer);

{Thử các cách chọn xi}


Var
j: Integer;
Begin
for j := 1 to n do
If c[j] then

{Chỉ xét những giá trị j còn tự do}
Trường THPT Chuyên Bảo Lộc
Trương Nguyễn Nha Trang
10


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


Begin
x[i] := j;
If i = k then Incauhinh

{Nếu đã chọn được đến xk thì Incauhinh}

Else
Begin
c[j]:= False;

{Đánh dấu: j đã bị chọn}

Try(i+1);
c[j]:= True;


{Bỏ đánh dấu: j lại là tự do, để thử cách chọn khác của

xj}

End;
End;
End;
{==========================}
Begin
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 x1}

Close(g); Close(f);
End.
{==========================}
Nhận xét: khi k = n thì đây là chương trình liệt kê hốn vị.
VÍ DỤ 4. BÀI TỐ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ố ngun dương,
các cách phân tích là hốn vị của nhau chỉ tính là 1 cách.
Input: file văn bản PHANTICH.INP chứa số ngun dương n (n ≤ 30).
Output: file văn bản PHANTICH.OUT ghi các cách Ý tưởng số n.

Trường THPT Chuyên Bảo Lộc

11

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


PHANTICH.INP PHANTICH.OUT

Ý tưởng:

5

5= 1+1+1+1+1
5 = 1+1+1+2

Ta sẽ lưu nghiệm trong mảng x, ngồi
ra có một mảng t. Mảng t xây dựng như

5 = 1+1+3

sau: ti sẽ là tổng các phần tử trong mảng x

5 = 1+2+2

từ x1 đến xi: ti := x1 + x2 + ... + xi.


5 = 1+4
5 = 2+3

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 =
n tức là (xi = n - ti - 1) thì in kết quả.
Vậy thủ tục Try(i) thử các giá trị cho xi có thể mơ tả như sau: (để tổng qt cho i
= 1, ta đặt x0=1 và t0= 0).
- Xét các giá trị của xi từ xi - 1 đến (n - ti-1), cập nhật ti := ti - 1 + xi và gọi đệ quy tìm
tiếp.
- Cuối cùng xét giá trị xi = n - ti-1 và in kết quả từ x1 đến xi.
Chương trình:
Program Phantichs;
Const
max = 30;
Var
n: Integer; f:text;
x: array[0..max] of Integer;
t: array[0..max] of Integer;
{==========================}
Procedure Incauhinh(k: Integer);
Var i: Integer;
Begin
Write(f,n,' = ');
Trường THPT Chuyên Bảo Lộc

12

Trương Nguyễn Nha Trang



Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


for i := 1 to k - 1 do Write(f,x[i], '+');
Writeln(f,x[k]);
End;
{==========================}
Procedure Try(i: Integer);
Var
j: Integer;
Begin
for j := x[i - 1] to (n - t[i - 1]) do
Begin
x[i] := j;
t[i] := t[i - 1] + j;
If x[i] = n - t[i - 1] then
Incauhinh(i)
Else
Try(i + 1);
End;
End;
{==========================}
Begin
Assign(f, 'PHANTICH.INP'); Reset(f); Readln(f,n); x[0] := 1; t[0] := 0;
Close(f);
Assign(f, 'PHANTICH.OUT'); ReWrite(f);
Try(1);
Close(f);

End.
{==========================}

Trường THPT Chuyên Bảo Lộc

13

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


VÍ DỤ 5. BÀI TỐN XẾP N QN HẬU
Xét bàn cờ tổng qt kích thước n x n. Một qn hậu trên bàn cờ có thể ăn
được các qn 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 qn hậu trên bàn cờ sao cho khơng qn nào ăn qn nào.
Input: file văn bản QUEENS.INP chứa số ngun dương n ≤ 12.
Output: file văn bản QUEENS.OUT, mỗi dòng ghi một cách đặt n qn hậu.
QUEENS.INP
5

QUEENS.OUT
(1, 1); (2, 3); (3, 5); (4, 2); (5, 4); (1, 1); (2, 4);
(3, 2); (4, 5); (5, 3); (1, 2); (2, 4); (3, 1); (4, 3);
(5, 5); (1, 2); (2, 5); (3, 3); (4, 1); (5, 4); (1, 3);
(2, 1); (3, 4); (4, 2); (5, 5); (1, 3); (2, 5); (3, 2);
(4, 4); (5, 1); (1, 4); (2, 1); (3, 3); (4, 5); (5, 2);
(1, 4); (2, 2); (3, 5); (4, 3); (5, 1); (1, 5); (2, 2);
(3, 4); (4, 1); (5, 3); (1, 5); (2, 3); (3, 1); (4, 4);

(5, 2);

Ví dụ

một

cách

xếp
với n = 5

Ý tưởng:
Rõ ràng n qn hậu sẽ được đặt mỗi con một hàng vì hậu ăn được ngang, ta
gọi qn hậu sẽ đặt ở hàng 1 là qn hậu 1, qn hậu ở hàng 2 là qn hậu 2...
qn hậu ở hàng n là qn hậu n. Vậy một nghiệm của bài tốn sẽ được biết khi ta
tìm ra được vị trí cột của những qn hậu.
* Ta dùng 3 mảng logic để đánh dấu:
-

Mảng a[1..n]. ai = TRUE nếu như cột i còn tự do, ai = FALSE nếu như cột i

đã bị một qn hậu khống chế.
Trường THPT Chuyên Bảo Lộc

14

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt



- 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.

- 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 qn hậu thứ i, thì ta phải chọn ơ (i, j) khơng bị các qn
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).
Chương trình:
Program Quanhau;
Const max = 12;
Var
n: Integer;f,g:Text;
x: array[1..max] of Integer;
a: array[1..max] of Boolean;
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);
Close(f);
End;

{============================}
Procedure Incauhinh;
Var
i: Integer;
Trường THPT Chuyên Bảo Lộc

15

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


Begin
for i := 1 to n do Write(f,'(', i, ', ', x[i], '); '); Writeln(f);
End;
{============================}
Procedure Try(i: Integer);
Var j: Integer;
Begin
for j := 1 to n do
If a[j] and b[i + j] and c[i - j] then

{Chỉ xét những cột j mà ơ (i, j) chưa bị khống

chế}

Begin
x[i] := j;


{Thử đặt qn hậu i vào cột j}

If i = n then Incauhinh
Else Begin
a[j] := False; b[i + j] := False; c[i - j] := False;

{Đánh dấu}

Try(i + 1);
a[j] := True; b[i + j] := True; c[i - j] := True;

{Bỏ đánh dấu}

End;
End;
End;
{============================}
Begin
Assign(f, 'QUEENS.INP'); Reset(f);
khoitao;
Assign(f, 'QUEENS.OUT'); ReWrite(f);
Try(1);

Close(f);

End.
{============================}
VÍ DỤ 6. XỔ SỐ ĐIỆN TỐN
Trường THPT Chuyên Bảo Lộc


16

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


Có N người (đánh số từ 1 đến N) tham gia một đợt xổ số điện tốn. Mỗi người
nhậ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 đưa
và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 ứng
củ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.
Ghi chú:
- Các số trên cùng một dòng trong các file vào/ ra, được ghi cách nhau ít nhất một
dấu cách trắng.
- Giới hạn kích thước: N ≤ 100, M ≤50, K ≤10.
- Dữ liệu vào trong các test là hợp lệ và đảm bảo có ít nhất một đáp án.
Ví dụ:
XOSO.INP
594


XOSO.OUT
1234

24682

2347

56890
24562
12373
35691

Ý tưởng:
Trường THPT Chuyên Bảo Lộc

17

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


Ta nhận thấy rằng mỗi nghiệm của bài tố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 tố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ẻ.
Chương trình:
Program Xoso_dien_toan;
Var
A: array[0..100,0..11] of Byte;
kt: array[0..51] of Boolean;
Logic:array[0..101,0..51] of Boolean;
X:array[0..11] of Byte;
M,N,K : Byte; Diem : Integer;
F : Text;
{============================}
Procedure Init;
Begin
Fillchar(A,sizeof(A),0);
Fillchar(X,sizeof(X),0);
Fillchar(kt,sizeof(kt),1);
Fillchar(logic,sizeof(logic),0);
End;
{============================}
Procedure Doctep;
Var i,j : Byte;
Begin
Trường THPT Chuyên Bảo Lộc

18

Trương Nguyễn Nha Trang



Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


Assign(F, ’XOSO.INP’); Reset(F);
Readln(F,N,M,K);
For i:= 1 to N do
Begin
For j:= 1 to k do
Begin
Read(f,A[i,j]); Logic[i,A[i,j]]:= True;
End;
Read(F,A[i,k+1]); diem:=diem+A[i,k+1];
If A[i,k+1] = 0 then
For j:= 1 to k do kt[A[i,j]]:= False;
End;
Close(F);
End;
{============================}
{đá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;
Var v : Byte;
Begin
Chapnhan:= False;
For v:= 1 to n do
If (A[v,K+1] = 0) and logic[v,j] then Exit;
Chapnhan:=True;
End;
{============================}
Procedure Rutgon(j: Byte);
Var i : Byte;

Begin
For i:= 1 to N do
Trường THPT Chuyên Bảo Lộc

19

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


If logic[i,j] then
Begin
Dec(A[i,k+1]);Dec(diem);
End;
End;
{============================}
Procedure Morong(j: Byte);
Var i : Byte;
Begin
For i:= 1 to N do
If logic[i,j] then
Begin
Inc(A[i,k+1]); Inc(diem);
End;
End;
{============================}
Procedure Incauhinh;
Var d: Byte;

Begin
For d:= 1 to K do Write(f,X[d],’ ’); Writeln(F);
End;
{============================}
Procedure Try(i:Byte);
Var j: Byte;
Begin
For j:= X[i-1] + 1 to M - K + i do
If kt[j] and chapnhan(j) then
Begin
X[i]:= j;
Trường THPT Chuyên Bảo Lộc

20

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


Kt[j]:= False;
Rutgon(j);
If (diem = 0) and (i = k) then Incauhinh
else
If i < k then Try(i+1);
kt[j]:= True;
Morong(j);
End;
End;

{============================}
Begin
Doctep; Init;
Assign(F, 'XOSO.OUT’); ReWrite(F);
Try(1);
Close(f);
End.
{============================}
2.3 Dạng 2 : Tìm một nghiệm
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
Begin
<Thử cho xi := j>;
If <xi là phần tử cuối cùng trong cấu hình> then
Begin
<Thơng báo cấu hình tìm được>
Halt;

{thốt khỏi chương trình}

Trường THPT Chuyên Bảo Lộc

21


Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


End
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>;
End;
End;
End;
Trong bài tốn tìm 1 nghiệm, người ta thường đưa thêm vào các điều kiện đối với
các khả năng chọn nghiệm để bỏ bớt đi một số khả năng chọn nghiệm hoặc làm cho
khả năng chọn nghiệm thu hẹp lại.
+ Đ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).
+ 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 tốn. Vì vậy ta tính tốn trước nếu chọn nghiệm này

thì trạng thái bài tốn có thay đổi q 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 tốn hay khơng ? Nếu vượt qua thì ta khơng
chọn nghiệm ấy. Trong nhiều bài tố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 tốn nhanh chóng kết thúc.
Một khó khăn khác của loại tốn hiện 1 nghiệm là: trường hợp bài tố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
Trường THPT Chuyên Bảo Lộc

22

Trương Nguyễn Nha Trang


Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


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 tố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.

Trường THPT Chuyên Bảo Lộc

23

Trương Nguyễn Nha Trang



Tên đề tài: Ứng dụng thuật toán quay lui giải bài toán liệt


VÍ 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 qi vật Minotau (Nửa bò, nửa người). Ban ngày qi 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 qi vật
ngủ trong mê cung và hòn than lửa của nó được cất ở phòng “Dich”; ai lấy được hòn
than lửa ấy thì chinh phục được qi 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 qi vật. Têđê xuất phát từ phòng
XP và quyết định dùng thuật tố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 đặc
phòng và hành lang, chàng đã tìm được được phòng “Dich” và thu phục qi 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.
Chương trình:

Program Mecung;
Uses

Crt;

Const

Max = 50;

Var

A

: Array[1..Max*Max] of Byte;

T

: Array[1..Max*Max] of Byte;

Trường THPT Chuyên Bảo Lộc

24

Trương Nguyễn Nha Trang


×