Tải bản đầy đủ (.docx) (85 trang)

Ứng dụng phương pháp quy hoạch động trong bồi dưỡng học sinh giỏi tin – trung học phổ thô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 (854.08 KB, 85 trang )

LỜI CAM ĐOAN
Tôi xin cam đoan đây là công trình nghiên cứu của riêng cá nhân tôi, dưới sự
hướng dẫn khoa học của PGS.TS. Hoàng Quang.
Các kết quả nêu trong luận văn là trung thực và chưa từng được công bố trong
bất kỳ công trình khoa học nào khác.

1


LỜI CẢM ƠN
Lời đầu tiên, tôi xin gởi lời cảm ơn chân thành sâu sắc đến Quý thầy cô giáo
Khoa Công nghệ thông tin - Trường Đại học Khoa học Huế đã tận tình hướng dẫn,
truyền đạt kiến thức, tạo điều kiện thuận lợi trong quá trình học tập và thực hiện
luận văn tốt nghiệp.
Tôi xin chân thành gửi lời biết ơn đến PGS.TS. Hoàng Quang, người thầy đã
hướng dẫn tận tình và có những góp ý sâu sắc, hướng dẫn rất quý báu cho tôi trong
suốt quá trình nghiên cứu, kịp thời cho tôi nhiều lời động viên cũng như những
nhận xét đúng đắn để tôi có thể hoàn thành tốt được đề tài này.
Trong quá trình thực hiện đề tài, xin chân thành gửi lời cảm ơn đến sự giúp đỡ,
những lời động viên từ phía gia đình, đồng nghiệp, bạn bè là động lực to lớn giúp
tôi hoàn thành tốt đề tài nghiên cứu của mình. Tôi cũng gửi lời cảm ơn đến trường
THPT Đông Hà, nơi tôi đang công tác đã tạo điều kiện đi học và thực hiện đề tài
nghiên cứu này.
Bản thân tôi đã cố gắng hết sức trong quá trình thực hiện đề tài này nhưng
chắc chắn sẽ không tránh khỏi những thiếu sót. Kính mong quý thầy cô và các bạn
tận tình góp ý, chỉ bảo.
Xin chân thành cám ơn!

2



MỤC LỤC
Trang
LỜI CAM ĐOAN.....................................................................................................i
LỜI CẢM ƠN..........................................................................................................ii
MỤC LỤC............................................................................................................... iii
DANH MỤC CÁC HÌNH........................................................................................v
MỞ ĐẦU..................................................................................................................1
Chương 1. CƠ SỞ LÝ THUYẾT VỀ PHƯƠNG PHÁP QUY HOẠCH ĐỘNG. 3
1.1. MỘT SỐ KHÁI NIỆM CƠ BẢN VỀ PHƯƠNG PHÁP QUY HOẠCH
ĐỘNG................................................................................................................... 3
1.1.1. Phương pháp quy hoạch động..................................................................3
1.1.2. Các bài toán con gối nhau........................................................................3
1.1.3. Cấu trúc con tối ưu..................................................................................5
1.1.4. Hai cách tiếp cận quy hoạch động thường dùng......................................7
1.1.5. Nguyên lý của phương pháp....................................................................8
1.2. CÁC BƯỚC THỰC HIỆN GIẢI BÀI TOÁN QUY HOẠCH ĐỘNG............9
1.2.1. Các bước thực hiện..................................................................................9
1.2.2. Một số ví dụ...........................................................................................11
1.3. TIỂU KẾT CHƯƠNG 1...............................................................................17
Chương 2. MỐI QUAN HỆ GIỮA PHƯƠNG PHÁP QUY HOẠCH ĐỘNG VÀ
MỘT SỐ PHƯƠNG PHÁP KHÁC......................................................................18
2.1. SỰ KHÁC NHAU GIỮA PHƯƠNG PHÁP QUY HOẠCH ĐỘNG VÀ
PHƯƠNG PHÁP ĐỆ QUY.................................................................................18
2.2. MỐI QUAN HỆ GIỮA PHƯƠNG PHÁP QUY HOẠCH ĐỘNG VÀ
PHƯƠNG PHÁP VÉT CẠN...............................................................................20
2.2.1. Giới thiệu về thuật toán vét cạn.............................................................20
2.2.2. Phương pháp quy hoạch động và phương pháp vét cạn.........................21

3



2.3. MỐI QUAN HỆ GIỮA PHƯƠNG PHÁP QUY HOẠCH ĐỘNG VÀ
PHƯƠNG PHÁP THAM LAM...........................................................................25
2.3.1. Giới thiệu về thuật toán tham lam..........................................................25
2.3.2. Phương pháp quy hoạch động và phương pháp tham lam......................26
2.4. TIỂU KẾT CHƯƠNG 2...............................................................................30
Chương 3. PHÂN LOẠI MỘT SỐ BÀI TOÁN GIẢI BẰNG PHƯƠNG PHÁP
QUY HOẠCH ĐỘNG...........................................................................................31
3.1. BÀI TOÁN XẾP BA LÔ (KNAPSACK)......................................................32
3.2. BÀI TOÁN DÃY CON ĐƠN ĐIỆU DÀI NHẤT.........................................47
3.3. BÀI TOÁN BIẾN ĐỔI XÂU........................................................................53
3.4. BÀI TOÁN NHÂN MA TRẬN....................................................................61
3.5. BÀI TOÁN GHÉP CẶP...............................................................................66
3.6. BÀI TOÁN DI CHUYỂN.............................................................................69
3.7. TIỂU KẾT CHƯƠNG 3...............................................................................72
KẾT LUẬN............................................................................................................73
TÀI LIỆU THAM KHẢO.....................................................................................75
PHỤ LỤC................................................................................................................. 1

4


DANH MỤC CÁC HÌNH
Trang
Hình 1.1 Đồ thị bài toán con cho dãy Fibonacci......................................................4
Hình 1.2 Tìm đường đi ngắn nhất sử dụng cấu trúc con tối ưu; một đường lượn
sóng đại diện cho một đường đi ngắn nhất giữa hai đỉnh mà nó nối........................5
Hình 1.3 Đồ thị có hướng của bài toán tìm đường đi dài nhất..................................6
Hình 2.1 Tam giác Pascal.......................................................................................19
Hình 2.2 Cây biểu diễn không gian ứng viên của bài toán cái giá với n=3............24

Hình 3.1 Bài toán cái ba lô.....................................................................................32
Hình 3.2 Trò chơi với băng số................................................................................52

5


MỞ ĐẦU
Tại các trường Trung học phổ thông hiện nay, nhiệm vụ quan trọng là đào
tạo một cách toàn diện đồng thời chú trọng bồi dưỡng năng lực của học sinh.
Chính vì vậy một trong những tiêu chí đánh giá chất lượng giáo dục của
trường THPT là kết quả của việc thực hiện hoạt động bồi dưỡng học sinh
giỏi.
Đối với mỗi giáo viên THPT, bồi dưỡng học sinh giỏi là một nhiệm vụ
quan trọng và khó khăn. Nó đòi hỏi giáo viên phải tìm hiểu, học tập rất nhiều
kiến thức về chuyên ngành và các phương pháp giảng dạy thích hợp.
Đối với giáo viên bộ môn Tin học, bồi dưỡng học sinh giỏi đòi hỏi giáo
viên phải hiểu biết về lập trình và cần có các phương pháp giảng dạy thuật
toán tốt giúp học sinh dễ dàng tiếp thu và vận dụng. Học sinh muốn đạt kết
quả cao trong kỳ thi học sinh giỏi tỉnh và cao hơn cần phải có lượng kiến
thức lớn và sâu trong việc lập trình. Những kiến thức này đối với chương
trình phổ thông bình thường là không đủ đáp ứng. Nhiệm vụ của giáo viên là
cung cấp thêm cho các em kiến thức, phương pháp lập trình để học sinh đạt
kết quả tốt trong kỳ thi.
Có nhiều phương pháp được dùng để thiết kế thuật toán như: chia để trị
(divide and conquer), vét cạn (exhaustivesearch), tham lam (greedy
algorithms), quy hoạch động (dynamic programming)... Trong đó, mỗi thuật
toán chỉ áp dụng cho những lớp bài toán phù hợp.
Trong ngành khoa học máy tính, quy

hoạch


động (Dynamic

Programming) là một phương pháp giảm thời gian chạy của các thuật toán
thể hiện các tính chất của các bài toán con gối nhau (overlapping
subproblem) và cấu trúc con tối ưu (optimal substructure) [1]. Phương pháp
quy hoạch động là một phương pháp hiệu quả trong việc giải bài toán tối ưu
hoá rời rạc. Có một số bài toán sử dụng phương pháp quy hoạch động lại cho
hiệu quả cao hơn so với các phương pháp khác.
Trong các kỳ thi học sinh giỏi tỉnh và cao hơn hiện nay, từ 30% đến 40%
các bài thi cần đến quy hoạch động và đây là những bài toán khó, đòi hỏi học
sinh phải có tư duy lập trình cao. Có thể có những cách khác để giải bài toán
1


đó. Nhưng vì các cuộc thi đều có giới hạn về thời gian, cũng như bộ nhớ của
chương trình, nên một thuật toán hiệu quả là cực kỳ cần thiết. Và trong những
trường hợp như vậy, quy hoạch động là một trong những thuật toán phù hợp.
Chỉ cần làm được những bài này là học sinh gần như có giải. Tuy nhiên việc
làm cho các em học sinh phổ thông có thể sử dụng thành thạo phương pháp
này trong lập trình không phải là vấn đề dễ dàng. Hiểu rõ các thuật toán là
bước đầu giúp các em học sinh tự tin đồng thời phân tích bài toán và xác
định phương pháp giải đúng đắn sẽ giúp các em có thành tích tốt hơn. Biết
được mối quan hệ giữa quy hoạch động và các phương pháp khác sẽ giúp
việc giải các bài toán dễ dàng và nhanh chóng hơn.
Là một giáo viên giảng dạy bộ môn Tin học ở trường trung học phổ thông,
việc bồi dưỡng học sinh giỏi là nhiệm vụ vô cùng quan trọng, tôi nhận thấy
việc ứng dụng phương pháp quy hoạch động trong thiết kế thuật toán là một
mảng kiến thức rất cần thiết đối với học sinh tham gia bồi dưỡng học sinh
giỏi.

Vì vậy, tôi chọn đề tài “Ứng dụng phương pháp Quy hoạch động trong
bồi dưỡng học sinh giỏi Tin – Trung học phổ thông” để làm đề tài nghiên
cứu. Hy vọng đây sẽ là một tư liệu hữu ích cho các giáo viên, học sinh và
những người quan tâm đến phương pháp này.
Luận văn này bao gồm ba chương. Chương 1 giới thiệu cho chúng ta có
cái nhìn khái quát về thuật toán quy hoạch động. Chương 2 giới thiệu về mối
quan hệ giữa phương pháp quy hoạch động và một số phương pháp khác: đệ
quy, vét cạn, tham lam. Chương 3 phân loại một số bài toán giải bằng phương
pháp quy hoạch động. Phần phụ lục là dùng Pascal để cài đặt, giải bài toán
được giới thiệu ở chương 3 bằng phương pháp quy hoạch động. Bởi vì hiện
nay thi học sinh giỏi THPT chủ yếu là sử dụng ngôn ngữ lập trình Pascal nên
toàn bộ các thuật toán sẽ được cài đặt bằng Pascal. Việc này sẽ thuận tiện
hơn cho các em học sinh và giáo viên trong việc học tập và giảng dạy.
Mặc dù bản thân tôi luôn cố gắng hết sức trong quá trình thực hiện đề tài
này nhưng chắc chắn sẽ không tránh khỏi những thiếu sót, kính mong quý
thầy cô và các bạn tận tình góp ý, chỉ bảo.

2


Chương 1. CƠ SỞ LÝ THUYẾT VỀ PHƯƠNG PHÁP QUY HOẠCH
ĐỘNG
Quy hoạch động mặc dù không nằm trong nội dung chương trình tin
học phổ thông nhưng là một trong những kỹ thuật lập trình thường được sử
dụng trong các kỳ thi học sinh giỏi, Olympic, tin học trẻ…
Chương này sẽ giới thiệu về thuật toán quy hoạch động, bao gồm các
phần: những khái niệm cơ bản về quy hoạch động, các bước để giải bài toán
bằng quy hoạch động và một số ví dụ minh hoạ.

1.1. MỘT SỐ KHÁI NIỆM CƠ BẢN VỀ PHƯƠNG PHÁP QUY

HOẠCH ĐỘNG
1.1.1. Phương pháp quy hoạch động
Trong ngành khoa học máy tính, quy hoạch động là một phương pháp
giảm thời gian chạy của các thuật toán thể hiện các tính chất của các bài toán
con gối nhau (overlapping subproblem) và cấu trúc con tối ưu (optimal
substructure) [7].
Nhà toán học Richard Bellman (1920-1984) đã phát minh phương
pháp quy hoạch động vào năm 1953. Phương pháp này đã được xây dựng như
là một chủ đề về kỹ thuật và phân tích bài toán đã được tổ chức IEEE thừa
nhận [2].
Phương pháp quy hoạch động bắt đầu từ việc giải tất cả các bài toán
nhỏ nhất (bài toán cơ sở) để từ đó từng bước giải quyết những bài toán lớn
hơn cho tới khi giải được bài toán lớn nhất (bài toán ban đầu). Ý tưởng cơ bản
của phương pháp quy hoạch động là tránh tính toán lại các bài toán con đã
xét, nói cách khác phương pháp quy hoạch động đã thể hiện sức mạnh của
nguyên lý chia để trị đến cao độ.
1.1.2. Các bài toán con gối nhau
Tương tự như thuật toán chia để trị, quy hoạch động cũng chia bài toán
lớn thành các bài toán con nhỏ hơn. Quy hoạch động được sử dụng khi các

3


bài toán con này được gọi đi gọi lại. Phương pháp quy hoạch động sẽ lưu kết
quả của bài toán con này, và khi được gọi, nó sẽ không cần phải tính lại, do
đó làm giảm thời gian tính toán. Quy hoạch động sẽ không thể áp dụng được
(hoặc nói đúng hơn là áp dụng cũng không có tác dụng gì) khi các bài toán
con không gối nhau. Ví dụ với thuật toán tìm kiếm nhị phân, quy hoạch động
cũng không thể tối ưu được gì cả, bởi vì mỗi khi chia nhỏ bài toán lớn thành
các bài toán con, mỗi bài toán cũng chỉ cần giải một lần mà không bao giờ

được gọi lại.
Một ví dụ rất điển hình của bài toán con gối nhau là bài toán tính số
Fibonacci.
Việc thực hiện một thuật toán đệ quy có thể không tối ưu về mặt thời
gian/không gian nhớ.

Hình 1.1 Đồ thị bài toán con cho dãy Fibonacci
Đây không phải là một cấu trúc cây mà là một đồ thị có hướng phi chu trình mô tả quan hệ
giữa các bài toán con gối nhau.

Ví dụ: Tính phần tử thứ n của dãy số Fibonaci
Function F(n: integer): integer;
Begin
If n 2 then F:=1
else F:= F(n-1)+F(n-2);
End;

Độ phức tạp: O(an) với a  1.61803...
Do F(n-1) và F(n-2) được tính một cách độc lập. Số lần gọi cần để tính
F(n) là số lần gọi để tính F(n-1) cộng với số lần gọi để tính F(n-2). Nếu tính
toán như trên, chúng ta có rất nhiều bài toán con sẽ được tính đi tính lại, điển
hình là các số F(1) và F(2).
Đặc điểm của lời giải đệ quy: thực hiện bài toán từ việc phân tích ở
mức cao xuống mức thấp.Và quy hoạch động chính là một trong số những

4


phương pháp có thể giúp chúng ta tối ưu hóa quá trình tính toán này. Mỗi bài
toán con (số F) sẽ được lưu lại trước khi tính những bài toán con lớn hơn.

Nhờ đó, mà việc tính toán giảm đi đáng kể, mỗi bài toán con chỉ cần
tính đúng một lần.
Một ví dụ quy hoạch động với bài toán này bằng cách giải quyết từ
mức thấp lên mức cao ta có thuật toán sau:
Function F(n: integer): integer;
var i: integer; a: array[1..100] of integer;
Begin
a[1]:=1; a[2]:=1;
For i:=3 to n do
a[i]:=a[i-1]+a[i-2];
F:= a[n];
End;

Độ phức tạp: O(n)
Đặc điểm của lời giải bài toán theo phương pháp quy hoạch động: giải
quyết bài toán đệ quy từ mức thấp trước, lời giải của chúng được lưu lại và
được sử dụng để tìm lời giải của các bài toán ở mức cao hơn [7].
1.1.3. Cấu trúc con tối ưu

Hình 1.2 Tìm đường đi ngắn nhất sử dụng cấu trúc con tối ưu; một đường lượn sóng đại
diện cho một đường đi ngắn nhất giữa hai đỉnh mà nó nối

Cấu trúc con tối ưu có nghĩa là các lời giải tối ưu cho các bài toán con
có thể được sử dụng để tìm các lời giải tối ưu cho bài toán toàn cục [7].
Ví dụ, đường đi ngắn nhất tới một đỉnh trong một đồ thị có thể được
tìm thấy bằng cách: trước hết tính đường đi ngắn nhất tới đích từ tất cả các
đỉnh kề nó, rồi dùng kết quả này để chọn đường đi toàn cục tốt nhất, như
trong hình 1.2.
Nói chung, ta có thể giải một bài toán với cấu trúc con tối ưu bằng một
quy trình ba bước:

5


1.
2.

Chia bài toán thành các bài toán con nhỏ hơn.
Giải các bài toán này một cách tối ưu bằng cách sử dụng đệ

quy quy trình ba bước này.
3.
Sử dụng các kết quả tối ưu đó để xây dựng một lời giải tối ưu
cho bài toán ban đầu.
Các bài toán con được giải bằng cách chia chúng thành các bài toán nhỏ
hơn, và cứ tiếp tục như thế, cho đến khi ta đến được trường hợp đơn giản dễ
tìm lời giải.
Tính chất cấu trúc con tối ưu rất quan trọng. Nó cho phép chúng ta giải
bài toán lớn dựa vào các bài toán con đã giải được. Nếu không có tính chất
này, chúng ta không thể áp dụng quy hoạch động được. Không phải bài toán
nào cũng có tính chất cấu trúc con tối ưu này. Ví dụ với đồ thị sau:

Hình 1.3 Đồ thị có hướng của bài toán tìm đường đi dài nhất

Đường đi dài nhất từ q -> t sẽ là q -> r -> t hoặc q -> s -> t. Nhưng
không giống như bài toán tìm đường đi ngắn nhất, đường đi dài nhất không
phải là tổ hợp của những đường đi thành phần, do đó, bài toán này không có
cấu trúc con tối ưu.
Ví dụ, đường q -> r -> t không phải là tổ hợp của đường đi dài nhất từ q
-> r và đường đi dài nhất từ r -> t. Bởi vì, đường đi dài nhất q -> r phải là q
-> s -> t -> r và đường đi dài nhất từ r -> t phải là r -> q -> s -> t.

1.1.4. Hai cách tiếp cận quy hoạch động thường dùng
a. Bottom-up (Từ dưới lên): cách này còn được gọi là quy hoạch động kiểu
“ngược”.
Ngược ở đây không phải là chúng ta duyệt các bài toán con từ lớn
ngược về nhỏ. Mà quy trình sẽ như thế này: Duyệt qua tất cả các bài toán con
(từ nhỏ đến lớn), với mỗi bài toán đó, chúng ta tính toán kết quả dựa vào bài
6


toán con trước đó. Tất nhiên, bài toán con phía trước đã được giải theo quy
trình duyệt, và với mỗi bài toán, chúng ta phải “nhìn ngược lại” bài toán trước
đó, nên cách làm này gọi là quy hoạch động kiểu “ngược”.
Phương pháp quy hoạch động ngược này được sử dụng rộng rãi, vì nó
khá tương ứng với suy nghĩ tự nhiên của chúng ta. Chúng ta đọc đề bài, suy
nghĩ cách giải cho nó. Cách giải đó yêu cầu phải giải những bài toán nhỏ hơn,
như kiểu làm toán phải chứng minh các bổ đề vậy. Chúng ta tiếp tục suy nghĩ
cho những bài toán con này, rồi tổng hợp để tìm ra lời giải cho bài toán lớn.
Quá trình cứ tiếp tục như vậy, và quy hoạch động kiểu “ngược” này đang
được xây dựng đúng như vậy.
Ngoài ra, về mặt lập trình, kiểu quy hoạch động này có mối quan hệ
tương đối gần gũi với đệ quy. Một bài toán lớn được giải dựa vào các bài toán
con tương tự nhau (và tương tự bài toán lớn) thì việc áp dụng đệ quy có thể là
một phương pháp dễ dàng để code. Vì vậy, nhiều trường hợp, có thể coi quy
hoạch động là một cách để tối ưu phương pháp đệ quy để giải một bài toán
[7].
b. Top-down (Từ trên xuống): kiểu quy hoạch động “xuôi”
Tuy không phổ biến, kiểu quy hoạch động xuôi cũng khá khó áp dụng,
nhưng quy hoạch động “xuôi” mang đến cho chúng ta nhiều tiện lợi. Kiểu
xuôi này cũng cần duyệt qua các bài toán con từ nhỏ đến lớn, nhưng với mỗi
bài toán con, chúng ta tính toán kết quả và từ đó tìm cách thực hiện một số

phép tính để giải bài toán lớn hơn. Nghĩa là, với mỗi bài toán con, chúng ta sẽ
nhìn về phía trước để xem phải giải bài toán tiếp theo như thế này từ bài toán
hiện tại.
Phương pháp này khó áp dụng hơn phương pháp ngược kia, và cũng
không phải bài toán nào cũng áp dụng được. Với mỗi bài toán, việc xác định
bước tiếp theo tương đối khó khăn, thậm chí việc kiểm tra tính đúng sai của
phương pháp cũng không hề dễ dàng.
Thông thường, mỗi bài toán cần phải giải bằng cách tổng hợp kết quả từ
một vài bài toán con trước đó. Vì vậy, cách quy hoạch động xuôi này chỉ sử
dụng một bài toán con để tính toán trước bài toán tiếp theo sẽ chỉ cho ra một
phần của kết quả chứ không phải kết quả cuối cùng. Vì vậy, để thực hiện quy
7


hoạch động xuôi, việc điền sẵn một mảng các giá trị trung tính là điều bắt
buộc (sau đó chúng ta sẽ cộng dồn kết quả vào mỗi khi giải được một bài toán
con mới) [7].
1.1.5. Nguyên lý của phương pháp
Quy hoạch động thường dùng để giải bài toán tối ưu- bài toán yêu cầu tìm một
giải pháp tốt nhất trong các giải pháp có thể tìm được. Cơ sở của quy hoạch động
trong bài toán tối ưu là nguyên lý tối ưu Bellman.
Nguyên lý tối ưu Bellman được phát biểu như sau: “Dãy tối ưu các quyết
định trong một quá trình quyết định nhiều giai đoạn có thuộc tính là dù trạng thái
và các quyết định ban đầu bất kể như thế nào, những quyết định còn lại phải tạo
thành một cách giải quyết tối ưu không phụ thuộc vào trạng thái được sinh ra từ
những quyết định ban đầu”.
Hay nói cách khác: “Trong một dãy tối ưu của các lựa chọn thì một dãy con
của nó cũng là tối ưu.”.
Do vậy khi giải bài toán theo quy hoạch động nếu dùng một phương pháp
gồm nhiều bước tiến hành thì điều kiện cần để giải pháp này tối ưu là nó được xây

dựng từ nghiệm tối ưu của những bước trước [7].

1.2. CÁC BƯỚC THỰC HIỆN GIẢI BÀI TOÁN QUY HOẠCH
ĐỘNG
1.2.1. Các bước thực hiện
Trong phương pháp đệ quy, để thiết lập một lời giải đệ quy người lập
trình cần xây dựng ba bước để thiết kế một lời giải là:
Bước 1: Tham số hoá bài toán
Bước 2: Phân tích trường hợp đệ quy
Bước 3: Xác định trường hợp suy biến
Một cách tương tự, với đa số các bài toán về quy hoạch động, chúng ta
có thể thực hiện theo quy trình sau
Bước 1. Phân tích bài toán
Ta tìm cách đưa bài toán về dạng giải được bằng quy hoạch động, nghĩa
là biểu diễn bài toán dưới dạng một bài toán nhiều mức, nhiều gia đoạn có
quan hệ khắng khít với nhau.
8


Từ bài toán đã cho đưa nó về các bài toán con đồng dạng.
Tương tự với tham số hoá bài toán trong đệ quy. Cần lựa chọn và xác
định những tham số cần thiết, các đại lượng là rời rạc.
Xét ví dụ về dãy Fibonaci đã trình bày ở 1.2.2: sử dụng đệ quy ta xây dựng
hàm F có một tham số thuộc kiểu số nguyên. Phân tích bài toán theo phương pháp
quy hoạch động ta thấy để tính được F(i) cần dựa tính F(i-1) và F(i-2) là các bài
toán con đồng dạng. Vì vậy ta cũng xây dựng hàm F có 1 tham số nguyên là n.
Function F(n: integer): integer;

Bước 2. Xây dựng giải pháp đệ quy
Đây là bước thiết lập mối quan hệ giữa các giai đoạn với nhau.

Dựa vào các thông tin bài toán đã cho lập công thức truy hồi. Phân tích
trường hợp đệ quy bằng cách biểu diễn bài toán dưới dạng bài toán cùng loại
nhưng khác phạm vi giải quyết và xác định trường hợp suy biến.
Đối với bài toán dãy Fibonacci từ thông tin bài toán là F(1) = F(2) = 1
và F(n)= F(n-1) + F(n-2) ta có được giải pháp đệ quy sau:
Bước 3. Lập bảng
Dựa vào công thức truy hồi để tìm lời giải của các bài toán tương ứng
với các giai đoạn đã chia. Các giá trị này sẽ được lưu trữ vào bảng. Bảng có
thể thiết lập như mảng 1 chiều, 2 chiều, … Thông thường nếu tham số hình
thức là 1 tham số thì dùng mảng 1 chiều, nếu 2 tham số thì dùng mảng 2
chiều…Thứ tự điền vào bảng cũng chính là thứ tự giải các bài toán từ thấp
đến cao nhưng phải đảm bảo sao cho các kết quả điền vào bảng cuối cùng sẽ
cho ta giá trị tối ưu của bài toán ban đầu và phương án tối ưu của giai đoạn
cuối.
Với bài toán Fibonacci, ta thấy có 1 tham số hình thức nên sử dụng
mảng 1 chiều a (array [1..max] of integer) để tính:
Cụ thể:
Các giá trị sẽ được điền vào theo thứ tự từ a[1] đến a[n]
Bước 4. Tổng hợp kết quả
Đây là bước kiến tạo một lời giải cho bài toán từ các thông tin đã tính
toán. Từ trạng thái cuối cùng ta biết được phương án tối ưu của giai đoạn

9


cuối và do đó biết được trạng thái tối ưu của giai đoạn ngay trước nó. Cứ tiếp
tục như vậy ta biết được toàn bộ phương án tối ưu của từng giai đoạn từ đầu
đến cuối.
Với bài toán Fibonacci. Phương án cuối cùng là a[n] nên bước tổng hợp
kết quả là: ;

Độ phức tạp tính toán: O(n)
Từ bốn bước phân tích trên ta có chương trình tính phần tử thứ n của dãy
Fibonacci theo phương pháp quy hoạch động như sau:
Function F(n: integer): integer;
var i: integer; a: array[1..100] of integer;
Begin
a[1]:=1; a[2]:=1;
For i:=3 to n do
a[i]:=a[i-1]+a[i-2];
F:= a[n];
End;

1.2.2. Một số ví dụ
1.2.2.1. Bài toán tính tổ hợp
Tính : tổ hợp chập k của n
Thực hiện các bước theo phương pháp quy hoạch động.
 Phân tích bài toán: Xây dựng hàm:
Function C(k, n: byte): longint; {giả thiết k<=n}

 Giải pháp đệ quy:
 Lập bảng: Sử dụng một mảng 2 chiều a (array[0..max,0..max] of longint) để
tính:
Cụ thể:
ngược lại:
 Tổng hợp kết quả:
Ví dụ: Tính = C(3, 5); Ta có bảng để tính các phần tử
i
j

0


0

1

1

1

1

1

10

2

3


2

1

2

1

3


1

3

3

1

4

1

4

6

4

5

1

5

10

10

Việc điền các giá trị vào bảng được tiến hành theo thứ tự giải các bài
toán từ thấp đến cao.

Kết quả điền vào bảng cuối cùng là a[3,5] cho ta giá trị tối ưu của bài
toán ban đầu và phương án tối ưu của giai đoạn cuối là 10
 Chương trình
Function C(k, n: byte): longint;
Var a: array [0..100; 0..100] of longint;
i, j: byte;
Begin
For i:=0 to k do
For j:=i to n do
If (i=0) or (i=j) then a[i,j]:=1
Else a[i,j]:= a[i-1,j-1] + a[i, j-1];
C:= a[k,n];
End;

Ở chương trình này phép toán tích cực là (i =0) or (i=j) nằm trong hai
vòng for lồng nhau nên độ phức tạp là
Lưu ý: Nếu ở hàm trên chúng ta thêm câu lệnh if n-kk:=n-k; {nghĩa là } thì độ phức tạp của thuậ toán sẽ là .

Nhận xét: Đối với những bài toán khi thực hiện tính một hàm bằng
phương pháp đệ quy mà có thể chỉ ra hướng tính những giá trị sau dựa vào
giá trị đã tính trước đó thì ta có thể sử dụng phương pháp quy hoạch động.
Có thể sử dụng mảng để tính toán và lưu các giá trị, số chiều của mảng phụ
thuộc vào số lượng tham số.
Chẳng hạn ví dụ sau có thể sử dụng quy hoạch động tương tự bài toán
tính tổ hợp.
Tính giá trị xác suất P(i, j) (i, j: byte). Biết rằng:

11



Phương pháp thực hiện, sử dụng phương pháp quy hoạch động. Thực
hiện theo 4 bước như bài toán tổ hợp từ đó ta có chương trình (i = 4, j = 5)
như sau:
 Chương trình
Program LapbangP45;
const i=4;j=5;
Function P(i,j:byte):real;
var
k,l:byte;
a:array[0..10,0..10] of real;
begin
For k:=0 to i do
For l:=0 to j do
If (k=0) and (l>0) then
a[k,l]:=1
else
if (k>0) and (l>0) then
a[k,l]:=(a[k-1,l]+a[k,l-1])/2
else a[k,l]:=0;
P:=a[i,j];
end;
BEGIN
writeln('Gia tri P[',i,',',j,' la:',P(i,j));
readln;
END.

1.2.2.2. Bài toán du lịch
Một người đi từ thành phố 0 đến thành phố n và có thể đi qua n-1 thành
phố khác 1, 2,..., n-1, theo lộ trình: 0  i1  i2 …..  ik  n, trong đó: 0 < i1

< i2 < …< ik < n. Giá vé của xe đi từ thành phố i đến thành phố j là c[i,j]. Tìm
một lộ trình từ thành phố 0 đến thành phố n sao cho tổng chi phí về giá vé
đạt cực tiểu.
Phương pháp thực hiện, sử dụng phương pháp quy hoạch động

 Phân tích bài toán
o Gọi là bài toán du lịch để đi từ thành phố 0 đến thành phố r.
(bài toán ban đầu là )
o Các giá trị cần tìm:
 d[r]: chi phí cực tiểu của bài toán
 l[r]: thành phố cuối cùng cần đến trước khi đến thành phố r của bài toán
12


 Giải pháp đệ quy
o Nếu r > 1:


o Nếu r = 1:
 d[r] = c[0,1]
 l[r] = 0
 Lập bảng
Procedure LapBang;
Begin
For r:=1 to n do
Tính d[r] và l[r];
End;

Độ phức tạp tính toán: O(n2)
 Tổng hợp kết quả

Procedure TongHop;
Begin
i := 0;r := n;
While r > 0 do
Begin i := i+1;x[i]:= l[r];r := x[i];end;
writeln(‘Thứ tự các thành phố trung gian phải đi qua:’);
For k:=i downto 1 do writeln(x[i]);
End;

Độ phức tạp tính toán: O(n)
 Chương trình
program btdl;
const

fi='dulich.inp';
fo='dulich.out';

var

r,v,n:integer;
d:array[0..100] of real;
x,l:array[0..100] of byte;
c:array[0..100,0..100] of real;

procedure nhapdl;
var i,j:byte;
begin
assign(input,fi); reset(input);

13



assign(output,fo); rewrite(output);
readln(n);
for i:=0 to n do
begin
for j:=0 to n do read (c[i,j]);
readln;
end;
end;
procedure lapbang;
var

tam:real;

begin
for r:=0 to n do
if r=0 then d[r]:=0
else
begin
d[r]:=d[0] + c[0,r];l[r]:=0;
for v:=1 to r-1 do
begin
tam:=d[v] + c[v,r];
if tam < d[r] then
begin d[r]:= tam;l[r]:=v; end;
end;
end;
end;
procedure tonghop;

var i,k:byte;
begin
i:=0;r:=n;
while r>0 do
begin
i:=i+1;
x[i]:=l[r];
r:=x[i];

14


end;
writeln('Thu tu cua cac thanh pho trung gian
phai di qua la:');
for k:=i-1 downto 1 do write(x[k],'');
writeln('Tong chi phi thap nhat la', d[n]:1);
end;
procedure dongtep;
begin
close(input); close(output);
end;
BEGIN
nhapdl;
lapbang;
tonghop;
dongtep;
END.

1.3. TIỂU KẾT CHƯƠNG 1

Chương này đã đề cập đến các nội dung cơ bản về hiện trạng bồi dưỡng
HSG tin học hiện nay và thuật toán quy hoạch động như: khái niệm về quy
hoạch động, bài toán con gối nhau, cấu trúc con tối ưu, hai cách tiếp cận
phương pháp thường dùng, nguyên lý của phương pháp, cách thực hiện và
một số ví dụ minh hoạ.
Ở chương tiếp theo, ta sẽ tìm hiểu về mối quan hệ giữa phương pháp
quy hoạch động và một số phương pháp khác.

15


Chương 2. MỐI QUAN HỆ GIỮA PHƯƠNG PHÁP QUY
HOẠCH ĐỘNG VÀ MỘT SỐ PHƯƠNG PHÁP KHÁC
Hiện nay, trong các kỳ thi học sinh giỏi tin học, các lớp bài toán về tối
ưu hoá luôn được ưu tiên lựa chọn vì tính ứng dụng vào thực tiễn cao.
Có rất nhiều phương pháp để giải quyết lớp các bài toán tối ưu. Tuỳ
từng bài toán cụ thể mà ta lựa chọn một phương pháp để áp dụng nhằm đạt
được hiệu quả (về tốc độ, về bộ nhớ). Trong đó phương pháp quy hoạch động
luôn được ưu tiên lựa chọn vì tính hiệu quả của nó cao hơn các phương pháp
khác trong đại đa số các bài toán về tối ưu hoá. Để hiểu rõ hơn về phương
pháp quy hoạch động, ở chương này sẽ tìm hiểu về mối quan hệ giữa phương
pháp quy hoạch động và một số phương pháp khác là phương pháp đệ quy,
phương pháp vét cạn và phương pháp tham lam.

2.1. SỰ KHÁC NHAU GIỮA PHƯƠNG PHÁP QUY HOẠCH
ĐỘNG VÀ PHƯƠNG PHÁP ĐỆ QUY
Về mặt nguyên tắc phương pháp quy hoạch động rất giống với phương
pháp đệ quy. Cả hai phương pháp đều sử dụng lời giải của các bài toán có
kích thước bé hơn, đồng dạng với bài toán ban đầu để đưa ra lời giải của bài
toán ban đầu. Tuy nhiên, nếu đệ quy gọi thực hiện các bài toán có kích thước

lớn trước rồi đến các bài toán có kích thước bé hay thực hiện các bài toán từ
tầng cao đến tầng thấp, các kết quả trung gian phải tính đi tính lại nhiều lần
thì ngược lại, quy hoạch động lại gọi thực hiện các bài toán có kích thước bé
trước rồi lưu lại kết quả để giải các bài toán có kích thước lớn hơn. Như vậy,
phương pháp quy hoạch động đã tránh được việc tính toán các giá trị trung
gian nhiều lần mà chỉ tính một lần góp phần làm giảm độ phức tạp của thuật
toán.
Do có sự khác biệt trên nên trong khi đệ quy dành phần lớn dung lượng
bộ nhớ để lưu trữ các chương trình con thì quy hoạch động lại dành phần lớn
bộ nhớ để lưu trữ các kết quả trung gian [3]. Chính vì vậy đệ quy chiếm dung
lượng bộ nhớ rất lớn, thông thường độ phức tạp là hàm mũ.

16


Ví dụ: Tam giác Pascal.
Tam giác Pascal là một mô hình dùng để đưa ra các hệ số của khai
triển nhị thức Newton bậc N (x+1)N.
Ví dụ: trong khai triển (x+1)2 = x2 + 2x +1
có các hệ số là 1 2 1
Trong khai triển (x+1)3 = x3 + 3x2 + 3x + 1

17
1

có các hệ số là 1 3 3 1
Yêu cầu: Hãy tìm các hệ số trong khai triển

1
1

1

N

nhị thức Newton (x + 1) .
Chương trình đệ quy như sau:
Program TamgiacP;
Var i, N: integer;
Function
PC(h,

1

2
3

4
5

1

10

1
3

6

1
4


10

1
5

1

Hình 2.4 Tam giác Pascal
c:

integer): integer;
Begin
If (h=0) and (c=0) then PC:=1
Else
If (h=c) or (c<=0) then PC:=1
Else
PC:=PC(h-1,c) + PC (h-1, c-1)
End;
Begin
Clrscr;
Write (‘Nhap N:’); Readln(N);
Write (‘Cac he so trong khai trien: ’);
For i:=0 to N do Write (PC(N,i):5);
Readln;
End.

Việc tính toán sẽ rất lãng phí thời gian của máy. Do PC(h-1, c) và
PC(h-1, c-1) được tính một cách độc lập! Số lần gọi cần để tính PC(h, c) là
số lần gọi để tính PC(h-1, c) cộng với số lần gọi để tính PC(h-1, c-1).

Nếu tính toán như trên, chúng ta có rất nhiều bài toán con sẽ được tính
đi tính lại. Để khắc phục điều này ta giải bằng phương pháp quy hoạch động:
Program TamgiacP;
Var i, N: integer;
L: array[0..100;0..100];
Begin
Clrscr;
Write (‘Nhap N:’); Readln(N);
L[0,0] = 1; L[1,1] = 1; L[1,2] = 1;
For i:= 2 to N do
Begin

17


L[i, 1] :=1;
For j:=2 to i+1 do
L[i, j] = L[i-1, j-1] + L[i-1, j];
End;
Write (‘Cac he so trong khai trien: ’);
For i:=0 to N do
Write (L[N,i]:5);
Readln;
End.

2.2. MỐI QUAN HỆ GIỮA PHƯƠNG PHÁP QUY HOẠCH
ĐỘNG VÀ PHƯƠNG PHÁP VÉT CẠN
2.2.1. Giới thiệu về thuật toán vét cạn
Trong khoa học máy tính, thuật toán vét cạn (exhaustive hay bruteforce), còn được biết đến như là tạo ra và kiểm tra, là một kỹ thuật giải quyết
vấn đề tổng quát bao gồm việc liệt kê một cách có hệ thống tất cả các ứng cử

viên có thể cho giải pháp và kiểm tra xem mỗi ứng viên có đáp ứng được yêu
cầu của vấn đề hay không [7].
Một thuật toán vét cạn để tìm các ước số của một số tự nhiên n sẽ đếm
tất cả các số nguyên từ 1 đến n, và kiểm tra xem mỗi số trong số chúng có
chia hết n không. Cách tiếp cận vét cạn của bài toán tám quân hậu có thể
xem xét tất cả các sự sắp xếp có thể có của 8 quân cờ trên bàn cờ 64 ô, và,
cho mỗi lần sắp xếp, kiểm tra xem mỗi quân hậu có thể tấn công bất cứ ai
khác.
Mặc dù việc dùng thuật toán vét cạn rất đơn giản, và sẽ luôn tìm ra giải
pháp nếu nó tồn tại, chi phí của nó tỷ lệ thuận với số lượng các giải pháp ứng
viên - trong nhiều vấn đề thực tế có xu hướng phát triển rất nhanh chóng khi
kích thước của vấn đề gia tăng. Do đó, thuật toán vét cạn thường được sử
dụng khi kích thước vấn đề bị hạn chế, hoặc khi có các heuristics cụ thể theo
từng vấn đề có thể được sử dụng để giảm các giải pháp ứng cử cho một kích
thước có thể quản lý được. Phương pháp này cũng được sử dụng khi tính đơn
giản của việc thực hiện quan trọng hơn tốc độ.

18


2.2.2. Phương pháp quy hoạch động và phương pháp vét cạn
Vét cạn là một trong những thuật toán giải bài toán tối ưu. Thuật toán
vét cạn là thuật toán tìm phương án tối ưu của bài toán bằng cách lựa chọn
một phương án trong tập hợp tất cả các phương án của bài toán để tìm ra
phương án tối ưu. Trong nhiều bài toán, không gian các phương án quá lớn.
Vét cạn giúp tìm ra kết quả tối ưu nhưng độ phức tạp lớn, thường là hàm mũ
trong khi phương pháp quy hoạch động độ phức tạp là đa thức. Do vậy, khi
áp dụng thuật toán vét cạn không đảm bảo về thời gian cũng như kĩ thuật.
Vét cạn là xét toàn bộ trường hợp, rồi tìm ra kết quả. Quy hoạch động
là tìm một kĩ thuật tìm kết quả trước thông qua một kết quả có sẵn hoặc được

tìm thấy.
Ưu điểm của vét cạn là chắc chắn tìm ra lời giải (nếu có), nhưng nhược
điểm của nó là có thể chạy quá lâu, vượt mức thời gian cho phép. Còn quy
hoạnh động có ưu điểm là chạy rất nhanh nhưng nhược điểm của nó là rất
khó tìm ra thuật toán, với một số bài toán có thể sẽ không có thuật toán quy
hoạch động.
Vét cạn theo nghĩa thông thường là xét hết mọi đối tượng hay mọi
trường hợp. Trong lập trình, vét cạn là phương pháp được dùng khi không
còn phương pháp nào hiệu quả hơn có thể sử dụng được.
Vấn đề trong phương pháp vét cạn là làm sao để liệt kê được tất cả các
ứng viên. Một khi đã liệt kê được các ứng viên, việc chấm điểm cho từng
ứng viên và chọn ra ứng viên có điểm cao nhất chỉ còn là việc làm đơn giản.
Một phương pháp hay được dùng để liệt kê các ứng viên là tổ chức
không gian ứng viên theo một cấu trúc cây, mỗi ứng viên trên một nút
(thường là lá) của cây. Đặc điểm của cây biểu diễn không gian ứng viên là
các ứng viên trên các nút có quan hệ cha-con hoặc anh-em giống nhau ở một
bộ phận nào đó. Một khi cấu trúc cây biểu diễn không gian ứng viên được
thiết lập, chúng ta có thể áp dụng thủ tục duyệt cây (theo chiều rộng hoặc
theo chiều sâu) để liệt kê các ứng viên. Cây biểu diễn không gian ứng viên
có thể rất lớn và sẽ mất nhiều thời gian để tạo cũng như yêu cầu nhiều không
gian để lưu trữ. Tuy nhiên, cây này chỉ mang tính trừu tượng, làm cơ sở cho

19


việc duyệt (chọn nút tiếp theo được thăm), mà không phải được tạo ra và lưu
trữ tất cả.
Các bước thiết lập cây biểu diễn không gian ứng viên được mô tả chung
như sau:
1. Xác định các tính chất của ứng viên dùng để phân loại ứng viên

2. Với tính chất thứ nhất, phân hoạch các ứng viên theo tính chất này,
nghĩa là chia tập ứng viên thành các tập con theo đó các phần tử thuộc cùng
một tập con giống nhau ở tính chất thứ nhất, hai phần tử thuộc hai tập con
khác nhau sẽ khác nhau ở tính chất thứ nhất. Tạo các cây con từ gốc, mỗi cây
con tương ứng với một tập con vừa nhận được.
3. Với mỗi tập con, sử dụng tính chất thứ hai để phân hoạch tập con
này. Kết quả thu được là các tập con nhỏ hơn. Từ gốc của cây con tương ứng
với tập con đang xét, tạo các nhánh tương ứng với các tập con nhỏ hơn.
4. Quá trình cứ tiếp tục như vậy cho đến khi chúng ta xét hết các tính
chất của ứng viên và thu được một cây biểu diễn không gian ứng viên.
Chúng ta xét một ví dụ sau đây.
Ví dụ. Cái giá (còn có tên là Knapsack 0/1)
Bài toán: Cho n đồ vật có khối lượng lần lượt là w1, w2, …, wn và một
cái giá chịu khối lượng tối đa là W.Hãy để các đồ vật lên giá sao cho tổng
khối lượng các đồ vật được để trên giá là lớn nhất có thể.
Ví dụ với 3 vật có khối lượng lần lượt là 6, 4, 3 và một cái giá có thể
chịu khối lượng tối đa là 8, phương án tốt nhất là để hai vật có khối lượng 4
và 3 lên giá.
Để giải bài toán này bằng phương pháp vét cạn, trước hết chúng ta phải
xác định dạng của ứng viên và hàm đánh giá ứng viên. Mỗi một tập con (bao
gồm các vật được chọn để đặt lên giá) của tập tất cả các vật là một ứng viên.
Ta có thể biểu diễn mỗi ứng viên bằng một xâu nhị phân c = x1 x2…xn với ý
nghĩa vật thứ i được để trên giá nếu xi = 1 và không được để trên giá nếu xi =
0. Hàm đánh giá ứng viên được xác định như sau:

Trong ví dụ trên, tập các ứng viên là C= {000, 001, 010, 011, 100,
20



×