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

Một số phương pháp chứng minh tính đúng của thuật toán và ứng dụng

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 (370.99 KB, 68 trang )

ĐẠI HỌC QUỐC GIA HÀ NỘI
TRƯỜNG ĐẠI HỌC KHOA HỌC TỰ NHIÊN
----------------------

Bế Thị Hương

MỘT SỐ PHƯƠNG PHÁP CHỨNG MINH
TÍNH ĐÚNG CỦA THUẬT TOÁN VÀ ỨNG DỤNG

LUẬN VĂN THẠC SĨ KHOA HỌC

Hà Nội – Năm 2015

1


ĐẠI HỌC QUỐC GIA HÀ NỘI
TRƯỜNG ĐẠI HỌC KHOA HỌC TỰ NHIÊN
-------------------------

Bế Thị Hương

MỘT SỐ PHƯƠNG PHÁP CHỨNG MINH
TÍNH ĐÚNG CỦA THUẬT TOÁN VÀ ỨNG DỤNG

Chuyên ngành: Cơ sở Toán học cho Tin học
Mã số: 60460110
LUẬN VĂN THẠC SĨ KHOA HỌC

NGƯỜI HƯỚNG DẪN KHOA HỌC: TS. NGUYỄN THỊ HỒNG MINH


Hà Nội – Năm 2015


LỜI CẢM ƠN

Lời đầu tiên em xin chân thành cảm ơn các thầy giáo, cô giáo giảng dạy
lớp cao học Cơ sở Toán học cho Tin học, Khoa Toán – Cơ – Tin học, Trường
Đại học Khoa học Tự nhiên – ĐHQGHN khóa 2012 – 2014. Các thầy cô đã rất
nhiệt tình, tâm huyết trong giảng dạy cho em học tập, nghiên cứu bổ sung được
thêm nhiều kiến thức mới quan trọng, hữu ích trong nghiên cứu và trong công
tác giảng dạy ở trường THPT chuyên. Đồng thời kịp nhận ra và sửa đổi, bổ sung
những kiến thức mình còn hiểu chưa thật chính xác giúp tăng cường năng lực và
phát triển tư duy trong nghiên cứu khoa học.
Đặc biệt, em gửi lời cảm ơn chân thành và sâu sắc tới cô giáo TS.Nguyễn
Thị Hồng Minh (Khoa Sau Đại học – ĐHQGHN). Cô đã giảng dạy cùng với
hướng dẫn luận văn cho em một cách rất khoa học, tận tâm, chu đáo và chi tiết
để em có thể hoàn thành luận văn một cách tốt nhất.
Cảm ơn gia đình đã cho em một chỗ dựa vững chắc để hoàn thành khóa
học cũng như hoàn thành luận văn này.
Mặc dù đã có rất nhiều cố gắng trong việc nghiên cứu khoa học để hoàn
thành luận văn tuy nhiên do hạn chế cá nhân về mặt thời gian nên em khó có thể
tránh được những thiếu sót. Kính mong thầy cô và các bạn đóng góp ý kiến quý
báu để hoàn chỉnh luận văn này hơn nữa.


MỤC LỤC
MỞ ĐẦU................................................................................................................................1
CHƯƠNG 1. TỔNG QUAN VỀ PHÂN TÍCH THUẬT TOÁN...........................................4
1.1. Một số khái niệm cơ bản.............................................................................................4
1.1.1. Bài toán................................................................................................................4

1.1.2. Thuật toán (Algorithm)........................................................................................5
1.1.3. Cấu trúc dữ liệu (Data Structure).......................................................................11
1.1.4. Chương trình (Program).....................................................................................11
1.2. Một số phương pháp thiết kế thuật toán....................................................................11
1.2.1. Kỹ thuật đệ quy..................................................................................................12
1.2.2. Phương pháp chia để trị (Divide and Conquer).................................................15
1.2.3. Phương pháp quay lui (Backtracking)................................................................16
1.2.4. Phương pháp nhánh cận.....................................................................................18
1.2.5. Phương pháp quy hoạch động (Dynamic Programming )..................................20
1.2.6. Phương pháp tham lam (Greedy Method)..........................................................22
1.3. Phân tích thuật toán...................................................................................................23
1.3.1. Tính đúng đắn của thuật toán.............................................................................23
1.3.2. Độ phức tạp thuật toán.......................................................................................24
a) Độ phức tạp về mặt thời gian...............................................................................24
b) Độ phức tạp về mặt không gian...........................................................................24
CHƯƠNG 2. MỘT SỐ PHƯƠNG PHÁP CHỨNG MINH TÍNH ĐÚNG CỦA THUẬT
TOÁN...................................................................................................................................25
2.1. Các chiến lược chứng minh tính đúng thuật toán......................................................25
2.2. Các phương pháp chứng minh tính đúng (Correctness proofs).................................26
2.2.1. Phương pháp quy nạp (induction)......................................................................26
a) Phương pháp quy nạp toán học............................................................................26
b) Chứng minh tính đúng của thuật toán bằng phương pháp quy nạp.....................27
c) Một số ví dụ..........................................................................................................27
2.2.2. Phương pháp bất biến vòng lặp (loop invariant)....................................................33
a) Chứng minh tính đúng của thuật toán bằng phương pháp bất biến vòng lặp..........33
b) Các đặc trưng của bất biến vòng lặp........................................................................35
c) Một số ví dụ.............................................................................................................35
CHƯƠNG 3. ỨNG DỤNG CHỨNG MINH TÍNH ĐÚNG CỦA MỘT SỐ THUẬT TOÁN
..............................................................................................................................................44



3.1. Bài toán: Dãy con đơn điệu tăng dài nhất.............................................................44
3.2. Bài toán: Chia kẹo.................................................................................................53
3.3. Bài toán Cây bao trùm nhỏ nhất (Minimum spanning tree)..................................54
KẾT LUẬN..........................................................................................................................61
TÀI LIỆU THAM KHẢO....................................................................................................62

5


MỞ ĐẦU
Thế kỷ XXI là thế kỷ của tri thức hiện đại, một nền tri thức không thể
không kể đến công cụ hỗ trợ đắc lực của máy tính điện tử trong mọi lĩnh vực
cuộc sống. Mặc dù công nghệ chế tạo ngày càng phát triển và phát triển với tốc
độ nhanh nhưng để sử dụng máy tính điện tử một cách hiệu quả cao thì thuật
toán (Algorithm) là thành phần luôn luôn quan trọng và không thể thiếu được kể
từ khi máy tính điện tử ra đời.
Theo lịch sử toán học nguồn gốc của từ thuật toán “Algorithm” là bắt
nguồn từ “Algorism” tên của một nhà bác học nổi tiếng người Arập là Abu Jafar
Mohammed ibn Musâ al Khowârizmi. (Phiên âm của từ al Khowârizmi chính là
Algorism). Ông là người đã viết hai quyển sách nổi tiếng là “Sơ lược về các
phép tính” và “Về hệ đếm ấn độ” vào khoảng năm 850. Đây là những quyển
sách giáo khoa nổi tiếng về toán học.
Lịch sử đã ghi nhận người được coi là nhà lập trình đầu tiên trên thế giới
là nữ bá tước Ada Lovelace (10/12/1815 - 27/11/1852), tên khai sinh là Augusta
Ada Byron. Các nhà khoa học về sau cho rằng thuật toán (viết năm 1842) của
Ada Lovelace là những thuật toán máy tính đầu tiên do con người lập ra, vì nó
lần đầu tiên thể hiện rõ từng bước phát triển logic, đặc trưng hoạt động xác định
dành riêng cho máy tính.
Với lịch sử lâu đời của thuật toán đã được nghiên cứu và phát triển cho tới

tận ngày nay và sẽ vẫn còn tiếp tục được nghiên cứu và phát triển hơn nữa. Khi
lập trình câu hỏi luôn luôn được đặt ra là thuật toán được thiết kế hoặc thuật toán
được sử dụng có đúng hay không? Điều này đảm bảo cho một chương trình máy
tính thực hiện có cho kết quả đúng hay không? (Chưa kể đến các kỹ năng của
người lập trình). Vì vậy việc xây dựng một thuật toán tốt để giải bài toán đã cho

1


là bước quan trọng có thể nói là quan trọng nhất trong việc giải một bài toán trên
máy tính điện tử.
Để đánh giá một thuật toán là tốt có rất nhiều tiêu chí trong đó không thể
bỏ qua tính đúng của thuật toán. Và đây cũng là nội dung chính của luận văn này
theo đề tài nghiên cứu: “Một số phương pháp chứng minh tính đúng của thuật
toán và ứng dụng”. Luận văn nhằm tìm hiểu, nghiên cứu, tổng hợp phương pháp
chứng minh tính đúng của thuật toán. Cấu trúc luận văn gồm 3 chương, nội dung
chính như sau:
Chương 1. Tổng quan về phân tích thuật toán.
Chương này nhằm tổng hợp lại một số kiến thức chung về bài toán, thuật
toán, cấu trúc dữ liệu, chương trình và kiến thức về phân tích thuật toán. Gồm
các định nghĩa, khái niệm và các ví dụ để minh họa.
Trong chương này còn tổng hợp lại một số phương pháp thiết kế thuật
toán thường sử dụng trong thực tế. Như kỹ thuật đệ quy, phương pháp chia để
trị, phương pháp quay lui, phương pháp nhánh cận, phương pháp quy hoạch
động và phương pháp tham lam.
Chương 2. Một số phương pháp chứng minh tính đúng của thuật toán.
Nội dung chương này gồm các chiến lược chứng minh tính đúng của thuật
toán; các phương pháp cụ thể để chứng minh tính đúng của thuật toán như
phương pháp quy nạp và phương pháp bất biến vòng lặp. Đây cũng chính là
điểm mới của luận văn.

Trong đó, phương pháp quy nạp chứng minh cho các thuật toán đệ quy,
phương pháp bất biến vòng lặp chứng minh cho các thuật toán không đệ quy.
Đối với mỗi phương pháp trình bày về đặc điểm, phương pháp chung đồng thời
nêu một số ví dụ về thuật toán và chứng minh tính đúng của các thuật toán đó.
Đối với những thuật toán phức tạp có chứa cả đệ quy và lặp thì cần kết hợp khéo

2


léo cả hai phương pháp chứng minh tính đúng của thuật toán là quy nạp và bất
biến vòng lặp.
Chương 3. Ứng dụng chứng minh tính đúng của một số thuật toán.
Nghiên cứu một số bài toán có sử dụng các thuật toán kinh điển, thường
sử dụng và vận dụng lý thuyết của chương 2 để chứng minh tính đúng của các
thuật toán đó. Như bài toán dãy con đơn điệu tăng dài nhất; Chia kẹo; Cây bao
trùm nhỏ nhất.

3


CHƯƠNG 1. TỔNG QUAN VỀ PHÂN TÍCH THUẬT TOÁN
Để khẳng định được một thuật toán là tốt là một điều không dễ dàng gì.
Thật vậy, để đánh giá một thuật toán tốt ta cần rất nhiều kỹ thuật từ thiết kế,
phân tích đến đánh giá một thuật toán. Ở chương này đề cập tổng quát đến các
vấn đề trong phân tích thuật toán và một số thuật toán cơ bản thường dùng trong
khoa học tính toán hiện đại.
1.1. Một số khái niệm cơ bản
1.1.1. Bài toán
Khoa học máy tính ngày nay giải quyết rất nhiều vấn đề trong thực tế
trong nhiều lĩnh vự khác nhau, những vấn đề đó ta thường gọi là bài toán. Tuy

nhiên bài toán ở đây không phải là một trường hợp cụ thể mà là bài toán mang
tính tổng quát bao gồm hầu như tất cả các khả năng có thể của thế giới thực
trong vấn đề cần giải quyết. Như vậy, nói một cách dễ hiểu thì bài toán là việc
nào đó ta muốn máy tính thực hiện. Có thể là một yêu cầu đơn giản như in ra
một dòng chữ trên màn hình, giải phương trình bậc hai, giải hệ phương trình bậc
nhất hai ẩn hoặc kiểm tra một số là chẵn hay lẻ,... Nhưng cũng có thể là giải
quyết những vấn đề rất phức tạp như tìm đường đi trong mê cung, tìm đường đi
ngắn nhất, tìm cây bao trùm,...
Điểm quan trọng đầu tiên khi giải một bài toán trên máy tính đó là cần xác
định rõ những gì đã biết input (dữ liệu vào) và kết quả cần thu được output (dữ
liệu ra) và phân tích mối quan hệ giữa hai yếu tố đó. Sau đây là một số ví dụ về
bài toán:
• Bài toán 1.1: Kiểm tra tính nguyên tố của một số nguyên dương cho trước.
− Input: Số nguyên dương N.
− Output: Xác định N là số nguyên tố hoặc N không là số nguyên tố.

4


• Bài toán 1.2: Giải phương trình bậc hai ax2+bx+c=0 (a≠0).
− Input: Các số thực a, b, c (a≠0).
− Output: Các nghiệm x thỏa mãn phương trình đã cho hoặc thông báo
không có nghiệm.
• Bài toán 1.3: Tìm ước số chung lớn nhất của hai số nguyên dương a, b.
− Input: Hai số nguyên dương a, b.
− Output: Ước số chung lớn nhất của a và b.
• Bài toán 1.4: Xác định vị trí của phần tử có giá trị bằng số nguyên x trong
một dãy số nguyên a1, a2,..., an.
− Input: Số n; dãy số nguyên a1, a2, ..., an và số nguyên x.
− Output: Chỉ số i nếu x=ai và là 0 nếu x không có mặt trong dãy.

• Bài toán 1.5. Cho đồ thị vô hướng G=(V, E). Tìm đường đi ngắn nhất từ đỉnh
u tới đỉnh v của đồ thị G.
− Input: Đồ thị vô hướng G=(V, E) và hai đỉnh u,v.
− Output: Xác định đường đi có độ dài ngắn nhất d=(u=v 1,v2,...,vn=v)
(với đỉnh vi thuộc V, cung (vi, vi+1) thuộc E).
• Bài toán 1.6. Sắp xếp một dãy các số cho trước thành dãy không giảm.
− Input: Số n và dãy gồm n số < a1, a2, …, an>.
− Output: Một hoán vị < a'1, a'2, …, a'n > của chuỗi đầu vào thỏa mãn:
a'1 ≤ a'2 ≤ …≤ a'n.
1.1.2. Thuật toán (Algorithm)
Để giải một bài toán trên máy tính sau khi đã xác định rõ ràng về bài toán
việc quan trọng nhất là phải đưa ra một thuật toán tốt, thuật toán này có thể là
một thiết kế mới hoặc lựa chọn một thuật toán đã có. Thuật toán là để biểu diễn
về cách giải một bài toán trên máy tính.

5


Một bài toán có thể có nhiều cách giải nhưng một thuật toán chỉ giải một
bài toán mà thôi. Đến hiện nay thì đã có nhiều định nghĩa về thuật toán và sau
đây là một lựa chọn định nghĩa thuật toán:
Định nghĩa: Thuật toán (Algorithm) để giải một bài toán là một dãy hữu
hạn các thao tác được sắp xếp theo một trình tự xác định, sao cho sau khi thực
hiện dãy thao tác ấy, từ dữ liệu vào có thể là một giá trị hoặc một tập giá trị
(input) của bài toán ta nhận được một giá trị hoặc một tập giá trị còn gọi là dữ
liệu ra (output) của bài toán đó.
Để thuật toán được rõ ràng, chính xác, dễ hiểu, dễ đọc hơn người ta đưa ra
các phương pháp biểu diễn thuật toán. Gồm có ba phương pháp biểu diễn thuật
toán như sau:
• Ngôn ngữ tự nhiên (Natural languages): Dùng ngôn ngữ tự nhiên để liệt kê

từng bước của thuật toán. Phương pháp này không có các quy tắc chung do
đó người viết và người đọc dễ dàng thực hiện được mà không cần phải nắm
được những quy tắc. Nhưng viết thuật toán theo cách này thường dài dòng,
không thể hiện được rõ cấu trúc thuật toán và đôi lúc có thể gây khó hiểu
hoặc hiểu nhầm đối với người đọc.
• Sơ đồ khối (Flowcharts): là công cụ trực quan để thể hiện thuật toán. Sơ đồ
khối biểu diễn được sự phân cấp của thuật toán cũng như trình tự thực hiện
thuật toán. Đặc biệt phù hợp với những thuật toán phức tạp, khó theo dõi quá
trình xử lý. Tuy nhiên, phương pháp biểu diễn này có nhược điểm là cồng
kềnh, cần không gian biểu diễn lớn hơn các phương pháp khác. Trong sơ đồ
khối thường sử dụng một số khối và cung để biểu diễn thuật toán như sau:
− Hình oval: Thể hiện thao tác nhập, xuất dữ liệu;
− Hình thoi: Thể hiện thao tác so sánh, chỉ có hai nhánh logic là đúng hoặc
sai;
− Hình chữ nhật: Thể hiện các phép gán, các thao tác tính toán;

6


− Cung có hướng: Thể hiện trình tự thực hiện các thao tác, thao tác này nối
tiếp thao tác kia theo hướng mũi tên.
− Nút nối: Để nối các phần khác nhau của sơ đồ khối lại với nhau. Thường
biểu diễn bằng hình tròn, bên trong có kí hiệu để biết là nút nối nào.
− Nút nối trang: Với các sơ đồ khối lớn cần biểu diễn trên nhiều trang thì
biểu diễn thêm bằng nút nối trang.
• Giả mã (Pseudocode): Sử dụng cú pháp của một ngôn ngữ lập trình nào đó
kết hợp với ngôn ngữ tự nhiên để thể hiện thuật toán. Với giả mã người lập
trình tận dụng được các định nghĩa và cấu trúc của ngôn ngữ lập trình. Đây
cũng là phương pháp chính được chọn lựa để biểu diễn các thuật toán trong
luận văn này.

Sau đây là ví dụ về thuật toán và ba cách để biểu diễn thuật toán tương
ứng của bài toán 1 đã nêu ở mục 1.1.1
Phân tích bài toán: Theo định nghĩa số nguyên tố thì số nguyên dương N
là số nguyên tố nếu N chỉ có đúng 2 ước số là 1 và chính nó. Nên ta có với
N là số nguyên dương thì:
− Nếu N=1 thì N không là số nguyên tố;
− Nếu 1− Nếu N≥4 thì N là số nguyên tố nếu N không có ước số từ 2 đến phần

nguyên căn bậc 2 của N, kí hiệu:  N  .
Do đó ta có thuật toán như sau:
• Thuật toán biểu diễn bằng ngôn ngữ tự nhiên:
Bước 1. Nhập số nguyên dương N;
Bước 2. Nếu N=1 thì thông báo N không nguyên tố rồi kết thúc;
Bước 3. Nếu N<4 thì thông báo N là số nguyên tố rồi kết thúc;

7


Bước 4. i = 2;
Bước 5. Nếu i > [ N ] thì thông báo N là nguyên tố rồi kết thúc;
Bước 6. Nếu N chia hết cho i thì thông báo N không nguyên tố rồi kết
thúc;
Bước 7. i = i+1 rồi quay lại bước 5.
• Thuật toán biểu diễn bằng sơ đồ khối:

8


Nhập N

nguyên dương

Đúng

N=1?
Sai
N<4?

Đúng

Sai
i=2

i>  N  ?

Đúng

Sai

i=i+1

Sai

N chia hết
cho i ?
Đúng

Thông báo N
không là số nguyên
tố rồi kết thúc.

• Thuật toán biểu diễn bằng giả mã:
Ngto(N):int ≡

//Hàm kiểm tra số N có phải nguyên tố hay không

if (N=1)
return 0;
else

9

Thông báo N
là số nguyên tố
và kết thúc.


if (N<4)
return 1
else
for i=2 to [sqrt(N)] do
if (N chia hết cho i) then
return 0;
return 1;
End.
Các tính chất của thuật toán: Khi viết thuật toán cần chú ý đến những
tính chất quan trọng sau đây:
− Tính tổng quát (Generality): Thuật toán áp dụng cho mọi trường hợp của bài
toán (nhiều bộ dữ liệu vào) chứ không phải chỉ cho một trường hợp riêng lẻ
(một bộ dữ liệu vào) nói một cách khác là áp dụng cho một lớp các bài toán
cùng loại;

− Tính dừng (Stationarity): Thuật toán phải kết thúc sau một số hữu hạn lần
thực hiện các thao tác, mặc dù đối với các bài toán phức tạp số lần này có thể
là rất lớn;
− Tính xác định (Definiteness): Sau khi thực hiện một thao tác thì hoặc là thuật
toán kết thúc hoặc là có đúng một thao tác xác định để được thực hiện tiếp
theo (do đó luôn thực hiện được).
− Tính hiệu quả (Effectiveness): Được đánh giá dựa trên một số tiêu chuẩn như
là sử dụng không gian bộ nhớ và thời gian thực hiện thuật toán. Đây cũng
chính là tính chất quan trọng để đánh giá và lựa chọn thuật toán để giải quyết
một bài toán trong thực tế.
− Tính đúng đắn (Generalliness): Sau khi thuật toán kết thúc ta phải nhận được
output cần tìm. Tính đúng là tính chất hiển nhiên khi giải một bài toán muốn

10


đạt được nhất nhưng cũng là tính chất khó đạt tới nhất. Vì không phải lúc nào
cũng tìm được lời giải đúng cho bài toán đã đặt ra.
1.1.3. Cấu trúc dữ liệu (Data Structure)
Cấu trúc dữ liệu là một cách lưu trữ dữ liệu trong máy tính sao cho việc
khai thác chúng được hiệu quả hơn. Trong thiết kế chương trình việc lựa chọn
cấu trúc dữ liệu rất quan trọng. Vì mỗi loại cấu trúc dữ liệu phù hợp với một số
loại ứng dụng khác nhau. Một cấu trúc dữ liệu được thiết kế cho phép thực hiện
nhiều phép toán, tiết kiệm tài nguyên, ít thời gian xử lý và sử dụng không gian
bộ nhớ càng ít thì càng tốt. Các cấu trúc dữ liệu được triển khai bằng cách sử
dụng các kiểu dữ liệu, các tham chiếu và các phép toán trên cấu trúc dữ liệu đó
được cung cấp bởi một ngôn ngữ lập trình cụ thể. Sự liên hệ giữa cấu trúc dữ
liệu và thuật toán rất chặt chẽ, thuật toán cần được thao tác trên các cấu trúc dữ
liệu nào đó và các cấu trúc dữ liệu sẽ được xử lý bởi thuật toán nào đó. Và vì
không có một cấu trúc duy nhất nào có thể tốt cho mọi mục đích hay phù hợp với

mọi thuật toán do đó điều quan trọng khi nghiên cứu cấu trúc dữ liệu là cần phải
biết sức mạnh cũng như giới hạn của cấu trúc dữ liệu đó để sử dụng cho phù
hợp, hiệu quả.
1.1.4. Chương trình (Program)
Chương trình = Thuật toán + Cấu trúc dữ liệu. Chương trình là sự thể hiện
bằng một ngôn ngữ lập trình cụ thể một thuật toán đã cho được thể hiện trên một
cấu trúc dữ liệu xác định. Việc lựa chọn cấu trúc dữ liệu phù hợp với thuật toán
hoặc ngược lại lựa chọn thuật toán phù hợp với cấu trúc dữ liệu cụ thể còn phụ
thuộc vào mục đích của chương trình, kỹ năng người lập trình và khả năng của
ngôn ngữ lập trình cụ thể.
1.2. Một số phương pháp thiết kế thuật toán
Ngày nay có nhiều phương pháp thiết kế thuật toán đã được nghiên cứu và
sử dụng trong công nghệ phần mềm. Có những bài toán có thể giải được bằng

11


thuật toán nhưng cũng những bài toán chưa có thuật toán hoặc chỉ có thuật toán
cho lời giải tương đối chấp nhận được. Trong luận văn này nghiên cứu về các
phương pháp thiết kế thuật toán và ứng dụng cho các bài toán có thuật toán để
giải.
1.2.1. Kỹ thuật đệ quy
Đệ quy là một khái niệm cơ bản trong toán học và tin học. Ta nói một đối
tượng là đệ quy nếu nó được định nghĩa qua chính nó hoặc một đối tượng cùng
dạng với chính nó bằng quy nạp. Ý tưởng của kỹ thuật đệ quy đó là chia bài toán
cần giải quyết thành nhiều bài toán nhỏ hơn, việc chia này thực hiện cho đến khi
bài toán con có lời giải và lời giải này thường là tường minh và tương đối đơn
giản.
Ví dụ: Kí hiệu |S| là số các phần tử của tập hữu hạn S.
Nếu S=∅ thì |S|=0

Ngược lại S≠∅ thì tất có một phần tử x∈S khi đó |S|=|S\{x}|+1.
Khái niệm giải thuật đệ quy: Một bài toán T được thực hiện bằng giải
thuật của một bài toán T’ có dạng giống như T thì giải thuật đó gọi là giải thuật
đệ quy.
Bài toán T’ tuy có dạng giống bài toán T nhưng T’ theo một nghĩa nào đó
phải là bài toán nhỏ hơn T. Bài toán T’ phải dễ giải hơn bài toán T và việc giải
bài toán T’ không cần dùng đến T.
Do đó phương pháp chung sử dụng kỹ thuật đệ quy để giải một bài toán là
ta chia bài toán đó thành các bài toán con đơn giản hơn cùng loại. Phương pháp
này còn được gọi là kỹ thuật lập trình chia để trị. Chính nó là chìa khóa để thiết
kế nhiều giải thuật quan trọng, là cơ sở của phương pháp quy hoạch động. Sau
đây là một số ví dụ về bài toán mang bản chất đệ quy:
• Ví dụ 1: Bài toán tính n giai thừa.

12


Cho n là một số tự nhiên (n≥0). Hãy tính giai thừa của n. Biết rằng 0!=1
và n!=(n-1)!n.
Phân tích:
Theo giả thiết, ta có : n! = (n-1)!n. Như vậy :
Để tính n! ta cần phải tính (n-1)!
Để tính (n-1)! ta phải tính (n-2)!
...................................................
Cứ như vậy, cho tới khi gặp trường hợp 0!.

• Ví dụ 2: Dãy Fibonacci
Dãy Fibonacci là dãy vô hạn các số tự nhiên. Số Fibonacci thứ n, ký hiệu
F(n), được định nghĩa như sau:
F(n) = 1, nếu n=1 hoặc n=2;

F(n) = F(n-1) + F(n-2), nếu n≥3.
Yêu cầu: Tính số fibonacci thứ n với n nguyên dương cho trước.
Phân tích:
Với n≥3 :
Đế tính F(n) ta phải tính F(n-1) và F(n-2).
Để tính F(n-1) ta lại phải tính F(n-2) và F(n-3), và để tính F(n-2) ta phải
tính F(n-3) và F(n-4).
.........................................................
Cứ như vậy cho đến khi n=1 và n=2.
Đặc trưng của các bài toán có thể giải bằng đệ quy:
− Các bài toán phụ thuộc tham số;

13


− Ứng với các giá trị đặc biệt nào đó của tham số thì bài toán có lời giải (trường
hợp suy biến). Phần này quan trọng vì nó quyết định tính dừng của thuật
toán;
− Trong trường hợp tổng quát bài toán có thể quy về dạng tương tự với một bộ
giá trị mới của tham số và sau hữu hạn lần sẽ dẫn tới trường hợp suy biến.
Phối hợp lời giải của các bài toán con để có được lời giải cho bài toán ban
đầu cần giải quyết.
Lược đồ giải thuật đệ quy:
Dequy(Tn) ≡;
if (Tn=T0)

//Trường hợp suy biến

<Thực hiện thuật toán trường hợp suy biến>;
else


//Trường hợp tổng quát
<Lệnh;>
Dequy(Tn-1);
<Lệnh;>

endif;
End.

Ví dụ: Sau đây là thuật toán Tính n! (n≥0).
S(n): int ≡;

//Hàm đệ quy tính giá trị n giai thừa

if (n=0) then
else

S=1

S=n*S(n-1);

End.
Giả sử tính 4!, thuật toán thực hiện như sau:
4! = 4 * 3!

14


3! = 3 * 2!
2! = 2 * 1!

1! = 1 * 0!
0! = 1
Kết quả 4! = (((1*1)*2)*3)*4 = 24.
1.2.2. Phương pháp chia để trị (Divide and Conquer)
Trong thực tế có nhiều thuật toán hữu ích được sử dụng có bản chất đệ
quy, tức là trong thuật toán có lời gọi đến chính nó một hoặc nhiều lần để giải
bài toán tương tự nhưng với kích thước dữ liệu vào nhỏ hơn.
Phương pháp chia để trị có tư tưởng là chia bài toán ban đầu thành các bài
toán con tương tự. Các bài toán con tiếp tục được chia nhỏ hơn, cứ chia liên tiếp
như vậy cho tới khi gặp bài toán con đã có lời giải hoặc có thể dễ dàng đưa ra lời
giải. Sau đó lần lượt giải các bài toán con này và kết hợp các kết quả lại với nhau
ta thu được kết quả cần tìm của bài toán ban đầu.
Lược đồ tổng quát thuật toán chia để trị với mô hình đệ quy như sau:
Chia_tri(A, x) ≡;

//Tìm nghiệm x của bài toán A

if (A đủ nhỏ) then Giai(A)

//Giải bài toán con đủ nhỏ

else
<Chia A thành các bài toán con A1, A2, ..., An>;
for i=1 to n do Chia_tri(Ai, xi);
<Kết hợp các nghiệm xi để được x>;
endelse;
End.
Ví dụ Số Fibonacci:
Bài toán Số Fibonacci được cho bởi công thức sau:


15


 F1 =1

 F2 =1
 F =F + F (n ≥ 3)
 n n-1 n-2

Hãy tính số Fibonacci thứ n?
Thuật toán chia để trị sau đây sử dụng hàm F(n) để tính số Fibonacci thứ n
(n là số nguyên dương) dựa vào F(n-1) và F(n-2):
F(n):int ≡;

//Hàm tính số Fibonacci thứ n.

if n≤2 then
else

F=1

F=F(n-1)+F(n-2);

End.
1.2.3. Phương pháp quay lui (Backtracking)
Tư tưởng của thuật toán quay lui đó là tìm nghiệm của bài toán bằng cách
xem xét tất cả các phương án có thể. Ta có thể thử duyệt các phương án cho đến
khi tìm thấy phương án đúng còn gọi là phương pháp thử sai. Với tốc độ xử lí
nhanh của máy vi tính thì phương pháp này có thể giải quyết được nhiều bài toán
tuy nhiên nếu kích thước bài toán quá lớn thì nó trở nên không phù hợp. Bởi vì

nếu kích thước bài toán lớn thì kéo theo thời gian duyệt các phương án và độ
phức tạp về mặt không gian cũng lớn và có thể lớn đến mức nào đó không thể
chấp nhận được. Do đó phương pháp này thường chỉ hữu dụng với các bài toán
có kích thước nhỏ.
Không mất tổng quát, việc tìm nghiệm của nhiều bài toán có thể quy về
việc tìm véc tơ hữu hạn X= ( x1 , x 2 , ..., x n , ...) độ dài véc tơ có thể xác định trước
hoặc không tùy thuộc vào điều kiện của bài toán. Trong đó các thành phần x i
thuộc một tập hữu hạn nào đó sao cho véc tơ X thỏa mãn một số điều kiện theo
yêu cầu đề bài. Tùy từng bài toán cụ thể mà ta có thể quy về tìm một véc tơ hoặc
tìm tất cả các véc tơ hoặc đếm các véc tơ thỏa mãn yêu cầu bài toán đặt ra.
Lược đồ tổng quát thuật toán quay lui với mô hình đệ quy như sau:

16


Quaylui(i) ≡ ;

//Tìm thành phần thứ i thỏa mãn yêu cầu

<Xác định tập Si >;

//Si là tập chứa các thành phần xi

for xi ∈ Si do
<Ghi nhận thành phần thứ i>;
if (Tìm thấy nghiệm) then <Đưa ra nghiệm>;
else Quaylui(i+1);
<Loại thành phần i>;
endfor;
End.

Một số lưu ý khi giải bài toán bằng thuật toán quay lui:
− Phải biểu diễn được nghiệm của bài toán dưới dạng một dãy các đối tượng
như véc tơ X mà các thành phần được chọn dần từng bước từ x 1, x2, ..., xi,...
cho đến hết các thành phần của véc tơ X.
− Xác định được tập hữu hạn Si chứa các phương án có thể của xi.
− Tìm các điều kiện để một véc tơ X được chọn là nghiệm của bài toán.
Ví dụ Bài toán tổ hợp:
Hãy xác định tất cả các tổ hợp chập k của n phần tử. Để đơn giản ta chỉ xét
bài toán tìm các tổ hợp của tập các số nguyên đầu tiên từ 1 đến n.
Phân tích bài toán:
− Input: k, n nguyên dương
− Output: Tất cả các tổ hợp chập k của n.
Theo toán học ta đã biết số tổ hợp k của n được tính theo công thức sau:
C kn =

n!
k!(n − k)!

2
Chẳng hạn với k=2 và n=3 thì có số tổ hợp là: C3 =

3!
=3
2!(3 − 2)!

Với S = {1, 2, 3}. Các tổ hợp chập 2 của 3 phần tử số nguyên dương đầu
tiên là: {1, 2}; {1, 3}; {2, 3}.

17



Tổng quát nghiệm X của bài toán tìm tổ hợp chập k của n số nguyên
dương đầu tiên phải thỏa mãn các điều kiện sau đây:
− Véc tơ X = ( x1 , x 2 , ..., x k ) ;
− Tập S = {1, 2, 3, ..., n-1, n} và xi ∈ S;
− Vì tập hợp không phân biệt thứ tự các phần tử nên ta sắp xếp thứ tự các phần
tử để tránh bị sót nghiệm và không có hai nghiệm nào trùng nhau, chẳng hạn:
xi < xi+1.
− Tìm tất cả các cấu hình thỏa mãn.
Thuật toán quay lui như sau:
Tohop(i)≡; //Tìm kiếm thành phần thứ i thỏa mãn
for j=x[i-1] +1 to n-k+i do
x[i] =j;
if i=k then
else

<In ra nghiệm X>

Tohop(i+1);

endfor;
End;
1.2.4. Phương pháp nhánh cận
Phương pháp quay lui mặc dù có ưu điểm là cho kết quả đúng của bài
toán nhưng nếu kích thước bài toán quá lớn thì sẽ không giải quyết được các vấn
đề về thời gian thực hiện và không gian lưu trữ. Một trong những phương pháp
giải quyết vấn đề này là phương pháp nhánh cận. Với phương pháp này ta có thể
thu hẹp phạm vi tìm kiếm phương án đúng bằng cách loại trừ đi một số phương
án mà cho rằng nó sẽ không đưa đến kết quả đúng ở bước thứ i nào đó. Do đó
phương pháp nhánh cận tiết kiệm được thời gian tính toán và không gian lưu trữ.

Có thể nói nhánh cận là một cải tiến của phương pháp quay lui, áp dụng để giải

18


một bài toán tối ưu. Trong đó, bài toán tối ưu là bài toán yêu cầu tìm phương án
tốt nhất theo một tiêu chí nào đó.
Giả sử nghiệm của bài toán biểu diễn bởi một véc tơ hữu hạn X=

( x1 , x 2 , ..., x n , ...)

độ dài véc tơ có thể xác định trước hoặc không tùy thuộc vào

điều kiện của bài toán. Trong đó các thành phần x i thuộc một tập hữu hạn nào đó
sao cho véc tơ X thỏa mãn một số điều kiện theo yêu cầu đề bài đồng thời thỏa
mãn hàm mục tiêu f(X). Xét các điều kiện để hàm mục tiêu f(X) phải đạt giá trị
lớn nhất hoặc nhỏ nhất tùy thuộc vào yêu cầu của bài toán. Sử dụng hàm mục
tiêu để đánh giá độ tốt của phương án được xem xét và lựa chọn.
Tư tưởng của phương pháp nhánh cận như sau: Ta xây dựng dần dần
các thành phần của nghiệm. Giả sử ta đã xây dựng được k thành phần của véc tơ
nghiệm ( x1 , x 2 , ..., x k ) , nhiệm vụ tiếp theo là phải xây dựng được thành phần
thứ k+1 tức là tìm xk+1 cho phù hợp. Trong quá trình mở rộng nghiệm như vậy
nếu như bằng cách nào đó có thể đánh giá được tất cả các nghiệm mở rộng của
nó ( x1 , x 2 , ..., x k , x k+1 ,...) đều không tốt bằng nghiệm tốt nhất đã biết thì ta
không mở rộng nghiệm theo nhánh này nữa. Như vậy thuật toán nhánh cận sẽ
nhanh hơn thuật toán quay lui vét cạn vì nhánh cận không phải duyệt hết tất cả
các trường hợp của bài toán. Nói tóm lại, với phương pháp nhánh cận ta có thể
thu hẹp phạm vi tìm kiếm nghiệm bằng cách đánh giá trong quá trình ta mở rộng
các thành phần của nghiệm để bỏ đi những nhánh chắc chắn không phải là
nghiệm của bài toán. Do đó phương pháp nhánh cận sẽ nhanh hơn và bớt độ

phức tạp về mặt không gian.
Lược đồ tổng quát phương pháp nhánh cận bằng mô hình đệ quy:
Nhanhcan(i)≡;

//Tìm thành phần thứ i thỏa mãn

<Đánh giá các nghiệm mở rộng thành phần>;
if (Các nghiệm mở rộng không tốt hơn Totnhat) then

19


Exit;

//Bỏ qua các nhánh đánh giá được là không tối ưu

<Xác định Si>;
for xi ∈ Si do
<Ghi nhận thành phần thứ i>;
if (Tìm thấy nghiệm) then
<Cập nhật Totnhat> //Tốt nhất tại thời điểm đó
else Nhanhcan(i+1);
<Loại thành phần i>;
endfor;
End.
1.2.5. Phương pháp quy hoạch động (Dynamic Programming )
Một trong những phương pháp thiết kế thuật toán giải bài toán tối ưu đó
là: Quy hoạch động. Tư tưởng của phương pháp quy hoạch động giống như
phương pháp chia để trị ở chỗ chia bài toán ban đầu thành nhiều bài toán con,
chia liên tiếp cho đến khi bài toán con có lời giải. Sau đó việc giải quyết bài toán

ban đầu trở nên đơn giản hơn bằng cách kết hợp lời giải của các bài toán con.
Nhưng phương pháp quy hoạch động tiên tiến hơn phương pháp chia để trị ở chỗ
phương pháp quy hoạch động lưu trữ lại kết quả của các bài toán con ở nhiều cấp
và sử dụng lại chúng nhiều lần mà không cần phải tính toán lại. Ngược lại,
phương pháp chia để trị giải các bài toán con một cách độc lập và mỗi lần như
vậy lại phải tính toán lại một dãy các bài toán con đệ quy cần sử dụng. Nên với
phương pháp chia để trị thì một bài toán con có thể phải tính đi tính lại nhiều lần
dẫn tới việc tốn thời gian tính toán. Phương pháp quy hoạch động đã khắc phục
được nhược điểm này của phương pháp chia để trị bằng cách sử dụng một bảng
gọi là bảng phương án. Bảng này dùng để lưu trữ lời giải của các bài toán con
nhiều cấp đã được giải.

20


×