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

Giúp học sinh tiếp cận với phương pháp quy hoạch động bằng một số bài toán đơn giản trong tin học

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 (456.76 KB, 32 trang )

MỤC LỤC

1


CÁC KÝ HIỆU VIẾT TẮT TRONG SÁNG KIẾN KINH NGHIỆM

Ký hiệu

Ý nghĩa

SKKN

Sáng kiến kinh nghiệm

THPT

Trung học phổ thông

ĐQ

Đệ quy

DQ

Đệ quy

QHĐ

Quy hoạch động


QHD

Quy hoạch động

PP

Phương pháp

NXB

Nhà xuất bản

2


BÁO CÁO KẾT QUẢ
NGHIÊN CỨU, ỨNG DỤNG SÁNG KIẾN
I. Lời giới thiệu
Hiện nay, công nghệ thông tin xuất hiện ở mọi nơi, sự phát triển nhanh chóng
của nó giúp cuộc sống của con người trở nên tốt đẹp hơn. Công nghệ thông tin giúp
các nhà khoa học tạo ra những nghiên cứu vượt bậc nhờ việc tính tốn và xử lý một
khối lượng cơng việc khổng lồ của máy tính. Chúng có thể thực hiện hàng tỷ phép
tính trong vài giây. Rất nhiều phần mềm ra đời nhằm giúp con người giải quyết công
việc dễ dàng hơn. Các phần mềm được viết nhờ các Lập trình viên dựa trên các ngơn
ngữ lập trình và Pascal là một ngơn ngữ lập trình giúp những người mới học lập trình
dễ tiếp cận.
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ư: Đệ quy (Recursion), quy hoạch động (Dynamic
programming), chia để trị (Divide and conquer), vét cạn (Exhaustivesearch), tham
lam (Greedy algorithms) ... Trong đó, mỗi thuật tố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 là chìa khóa để thiết kế nhiều
giải thuật quan trọng và cũng là cơ sở của quy hoạch động (Dynamic Programming).
Quy hoạch động là một phương pháp giảm thời gian chạy của các thuật tốn thể hiện
các tính chất của các bài tốn con gối nhau (Overlapping subproblem) và cấu trúc con
tối ưu (Optimal substructure). 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 tố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 tố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 tốn đó. Nhưng vì các bài thi đều có
3


xét đến thời gian thực hiện (chạy) chương trình, cũng như dung lượng bộ nhớ để lưu
trữ và thực hiện chương trình đó. Nên một thuật tốn hiệu quả là cực kỳ cần thiết. Có
nghĩa là cùng một bài tốn, cách nào mà thời gian thực hiện càng nhanh, chiếm ít bộ
nhớ hơn sẽ được đánh giá cao hơn. 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, các em thường hay bị nhầm lẫn và khó phân biệt
được khi thuật toán sử dụng phương pháp quy hoạch động và các phương pháp khác.
Nên việc làm cho các em học sinh phổ thơng có thể phân biệt và thấy được sự ưu việt
của quy hoạch động từ đó 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 tố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. 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, sau nhiều năm tham gia dạy bồi dưỡng đội tuyển thi học sinh giỏi, tôi nhận
thấy việc bồi dưỡng học sinh giỏi là nhiệm vụ vô cùng quan trọng và 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 đội tuyển học sinh giỏi môn Tin học.
Vì vậy tơi chọn đề tài “Giúp học sinh tiếp cận với phương pháp quy hoạch
động bằng một số bài toán đơn giản trong Tin học” 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ần kiến thức này.
II. Tên sáng kiến:
Giúp học sinh tiếp cận với phương pháp quy hoạch động bằng một số bài
toán đơn giản trong Tin học.
III. Tác giả sáng kiến:
- Họ và tên: Nguyễn Thị Hà
- Địa chỉ tác giả sáng kiến: Xã Đại Đồng – huyện Vĩnh Tường – tỉnh Vĩnh Phúc
- Số điện thoại: 0977 212 636
- E_mail:
IV. Chủ đầu tư tạo ra sáng kiến: Nguyễn Thị Hà
V. Lĩnh vực áp dụng sáng kiến: Dạy bồi dưỡng học sinh đội tuyển Tin học, giáo
viên hoặc những người quan tâm tới lập trình.
VI. Ngày sáng kiến được áp dụng lần đầu hoặc áp dụng thử: Năm 2017 – 2018.
VII. Mô tả bản chất của sáng kiến:

4



PHẦN I: SƠ ĐỒ NỘI DUNG SÁNG KIẾN KINH NGHIỆM

5


PHẦN II: NỘI DUNG SÁNG KIẾN KINH NGHIỆM
I. Một số khái niệm cơ bản về phương pháp quy hoạch động
1.1. Khái niệm
* Quy hoạch động (Dynamic Programming) là một phương pháp rất hiệu quả giải
nhiều bài toán tin học, đặc biệt là những bài toán tối ưu.
* Quy hoạch động trong ngành khoa học máy tính: 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 tốn con gối nhau
(Overlapping subproblem) và cấu trúc con tối ưu (Optimal substructure).
Phương pháp quy hoạch động bắt đầu từ việc giải tất cả các bài tốn nhỏ nhất
(bài tố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 tốn lại các bài tố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 độ.
* Một bài toán P muốn giải bằng phương pháp quy hoạch động cần có 2 đặc
điểm sau:
- Bài toán P thỏa mãn nguyên lý tối ưu Bellman, nghĩa là có thể sử dụng lời giải
tối ưu của các bài tốn con từ mức thấp nhất để tìm dần lời giải tối ưu cho bài toán
con ở mức cao hơn và cuối cùng là lời giải tối ưu cho bài tốn P.
- Bài tốn P có các bài tốn con phủ chồng lên nhau, nghĩa là khơng gian bài
tốn con “hẹp” khơng tạo dạng hình cây.
1.2. Các bước giải quyết bài toán bằng phương pháp quy hoạch động.
- Bước 1: Xây dựng hàm mục tiêu
Áp dụng nguyên lý tối ưu của Bellman ta phân rã bài toán ban đầu thành các

bài tốn con có cùng cấu trúc sao cho việc giải quyết bài toán con cấp i phụ thuộc vào
kết quả của các bài tốn con trước đó. Cụ thể hóa bước này là ta phải xây dựng được
hàm mục tiêu F(i) là nghiệm của bài toán con cấp i.
- Bước 2: Xác định các bài toán cơ sở.
Bài toán cơ sở là các bài toán con nhỏ nhất mà ta có thể biết ngay kết quả hoặc
tính được kết quả dễ dàng. Đây chính là cơ sở để tính nghiệm cho các bài tốn cấp lớn
hơn.
- Bước 3: Xây dựng công thức truy hồi

6


Căn cứ vào ý nghĩa của hàm mục tiêu, tìm mối quan hệ giửa các bài toán con
các cấp, ta tiến hành xây dựng cơng thức tính kết quả của bài toán cấp i dựa vào kết
quả của các bài tốn con cấp trước đó.
- Bước 4: Lập bảng phương án
Sử dụng công thức truy hồi và nghiệm các bài tốn cơ sở tính nghiệm tất cả các
bài tốn con và lưu trữ chúng vào bảng phương án.
- Bước 5: Kết luận nghiệm của bài toán.
Dựa vào bảng phương án chỉ ra nghiệm của bài toán. Các bước giải quyết trên
tuy rất cụ thể nhưng vẫn trừu tượng đối với học sinh.
Bài tốn 1: Tính an (n là số ngun dương)
- Bước 1: Hàm mục tiêu: f(i) là lũy thừa của ai
- Bước 2: Các bài toán cơ sở: f(0) = 1;
- Bước 3: Công thức truy hồi: f(i) = a* f(i-1)
- Bước 4: Bảng phương án
i

0


1

2

3

4

5

6

f(i)

1

a

a*a1 a*a2 a*a3 a*a4 a*a5

…..

….

- Bước 5: Nghiệm f(n) của bài toán
Bài toán 2: Tính n!
- Bước 1: Hàm mục tiêu: f(i) là giai thừa của số i
- Bước 2: Các bài toán cơ sở: f(0) = 1; f(1) = 1
- Bước 3: Công thức truy hồi: f(i) = i* f(i-1)
- Bước 4: Bảng phương án

i

0

1

2

3

4

5

6

f(i)

1

1

2*1= 2

3*2=6

4*6 =
24

5*24 = 120


6*120=72
0

- Bước 5: Nghiệm F(n) của bài tốn
Bài tốn 3: Tìm số Fibonaci thứ N?
- Bước 1: Hàm mục tiêu: f(i) là số fibonaci thứ i.
- Bước 2: Các bài toán cơ sở: f(0) = 1; f(1) = 1
- Bước 3: Công thức truy hồi: f(i) = f(i-1) + f(i-2)
7

…..

….


- Bước 4: Bảng phương án
i

1

2

3

4

5

6


7

f(i) 1

1

2

3

5

8

13

….
.

….

- Bước 5: Nghiệm f(n) của bài toán
Bài toán 4: Tháp Hà Nội
Chuyển n chiếc đĩa từ cọc 1 sang cọc 2 theo thứ tự từ lớn đến nhỏ. có sử dụng cọc
3 làm cọc trung gian. Mỗi lần di chuyển được 1 đĩa. Và đĩa đĩa lớn phải ở dưới
đĩa nhỏ.
- Bước 1: Hàm mục tiêu: f(i)
- Bước 2: Các bài toán cơ sở: f(1): = 1
- Bước 3: Công thức truy hồi: f(i):=2*f(i-1)+1

- Bước 4: Bảng phương án
i

1

2

3

4

5

f(i) 1

3

7

15

31

….
.

….

- Bước 5: Nghiệm f(n) của bài toán
Bài toán 5: Bài toán cái túi

Trong siêu thị có n gói hàng (n <= 100), gói hàng thứ i có trọng lượng là W i
<= 100 và trị giá Vi <= 100. Một tên trộm đột nhập vào siêu thị, sức của tên trộm
không thể mang được trọng lượng vượt quá M (M <= 100). Hỏi tên trộm sẽ lấy đi
những gói hàng nào để được tổng giá trị lớn nhất.
- Bước 1: Hàm mục tiêu: F[i,j] = max(F[i-1,j],V[i]+ F[i,j-W[i]])
- Bước 2: Các bài toán cơ sở: F[0,j] = 0
- Bước: Cơng thức truy hồi tính B[i, j].
Với giới hạn trọng lượng j, việc chọn tối ưu trong số các gói {1, 2, ...,i - 1, i} để
có giá trị lớn nhất sẽ có hai khả năng:
+ Nếu khơng chọn gói thứ i thì F[i, j] là giá trị lớn nhất có thể bằng cách chọn
trong số các gói {1, 2, ..., i - 1} với giới hạn trọng lượng là j. Tức là F[i, j] = F[i - 1, j]
+ Nếu có chọn gói thứ i (tất nhiên chỉ xét tới trường hợp này khi mà W i ≤ j) thì
F[i, j] bằng giá trị gói thứ i là Vi cộng với giá trị lớn nhất có thể có được bằng cách
chọn trong số các gói {1, 2, ..., i - 1} với giới hạn trọng lượng j - W i. Tức là về mặt giá
8


trị thu được: F[i, j] = Vi + F[i - 1, j - Wi] Vì theo cách xây dựng F[i, j] là giá trị lớn
nhất có thể nên nó sẽ là max trong 2 giá trị thu được ở trên.
Cơ sở quy hoạch động:
Dễ thấy F[0, j] = giá trị lớn nhất có thể bằng cách chọn trong số 0 gói = 0.
- Bước 4: Bảng phương án
Ta xây dựng bảng phương án dựa trên công thức truy hồi trên. Để kiểm tra kết
quả có chính xác hay khơng (nếu khơng chính xác chúng ta xây dựng lại hàm mục
tiêu). Thông qua cách xây dựng hàm mục tiêu và bảng phương án chúng ta sẽ định
hướng việc truy vết.
* Trường hợp mỗi vật được chọn 1 lần
N/
M


0

1

2

3

4

5

6

7

8

9

10

11

12

13

14


15

0

0

0

0

0

0

0

0

0

0

0

0

0

0


0

0

0

1

0

0

0

0

0

0

0

0

0

0

0


0

4

4

4

4

2

0

0

2

2

2

2

2

2

2


2

2

2

4

4

6

6

3

0

1

2

3

3

3

3


3

3

3

3

3

4

5

6

7

4

0

2

3

4

5


5

5

5

5

5

5

5

5

6

7

8

5

0

2

3


4

10 12 13 14 15 15

15

15

15

15

15

15

Vậy ta có thể chọn vật 2, 3, 4, 5
* Trường hợp mỗi vật được chọn nhiều lần
N/
M

0

1

2

3

4


5

6

7

8

9

10

11

12

13

14

15

0

0

0

0


0

0

0

0

0

0

0

0

0

0

0

0

0

1

0


0

0

0

0

0

0

0

0

0

0

0

4

4

4

4


2

0

0

2

2

4

4

6

6

8

8

10

10

12

12


14

14

3

0

1

2

3

4

5

6

7

8

9

10

11


12

13

14

15

4

0

2

4

6

8

10 12 14 16 18

20

22

24

26


28

30

5

0

2

4

6

10 12 14 16 20 22

24

26

30

32

34

36

Vậy chúng ta có thể chọn vật 4 (3 lần) và vật 5 (3 lần)

- Bước 5: Truy vết:
9


* Trường hợp 1: Trong bảng phương án F[n,m] chính là giá trị lớn nhất thu được khi
chọn trong cả n vật với giới hạn trọng lượng là M.
Nếu f[n,M]=f[n-1,M] thì tức là khơng chọn vật thứ n, ta truy về f[n-1,M]. Cịn nếu
f[n,M]≠f[n-1,M] thì ta thơng báo rằng phép chọn tối ưu có chọn vật thứ n và truy về
f[n-1,M-Wn].
* Trường hợp 2: Trong bảng phương án F[n,m] chính là giá trị lớn nhất thu được khi
chọn trong cả n vật với giới hạn trọng lượng là M.
Nếu f[n,M] = f[n-1,M] thì tức là khơng chọn vật thứ n, ta truy về f[n-1,M]. Cịn nếu
f[n,M] ≠ f[n-1,M] thì ta thơng báo rằng phép chọn tối ưu có chọn vật thứ n và truy về
f[n,M-Wn].
II. So sánh phương pháp quy hoạch động với các phương pháp khác
2.1. 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.
* Giống nhau: Cả hai phương pháp đều sử dụng lời giải của các bài tố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.
* Khác nhau:
Quy hoạch động

Đệ quy

- Gọi thực hiện các bài toán cả hai chiều: - Gọi thực hiện các bài tốn có kích
Từ trên xuống (top - down) hoặc từ dưới thước lớn trước rồi đến các bài tốn
lên (bottom - up).
có kích thước bé hay thực hiện các bài
toán từ trên xuống (top - down)

- Các kết quả trung gian được lưu lại, khi - Các kết quả trung gian không được
giải các bài toán lớn hơn chỉ cần cần lấy lưu lại nên khi cần dùng đến phải tính
ra khơng cần tính tốn lại.
đi tính lại nhiều lần.
- Dành phần lớn bộ nhớ để lưu trữ các kết - Dùng phần lớn bộ nhớ để lưu trữ
quả trung gian.
chương trình con.
- Tốn ít bộ nhớ hơn và thời gian thực hiện - Chiếm dung lượng bộ nhớ rất lớn,
nhanh hơn
thông thường độ phức tạp là hàm mũ.
Trong hầu hết các bài tốn thì quy hoạch động chiếm ưu thế hơn đệ quy nhưng
trong một vài trường hợp cả hai bài toán thực hiện đều như nhau. Ta xét một số ví dụ
cả về quy hoạch động và đệ quy để thấy được tính ưu việt của quy hoạch động.
Ví dụ 1: Bài tốn tính an (n là số ngun dương)
10


Quy hoạch động

Đệ quy

var n,i,a:longint;

var n, a:longint;

f:array[0..100] of int64;

function lthua(a,n:longint):int64;

function lthua(a,n:longint):int64;


begin

begin

if n =0 then lthua:=1

f[0]:=1;

else lthua:=a*lthua(a,n-1);

for i:=1 to n do f[i]:=a*f[i-1];

end;

lthua:=f[i];
end;
* Nhận xét:

* Nhận xét:

- Ta xây dựng mảng f chứa các giá trị
trung gian nên khi cần dùng đến chỉ cần
lấy ở trong f ra khơng cần phải tính lại.
Nên chương trình thực hiện sẽ nhanh
hơn, khơng tốn bộ nhớ.

- Khi chúng ta gọi hàm lthua(n,a)
trong chương trình chính thì ở trong
con sẽ gọi hàm lthua(n-1) và lthua(n2)

- Rồi để gọi hàm lthua(n-1) thì lại phải
gọi hàm lthua(n-2) và lthua(n-3) … cứ
như vậy các hàm sẽ bị gọi lặp lại rất
nhiều lần.
- Như vậy trong bài này khi ta dùng đệ
quy để giải bài toán. Mỗi lần muốn
dùng lại hàm nào thì nó gọi lại hàm đó
nên sẽ tốn nhiều bộ nhớ

Ví dụ 2: Tính n!

Ta có định nghĩa như sau: n! =

nếu

Cho một số nguyên dương n (0 < n < 13).
Quy hoạch động
var n,i:longint;

Đệ quy
var n:longint;

f:array[0..100] of int64;
function gt(n:longint):int64;

function gt(n:longint):int64;

begin

begin

11


f[0]:=1;

if n<2 then gt:=1

f[1]:=1;

else gt:=n*gt(n-1);

for i:= 2 to n do f[i]:=i*f[i-1];

end;

gt:=f[i];
end;
Ví dụ 3: Tính phần tử thứ n của dãy số Fibonaci

Ta có định nghĩa như sau: f(n) =

nếu

Cho một số nguyên dương n (0 < n < 50).
Quy hoạch động

Đệ quy

var n,i,a: longint;


var n: longint;

f: array[0..100] of longint;
function fibo(n:longint): int64;

function fibo(n:longint):int64;

begin

begin

f[0]:=1;

f[1]:=1;

if n <2 then fibo:= 1

for i:=2 to n do f[i]:=f[i-1]+ f[i-2];

else fibo(n):=fibo(n-1)+fibo(n-2);

fibo:=f[i];

end;

end;
* Nhận xét

* Nhận xét:


- Độ phức tạp: O(n).

- Độ phức tạp: O(an) với a ≈
1.61803...
- Do hàm 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 tốn như trên, chúng ta có rất
nhiều bài tốn con sẽ được tính đi tính
lại, điển hình là các số f(1) và f(2).

- Mỗi bài toán con sẽ được lưu lại vào
mảng f trước khi tính những bài tốn con
lớn hơn. Nhờ đó, mà việc tính tốn giảm
đi đáng kể, mỗi bài tốn con chỉ cần tính
đúng một lầ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ử - Đặc điểm của lời giải đệ quy: thực
dụng để tìm lời giải của các bài toán ở hiện bài toán từ việc phân tích ở mức
cao xuống mức thấp.
mức cao hơn.
12


Ví dụ 4: Bài tốn tháp Hà Nội
Chuyển n chiếc đĩa từ cọc 1 sang cọc 2 theo thứ tự từ lớn đến nhỏ. có sử dụng

cọc 3 làm cọc trung gian. Mỗi lần di chuyển được 1 đĩa. Và đĩa đĩa lớn phải ở
dưới đĩa nhỏ.
Quy hoạch động
var n,i:longint;

Đệ quy
var n,i:longint;

f:array[0..100] of longint;
function thaphanoi(n:longint):longint;

function thaphanoi(n:longint):longint;
begin

begin

if n = 1 then exit(1)

f[1]:=1;

else exit (2*thaphanoi(n-1)+1);

for i:= 2 to n do f[i]:=2*f[i-1]+1;

end;

thaphanoi:=f[i];
end;
Ví dụ 5: Bài tốn cái túi
Trong siêu thị có n gói hàng (n <= 100), gói hàng thứ i có trọng lượng là Wi <=

100 và trị giá Vi <= 100. Một tên trộm đột nhập vào siêu thị, sức của tên trộm
không thể mang được trọng lượng vượt quá M ( M <= 100). Hỏi tên trộm sẽ
lấy đi những gói hàng nào để được tổng giá trị lớn nhất.
Quy hoạch động

Đệ quy

procedure qhd;

procedure test;

begin

var i1,s1,s:longint;

fillchar(f[0],sizeof(f[0]),0);

begin

for i:=1 to n do

s:=0;

for j:=1 to m do

s1:=0;

begin

for i1:=1 to n do


dem:=dem+1;

begin

f[i,j]:=f[i-1,j];

inc(dem);

if (j>=w[i]) and (f[i1,j-w[i]]+v[i]>c[i,j]) then

s:=s+v[f[i1]];
s1:=s1+w[f[i1]];

f[i,j]:=f[i-1,j-w[i]]

if s1>m then exit;

+v[i];

end;
13


end;
end;

if s>max then max:=s;
end;


2.2. Phương pháp quy hoạch động và phương pháp vét cạn
Quy hoạch động

Vét cạn

- 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

- Vét cạn là một trong những thuật toán giải bài
toán tối ưu

- Ưu điểm là chạy rất nhanh

- Thuật toán vét cạn là thuật tốn tìm phương án
tối ưu của bài tố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 tốn để tìm ra phương án tối ưu. Trong
nhiều bài tố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 tố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 tồn bộ trường hợp, rồi tìm ra kết
quả.

- Nhược điểm của nó là rất khó - Ưu điểm của vét cạn là chắc chắn tìm ra lời
tìm ra thuật tốn, với một số bài giải (nếu có)

tốn có thể sẽ khơng có thuật
- Nhược điểm của nó là có thể chạy quá lâu,
toán quy hoạch động.
vượt mức thời gian cho phép
Chú ý: 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.
III. Cài đặt chương trình cho một số bài tốn đơn giản thường gặp
Vì trong đề tài chỉ nói đến những bài tốn đơn giản nên thường là những bài
tốn dễ tìm ra phương pháp giải và phương pháp giải thường dùng là đệ quy hoặc quy
hoạch động. Nên phần ví dụ trong SKKN này mỗi bài tốn tơi chỉ xin phép đề cập
đến mối tương quan giữa hai phương pháp là đệ quy và quy hoạch động. Tức là trong
phần chương trình của từng bài tơi thường có thêm biến “đếm” để đếm số lần lặp thực
hiện trong từng bài khi chạy cùng số test để người học có thể thấy được cái nào hay,
cái nào ngắn hơn. Giúp người học dể hiểu, dễ phân biệt và rút ra được cái hay và cái
chưa hay của quy hoạch động trong từng bài toán.
14


Ví dụ 1: Bài tốn tính an (n là số nguyên dương).
Cách xác định bài toán
- Input: a, n
- Output: giá trị an
Ý tưởng của bài toán
- Ta thấy

n = 0 thì f(0) =1
n = 1 thì f(1) = a

n = 2 thì f(2) = f(1)*a
n = 3 thì f(3) = f(2)*a
…..
n = i thì f(i) = f(i-1)*a

- Trong bài tốn này có giá trị chặn là n = 0
Cài đặt chương trình
Phương pháp QHĐ

Phương pháp ĐQ

uses crt;

uses crt;

var n,i,a,dem:longint;

var n,a,dem:longint;

g:text;

f:text;

f:array[0..100] of int64;
function lthua(a,n:longint):int64;

function lthua(a,n:longint):int64;
begin

begin


dem:=dem+1;

f[0]:=1;

if n =0 then lthua:=1

for i:=1 to n do

else lthua:=a*lthua(a,n-1);

begin
f[i]:=a*f[i-1];

end;
BEGIN

dem:=dem+1;

clrscr;

end;

dem:=0;

lthua:=f[i];

assign(f,'nhap.inp'); reset(f);

end;


//write('nhap a,n:=') ; readln(a,n);

BEGIN

readln(f,a,n);
15


clrscr;

close(f);

assign(g,'nhap.inp'); reset(g);

assign(f,'xuat.out'); rewrite(f);

dem:=0;

lthua(a,n);

//write('nhap a,n:=');readln(a,n);
readln(g,a,n);

writeln(f,'KQ BT luy thua thuc hien
bang pp DE QUY la:');

close(g);

writeln(f,'so lan lap la:=',dem);


lthua(a,n);

write(f,'luy thua la:=',lthua(a,n));

assign(g,'xuat.out'); rewrite(g);

close(f);

writeln(g,'KQ BT lthua thuc hien END.
bang pp QHD la:');
writeln(g,'so lan lap la:=',dem);
write(g,'lthua la:=',lthua(a,n));
close(g);
END.
Chạy thử chương trình

* Nhận xét: Qua phần chạy thử bộ test 210 ta thấy chương trình chạy bằng quy hoạch
động số lần lặp là 10. Cịn chương trình chạy bằng đệ quy số lần lặp lại bằng 11. Số
lần lặp sử dụng phương pháp QHĐ ít hơn sử dụng phương pháp ĐQ 1 lần.
Ví dụ 2: Tính n! (n là số nguyên dương)
Cách xác định bài toán
- Input: n
- Output: giá trị giai thừa của n
16


Ý tưởng của bài tốn
- Ta thấy


n = 0 thì f(0) =1
n = 1 thì f(1) = 1
n = 2 thì f(2) = f(1)*2
n = 3 thì f(3) = f(2)*3
…..
n = i thì f(i) = f(i-1)*i

- Trong bài tốn này có hai giá trị chặn là n = 0 và n = 1
Cài đặt chương trình
Phương pháp QHĐ

Phương pháp ĐQ

Uses crt;

Uses crt;

var

var dem,n:longint;

n,i,dem:longint;
g:text;

f:text;

f:array[0..100] of int64;

function gthua(n:longint):int64;


function gthua(n:longint):int64;

begin

begin

dem:=dem+1;

f[1]:=1;

if n <2 then gthua:=1

for i:= 2 to n do

else gthua:=(n*gthua(n-1));

begin

end;
dem:=dem+1;
f[i]:=i*f[i-1];

end;
exit(f[n]);

BEGIN
clrscr;
dem:=0;
//write('nhap n:='); readln(n);


end;

assign(f,'nhap.inp'); reset(f);

BEGIN

readln(f,n);

clrscr;

close(f);

dem := 1;

assign(f,'xuat.out'); rewrite(f);

//write('nhap n:='); readln(n);

gthua(n) ;

//n := 15;

writeln(f,'KQ BT tinh GIAI THUA
bang PP DE QUY la:');
17


assign(g,'nhap.inp');

reset(g);


readln(g,n);

writeln(f,'So lap la:=',dem);
writeln(f,'Giai thua cua ',n,' co gia
tri la:=',GTHUA(n));

close(g);

close(f);

assign(g,'xuat.out');

END.

rewrite(g);
gthua(n) ;
writeln(g,'KQ BT tinh GIAI THUA
bang PP QHD la:');
writeln(g,'So lan lap la:=',dem);
writeln(g,'Giai thua cua so ',n,' co
gia tri la:=',gthua(n));
close(g);
END.

Chạy thử chương trình

* Nhận xét: Ta chạy thử với test bằng 5 thì số lần lặp của hai phương pháp trên là như
nhau, đều lặp lại 5 lần. Ta thấy rằng trong trường hợp này phương pháp quy hoạch
động khơng hơn gì phương pháp đệ quy.

Ví dụ 3: Dãy số fibonacci:
Tính số hạng thứ n của dãy fibonacci bằng phương pháp đệ quy.
Trong đó chuỗi có giá trị như sau: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
Cách xác định bài toán
- Input: n
18


- Output: giá trị fibonacci của n
Ý tưởng của bài tốn
- Ta thấy

n = 0 thì f(0) =1
n = 1 thì f(1) = 1
n = 2 thì f(2) = f(1) + f(0)
n = 3 thì f(3) = f(2) + f(1)
…..
n = i thì f(i) = f(i-1) + f(i-2)

- Trong bài tốn này có hai giá trị chặn là n = 0 và n = 1
Cài đặt chương trình
Phương pháp QHĐ

Phương pháp ĐQ

uses crt;

uses crt;

var


var n,i,dem:longint;

n,i,a,dem:longint;
f:array[0..100] of longint;
g:text;

f:text;
function dq(n:longint):int64;

function qhd(n:longint):int64;

begin

begin

dem:=dem+1;

f[0]:=1;

f[1]:=1;

if n<2 then exit(1)

for i:=2 to n do
begin
f[i]:=f[i-1]+f[i-2];

else exit(dq(n-1)+dq(n-2));
end;

BEGIN

dem:=dem+1 ;

clrscr;

end;

dem:=0;

qhd:=f[i];

//Write('nhap n:='); readln(n);

end;

assign(f,'nhap.inp'); reset(f);

BEGIN

readln(f,n);

clrscr;

close(f);

dem := 2;

dq(n) ;


// Write('nhap n:='); readln(n);

assign(f,'xuat.out'); rewrite(f);
19


assign(g,'nhap.inp'); reset(g);
readln(g,n);
close(g);

writeln(f,'KQ BT tinh so FIBO
bang PP DE QUY la:');
writeln(f,'So lan lap la:=',dem);

qhd(n);

assign(g,'xuat.out'); rewrite(g);
writeln(g,'KQ BT tinh so FIBO
bang PP QHD la:');
writeln(g,'So lan lap la:=',dem);

writeln(f,'So FIBO thu ',n,' co gia
tri la:=',dq(n));
close(f);
END.

writeln(g,'So FIBO thu ',n,' co gia
tri la:=',qhd(n));
close(g);
END.

Chạy thử chương trình

20


* Nhận xét: Trong bài toán này khi chạy thử với giá trị n nhỏ thì thấy số lần lặp
khơng hơn nhau nhiều. Nhưng khi chạy thử với giá trị n lớn hơn thì đối với phương
pháp QHĐ ta thấy số lần lặp là 41. Còn đối với phương pháp ĐQ số lần lặp là 331
160 281, lớn hơn rất nhiều so với QHĐ. Như vậy, trong bài toán này ta chọn PP QHĐ
sẽ nhanh hơn ĐQ rất nhiều.
Ví dụ 4: Bài toán tháp Hà Nội
Chuyển n chiếc đĩa từ cọc 1 sang cọc 2 theo thứ tự từ lớn đến nhỏ. có sử dụng cọc
3 làm cọc trung gian. Mỗi lần di chuyển được 1 đĩa. Và đĩa đĩa lớn phải ở dưới
đĩa nhỏ.
Cách xác định bài toán
- Input: n
- Output: Số bước thực hiện chuyển đĩa từ cọc này sang cọc khác.
Ý tưởng của bài toán
Đầu tiên ta phải chuyển được tất cả (n-1) đĩa nhỏ qua cột thứ hai, chuyển đĩa to
nhất qua cột thứ ba rồi chuyển (n-1) đĩa từ cột thứ hai sang cột thứ ba. Trò chơi sẽ kết
thúc!
Nếu gọi S(n) là số bước để di chuyển n đĩa qua cột ta cần thì:


Bước 1: Chuyển (n-1) đĩa bé hơn từ cột (1) sang cột (2). Chúng ta có S(n-1)
bước di chuyển.



Bước 2: Chuyển đĩa to nhất từ cột (1) sang cột (3). Chúng ta có 1 bước di

chuyển.



Bước 3: Chuyển (n-1) đĩa từ cột (2) sang cột (3). Chúng ta có S(n-1) bước di
chuyển.

Cài đặt chương trình
Phương pháp QHĐ

Phương pháp ĐQ

uses crt;

uses crt;

var dem,i,n:longint;

var dem,n:longint;

f:array[0..100] of longint;

f:text;

g:text;

function thaphn(n:longint):longint;

function thaphn(n:longint):longint;


begin

begin

dem:=dem+1;
21


// dem:=dem+1;

if n=1 then exit(1)

f[1]:=1;

else exit (1+2*thaphn(n-1)) ;

for i:=2 to n do

end;

begin

BEGIN

f[i]:=2*f[i-1]+1;

clrscr;

dem := dem+1;


dem:=0;

end;

assign(f,'nhap.inp');

thaphn:=f[i];

reset(f);

end;

readln(f,n);

BEGIN

close(f);

clrscr;

assign(f,'xuat.out');

dem := 1;

rewrite(f);

//write('Nhap vao so chiec dia:=');
// write('Nhap vao so chiec
readln(n);
dia:='); readln(n);

assign(g,'nhap.inp');

reset(g);

readln(g,n);

writeln(f,'KQ BT thap HA NOI
bang PP DQ la:');
// thaphn(n);

close(g);
assign(g,'xuat.out');

rewrite(g);

writeln(g,'KQ BT thap HA NOI
bang PP QHD la:');

writeln(f,'so lan chuyen la :
',thaphn(n));
writeln(f,'So lan lap la:=',dem);
close(f);

//thaphn(n);

writeln(g,'so lan chuyen la : END.
',thaphn(n));
writeln(g,'So lan lap la:=',dem);
close(g);
END.

Chạy thử chương trình

22


* Nhận xét: Trong bài toán này cả hai phương pháp đều có số lần lặp như nhau.
Ví dụ 5: Bài tốn cái túi
Trong siêu thị có n gói hàng (n <= 100), gói hàng thứ i có trọng lượng là W i <=
100 và trị giá Vi <= 100. Một tên trộm đột nhập vào siêu thị, sức của tên trộm không
thể mang được trọng lượng vượt quá M (M <= 100). Hỏi tên trộm sẽ lấy đi những gói
hàng nào để được tổng giá trị lớn nhất.
Cách xác định bài tốn
caitui.inp

caitui. out



Dịng 1: n, m cách nhau ít nhất Ghi giá trị lớn nhất tên trộm có thể lấy
một dấu cách



n dịng tiếp theo: Mỗi dịng gồm 2
số Wi, Vi, là chi phí và giá trị đồ
vật thứ i.

Ví dụ
caitui.inp


caitui. out

5 15

15

12 4
22
11
12
4 10
Ý tưởng của bài tốn
23


Giải quyết bài tốn trong các trường hợp sau:


Mỗi vật chỉ được chọn một lần.



Mỗi vật được chọn nhiều lần (không hạn chế số lần)

Cách giải:
* Trường hợp mỗi vật được chọn 1 lần
Ta nhận thấy rằng: Giá trị của cái túi phụ thuộc vào 2 yếu tố: Có bao nhiêu vật
đang được xét và trọng lượng còn lại cái túi có thể chứa được, do vậy chúng ta có 2
đại lượng biến thiên. Cho nên hàm mục tiêu sẽ phụ thuộc vào hai đại lượng biến
thiên. Do vậy bảng phương án của chúng ta sẽ là bảng 2 chiều.

Gọi F[i,j] là tổng giá trị lớn nhất của cái túi khi xét từ vật 1 đến vật i và trọng
của cái túi chưa vượt quá j. Với giới hạn j, việc chọn tối ưu trong số các vật {1,2,…,i1,i} để có giá trị lớn nhất sẽ có hai khả năng:
Nếu khơng chọn vật thứ i thì F[i,j] là giá trị lớn nhất có thể chọn trong số các
vật {1,2,…,i-1} với giới hạn trọng lượng là j, tức là:
F[i,j]:=F[i-1,j].
Nếu có chọn vật thứ i (phải thỏa điều kiện W[i] ≤ j) thì F[i,j] bằng giá trị vật
thứ i là V[i] cộng với giá trị lớn nhất có thể có được bằng cách chọn trong số các vật
{1,2,…,i-1} với giới hạn trọng lượng j-W[i] tức là về mặt giá trị thu được:
F[i,j]:=V[i]+F[i-1,j-W[i]]
Vậy chúng ta phải xem xét xem nếu chọn vật i hay khơng chọn vật i thì sẽ tốt hơn.
Từ đó chúng ta có cơng thức truy hồi như sau.


F[0,j] = 0 (hiển nhiên) – Bài tốn con nhỏ nhất.



F[i,j]= max(F[i-1,j], V[i]+F[i-1,j-W[i]]

* Trường hợp mỗi vật được chọn nhiều lần: Tương tự như suy luận ở trên ta xét:
Nếu không chọn vật thứ i thì F[i,j] là giá trị lớn nhất có thể chọn trong số các
vật {1,2,…,i-1} với giới hạn trọng lượng là j, tức là:
F[i,j]:=F[i-1,j].
Nếu có chọn vật thứ i (phải thỏa điều kiện W[i] ≤ j) thì F[i,j] bằng giá trị vật
thứ i là V[i] cộng với giá trị lớn nhất có thể có được bằng cách chọn trong số các vật
{1,2,…,i} (vì vật i vẫn có thể được chọn tiếp) với giới hạn trọng lượng j-W[i] tức là
về mặt giá trị thu được:
F[i,j]:=V[i]+F[i,j-W[i]]
24



Do vậy chúng ta có cơng thức truy hồi như sau:


F[0,j] = 0 (hiển nhiên) – Bài tốn con nhỏ nhất.



F[i,j]= max(F[i-1,j], V[i]+F[i,j-W[i]]
Cài đặt chương trình
Phương pháp QHĐ

Phương pháp ĐQ

uses crt;

uses crt;

const

const

fi='nhap.inp';

fi='nhap.inp';

fn='xuat.out';

fn='xuat.out';


var

var
g1,g2:text;

g1,g2:text;

n,m,i,j,dem:longint;

v,w,f:array[0..100] of longint;

v,w:array[1..100] of longint;

n,m,i,j,dem,max:longint;

f:array[0..100,0..100] of longint;

kt:array[0..100] of boolean;

//=================

//=================

procedure qhd;

procedure test;

begin

var i1,s1,s:longint;


fillchar(f[0],sizeof(f[0]),0);

begin

for i:=1 to n do

s:=0; s1:=0;

for j:=0 to m do

for i1:=1 to n do

begin

begin
dem:=dem+1;

inc(dem);

f[i,j]:=f[i-1,j];

s:=s+w[f[i1]];

if (j>=v[i]) and
(f[i-1,j-v[i]]+w[i]>f[i,j]) then

s1:=s1+v[f[i1]];
if s1>m then exit;


f[i,j]:=f[i-1,j-v[i]]

end;

+w[i];
end;
end;
//==================

if s>max then max:=s;
end;
//===================
25


×