Tải bản đầy đủ (.pdf) (17 trang)

BÁO CÁO THỰC TẬP-Lập trình quy hoạch độ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 (358.31 KB, 17 trang )

Lập trình quy hoạch động
(Dynamic Programming)
1
Giới thiệu
• Nhà toán học Richard Bellman đã phát minh
phương pháp quy hoạch động vào năm 1953.
• 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).
2
Giới thiệu
• 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.

3
Giới thiệu
• 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:
– 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 con này một cách tối ưu bằng cách sử
dụng một cách đệ quy quy trình ba bước này.
– 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.
• Như vậy, 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 để tìm lời giải.


4
Giới thiệu
• Nói rằng một bài toán có các bài toán con
trùng nhau có nghĩa là mỗi bài toán con đó
được sử dụng để giải nhiều bài toán lớn hơn
khác nhau.
• Ví dụ, trong dãy Fibonacci, F
3
= F
1
+ F
2
và F
4

= F
2
+ F
3
— khi tính mỗi số đều phải tính F
2
.
Vì tính F
5
cần đến cả F
3
và F
4
, một cách tính
F

5
một cách đơn giản có thể sẽ phải tính F
2
hai
lần hoặc nhiều hơn.
5
Giới thiệu
• Điều này xảy ra mỗi khi có mặt các bài toán
con gối nhau: do vậy tốn thời gian tính toán lại
lời giải tối ưu cho các bài toán con mà nó đã
giải.
• Để tránh việc đó, ta lưu trữ lời giải của các bài
toán con đã giải. Do vậy, nếu sau này ta cần
giải lại chính bài toán đó, ta có thể lấy và sử
dụng kết quả đã được tính toán.

6
Giới thiệu
• Hướng tiếp cận này được gọi là lưu trữ (trong
tiếng Anh được gọi là memoization, không
phải memorization).
• Nếu ta chắc chắn rằng một lời giải nào đó
không còn cần thiết nữa, ta có thể xóa nó đi để
tiết kiệm không gian bộ nhớ.
• Trong một số trường hợp, ta còn có thể tính lời
giải cho các bài toán con mà ta biết trước rằng
sẽ cần đến.
7
Giới thiệu
• Tóm lại, quy hoạch động sử dụng:

– Các bài toán con gối nhau
– Cấu trúc con tối ưu
– Lưu trữ

8
Giới thiệu
• Quy hoạch động thường dùng một trong hai
cách tiếp cận:
– top-down (từ trên xuống): Bài toán được chia
thành các bài toán con, các bài toán con này được
giải và lời giải được ghi nhớ để phòng trường hợp
cần dùng lại chúng. Đây là đệ quy và lưu trữ được
kết hợp với nhau.

9
Giới thiệu
• Quy hoạch động thường dùng một trong hai
cách tiếp cận:
– bottom-up (từ dưới lên): Tất cả các bài toán con
có thể cần đến đều được giải trước, sau đó được
dùng để xây dựng lời giải cho các bài toán lớn hơn.
Cách tiếp cận này hơi tốt hơn về không gian bộ
nhớ dùng cho ngăn xếp và số lời gọi hàm. Tuy
nhiên, đôi khi việc xác định tất cả các bài toán con
cần thiết cho việc giải quyết bài toán cho trước
không được trực giác lắm.
10
Dãy Fibonacci
• Một cài đặt đơn giản của một hàm tính phần tử
thứ n của dãy Fibonacci, trực tiếp dựa theo

định nghĩa toán học.
function fib(n)
if n = 0 or n = 1
return n
else
return fib(n − 1) + fib(n − 2)
11
Dãy Fibonacci
• Giả sử nếu gọi, chẳng hạn, fib(5), ta sẽ tạo ra
một cây các lời gọi hàm, trong đó các hàm của
cùng một giá trị được gọi nhiều lần:
– fib(5)
– fib(4) + fib(3)
– (fib(3) + fib(2)) + (fib(2) + fib(1))
– ((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) +
fib(0)) + fib(1))
12
Dãy Fibonacci
– (((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) +
((fib(1) + fib(0)) + fib(1))

• Trong minh họa trên, fib(2) được tính hai lần.
Trong các ví dụ lớn hơn, sẽ có nhiều giá trị của
fib, hay các bài toán con được tính lại, dẫn đến
một thuật toán có thời gian lũy thừa.

13
Dãy Fibonacci
• Giả sử ta có một đối tượng ánh xạ đơn giản, nó
ánh xạ mỗi giá trị của fib đã được tính tới kết

quả của giá trị đó.
• Ta sửa đổi hàm trên như sau để sử dụng và cập
nhật ánh xạ trên. Hàm thu được chỉ đòi hỏi
thời gian chạy O(n) thay vì thời gian chạy lũy
thừa:

14
Dãy Fibonacci
var m := map(0 → 1, 1 → 1)
function fib(n)
if n not in keys(m)
m[n] := fib(n − 1) + fib(n − 2)
return m[n]
15
Dãy Fibonacci
• Nhận xét: Đây là cách tiếp cận từ trên xuống,
do trước hết ta chia bài toán thành các bài toán
nhỏ hơn, rồi giải chúng và lưu trữ các kết quả.
• Trong trường hợp này, ta cũng có thể giảm từ
chỗ hàm sử dụng không gian tuyến tính (O(n))
xuống chỉ còn sử dụng không gian hằng bằng
cách sử dụng cách tiếp cận từ dưới lên. Cách
này tính các giá trị nhỏ hơn của fib trước, rồi
từ đó xây dựng các giá trị lớn hơn:
16
Dãy Fibonacci
function fib(n)
var previousFib := 1, currentFib := 1
repeat n − 1 times
var newFib := previousFib + currentFib

previousFib := currentFib
currentFib := newFib
return currentFib
17

×