ĐẶT VẤN ĐỀ Thuật toán còn gọi là giải thuật, là một tập hợp hữu hạn của các chỉ thị hay phương thức được định nghĩa rõ ràng cho việc hoàn tất một số sự việc từ một trạng thái ban đầu cho
Trang 1MỤC LỤC
I ĐẶT VẤN ĐỀ 2
II MỘT SỐ NÉT CƠ BẢN VỀ PHƯƠNG PHÁP QUY HOẠCH ĐỘNG 3
1 Lý thuyết cơ bản về quy hoạch động 3
2 Giải bài toán theo phương pháp quy hoạch động 4
III MỘT SỐ HẠN CHẾ CỦA PHƯƠNG PHÁP QUY HOẠCH ĐỘNG 5
IV- THUẬT TOÁN 6
1 Bài toán 6
2 Mô tả thuật toán 7
3 Ý tưởng và cách giải 7
4 Độ phức tạp của thuật toán 9
V CÀI ĐẶT CHƯƠNG TRÌNH 9
TÀI LIỆU THAM KHẢO 13
Trang 2I ĐẶT VẤN ĐỀ
Thuật toán còn gọi là giải thuật, là một tập hợp hữu hạn của các chỉ thị hay phương thức được định nghĩa rõ ràng cho việc hoàn tất một số sự việc từ một trạng thái ban đầu cho trước; khi các chỉ thị này được áp dụng triệt để thì sẽ dẫn đến kết quả sau cùng như đã dự đoán Nói cách khác, thuật toán là một bộ các quy tắc hay quy trình cụ thể nhằm giải quyết một vấn đề trong một số bước hữu hạn, hoặc nhằm cung cấp một kết quả từ một tập hợp của các dữ kiện đưa vào
Phân tích và thiết kế thuật toán là một lĩnh vực quan trọng của khoa học máy tính Quá trình phân tích thuật toán sẽ giúp ta hiểu sâu sắc được mục đích, yêu cầu mà thuật toán cần xây dựng, qua đó có thể tiên liệu được các tài nguyên
mà thuật toán yêu cầu như: tài nguyên bộ nhớ, băng thông, hoặc các tài nguyên ngoại vi… Song phần chính thời gian tính toán mới là yếu tố quan trọng mà ta cần quan tâm và đánh giá khi thuật toán thực hiện
Trong bài tập lớn này em xin trình bày một số hiểu biết của mình về phương pháp quy hoạch động và phân tích, đánh giá thuật toán tìm dãy con đơn điệu tăng (không giảm) dài nhất trong một dãy số bất kỳ cho trước
Do kiến thức còn hạn chế, trong quá trình trình bày không tránh khỏi những khiếm khuyết, rất mong nhận được sự đóng góp ý kiến quý báu của Thầy
và các bạn
Em xin chân thành cảm ơn Thầy Đào Thanh Tĩnh, và toàn thể các bạn trong lớp Cao học KHMT–K25b đã tạo điều kiện thuận lợi để em hoàn thành bài tập này
Học viên
Trần Tuấn Anh
Trang 3II MỘT SỐ NÉT CƠ BẢN VỀ PHƯƠNG PHÁP QUY HOẠCH ĐỘNG
1 Lý thuyết cơ bản về quy hoạch động
Phương pháp quy hoạch động là một phát minh của nhà toán học Mỹ, Richard Bellman, người mà đưa ra nhận định rằng để giải quyết một vấn đề thì điều cần thiết là cần phải lựa chọn ra một quyết định tốt nhất Hơn 40 năm qua,
kể từ khi phát minh này ra đời thì số lượng người sử dụng quy hoạch động đã trở lên phổ biến
Quy Hoạch Động là một phương pháp rất mạnh mẽ trong Tin học Nếu phân tích kỹ chúng ta sẽ thấy những thuật toán nổi tiếng như Ford-Bellman, Dijkstra, hay Floyd đều có bản chất là quy hoạch động
Phương pháp quy hoạch động thường dùng để giải bài toán tối ưu có bản chất đệ quy, tức là việc tìm phương án tối ưu cho bài toán đó có thể đưa về tìm phương án tối ưu của một số hữu hạn các bài toán con Ðối với một số bài toán
đệ quy, nguyên lý chia để trị (Divide and Conquer) thường đóng vai trò chủ đạo trong việc thiết kế thuật toán Ðể giải quyết một bài toán lớn, ta chia nó thành nhiều bài toán con cùng dạng với nó để có thể giải quyết độc lập Trong phương
án quy hoạch động, nguyên lý này càng được thể hiện rõ: Khi không biết phải giải quyết những bài toán con nào, ta sẽ đi giải quyết toàn bộ các bài toán con và lưu trữ những lời giải hay đáp số của chúng với mục đích sử dụng lại theo một
sự phối hợp nào đó để giải quyết những bài toán tổng quát hơn Ðó chính là điểm khác nhau giữa quy hoạch động và phép phân giải đệ quy và cũng là nội dung phương pháp quy hoạch động
Phương pháp phân tích từ trên xuống (Top-Down) chính là phép phân giải
đệ quy bắt đầu từ bài toán lớn phân ra thành nhiều bài toán con và đi giải từng bài toán con đó Việc giải từng bài toán con lại đưa về phép phân ra tiếp thành nhiều bài toán nhỏ hơn và lại đi giải các bài toán nhỏ hơn đó bất kể nó đã được giải hay chưa
Trang 4Quy hoạch động là phương pháp ngược lại của phép giải Đệ qui bắt đầu từ dưới lên (Bottom-Up), từ việc nó 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
2 Giải bài toán theo phương pháp quy hoạch động
Bài toán giải theo phương pháp quy hoạch động gọi là bài toán quy hoạch động
Công thức phối hợp nghiệm của các bài toán con để có nghiệm của bài toán lớn gọi là công thức truy hồi của quy hoạch động
Tập các bài toán có ngay lời giải để từ đó giải quyết các bài toán lớn hơn gọi là cơ sở quy hoạch động
Không gian lưu trữ lời giải các bài toán con để tìm cách phối hợp chúng gọi là bảng phương án của quy hoạch động
Phương pháp giải bài toán quy hoạch động:
- Tìm công thức truy hồi (đệ quy) thường xuất phát từ trường hợp đơn giản
có thể tìm ra nghiệm ngay
- Tìm dữ liệu thích hợp
Chú ý: Cần nhìn bài toán từ nhiều góc độ, khía cạnh.
- Từ dễ đến khó, từ đơn giản đến phức tạp
- Không nên phức tạp hoá bài toán đơn giản
Nếu bài toán tối ưu ta tìm được công thức truy hồi thì ta có thể giải bài toán bằng phương pháp quy hoạch động Bởi vì quy hoạch động là một phương pháp cải tiến hơn của phương pháp giải bài toán theo hướng phân rã Từ vấn đề lớn ta chia nó ra và tiếp tục như vậy đến khi gặp bài toán nhỏ hơn, đơn giản hơn
và có thể giải quyết được một cách dễ dàng
Trang 5Phương pháp phân rã
Nhưng khi giải theo phương pháp phân rã ta bị hạn chế về tốc độ do chương trình phải tính đi tính lại nhiều lần một số bài toán con giống nhau nào
đó Để khắc phục nhược điểm này, quy hoạch động đã ra đời kỹ thuật
Bottom-Up đi từ dưới lên Đi từ trường hợp riêng đơn giản nhất có thể tìm ngay ra nghiệm Kết hợp các nghiệm của bài toán cỡ nhỏ ta thu được nghiệm bài toán cỡ lớn hơn và cứ tiếp tục cho đến khi tìm được nghiệm của bài toán đề ra
Trường hợp đơn giản:
III MỘT SỐ HẠN CHẾ CỦA PHƯƠNG PHÁP QUY HOẠCH ĐỘNG
Không phải bài toán nào cũng giải bằng quy hoạch động được, nếu quá áp đặt, cố sức giải nó như vậy chúng ta sẽ dễ dàng mắc phải sai lầm gọi là Phụ thuộc tiền sử
Bài toán
BT111
1
Các bài toán con giống nhau
f0
f0'
f0''
f1
f1'
fn
Phương pháp QHĐ
Trang 6Để tìm hiểu và khắc phục điều này em xin phép mạnh dạn đưa ra một ví
về nguyên lý quy hoạch động do Bellman phát minh ra: "Một dãy là tối ưu thì các dãy con của nó cũng là tối ưu" Nghe có vẻ khó hiểu nhưng thực chất là ta
có một bảng rồi lần lượt điền các số vào bảng đó, các số điền vào đầu tiên thường đơn giản, các số sau đó dựa vào các số trước mà điền, cho đến khi điền kín bảng Kết quả bài toán là số mà ta điền cuối cùng Như vậy nguyên lý "chia
để trị" đã được đẩy tới cao độ ở đây Khâu quan trọng nhất là làm thế nào điền được số sau dựa trên các số trước, hay nói cách khác là ta đang tìm một hệ thức truy hồi
Gọi f(i) là hàm quy hoạch động tại i, ta xây dựng công thức truy hồi tính f(i) dựa vào các f(j) với j = i - 1, i - 2, Công thức truy hồi này chỉ được phụ thuộc vào các f(j) cùng các đại lượng cố định của các j (cho trong đề bài) tuyệt đối không được phụ thuộc vào tiền sử, cách thức tạo nên f(j) Đây là chính là một sai lầm khi sử dụng quy hoạch động
Như vậy, không phải lúc nào sự kết hợp các lời giải của các bài toán con cũng cho ta lời giải đúng của bài toán cỡ lớn hơn Số lượng bài toán con cần giải quyết và lưu trữ đáp án có thể rất lớn không thể chấp nhận được vì bộ nhớ máy tính không cho phép
Khi giải bài toán bằng phương pháp quy hoạch động cần phải có
phương trình truy hồi chính xác
Tuy nhiên khi áp dụng phương pháp quy hoạch động để giải một số bài toán tối ưu thường mang lại hiệu quả tốt nhất
IV- THUẬT TOÁN
1 Bài toán
Phương pháp quy hoạch động Giải bài toán sau: Mỗi đoạn thẳng trên trục Ox được mô tả bới hai giá trị [a, b] Kí hiệu S là tập hợp n đoạn thẳng
S = { [ai,bi], i = 1,2, ,n} Xây dựng thuật toán tìm tập S*S sao cho các
Trang 7đoạn thẳng trong S* đôi một không có điểm chung và |S*| có giá trị lớn nhất.
Nhận định bài toán: Giả sử ta ràng buộc đầu vào là: a,b là các số nguyên
và ai<bi (ai=bi thì đoạn thẳng này là một điểm ta không xét đến) Nhận thấy các đoạn thẳng đôi một không có diểm chung khi đặt trên trục Ox nếu đọc tức trái qua phải (không xét đến thứ tự trong tập S) thì luôn là một dãy tăng liên tục Ta thấy đâu đó bài toán tìm dãy con tăng đơn điệu Vì vậy nếu đưa đầu vào của tập S về dạng một dãy số {a1,b1,a2,b2,a3,b3… an,bn} thì bài toán trở về dạng tìm dãy con tăng đơn điệu lớn nhất của dãy số trên Tuy nhiên nếu áp dụng thuật toán tìm dãy con tăng đơn điệu vào bài toán này thì rất có thể bỏ sót một vài đoạn thẳng Ví dụ: với đầu vào là một dãy {9,10,1,2,3,4,2,4} nếu áp dụng thuật toán tìm dãy tăng đơn điệu lớn nhất thì ta có dãy con S* {1,2,3,4} tức là gồm 2 đoạn thẳng (1,2) và (3,4), tuy nhiên nếu dựa vào dữ kiện bài toán thì S* phải là 3 đoạn thẳng bao gồm (9,10), (1,2) và (3,4)
Để khắc phục thiếu khuyết trên ta sắp xếp lại dãy S thành S’ theo chiều tăng dần của bi Khi đó S’={1,2,3,4,2,4,9,10} ta áp dụng thuật toán tìm dãy con tăng đơn điệu dài nhất S’*={1,2,3,4,9,10} hay tập S’* gồm các đoạn thẳng (1,2), (3,4), (9,10) Công việc còn lại là trả các đoạn thẳng về đúng vị trí của nó (9,10), (1,2) và (3,4)
Tuy nhiên khó khăn vẫn chưa dừng lại ở đây bởi trong tập S’* các phần tử liền kề nhau chưa chắc đã là một đoạn thẳng nằm trong tập S cho trước Ví dụ S={9,10,1,2,3,4,2,5} => S’={1,2,3,4,2,5,9,10} => S’*={1,2,3,4,5,9,10} Nhiệm
vụ của ta là phải làm sao loại phần tử 5 ra khỏi dãy bằng cách từ tập S’* lấy thứ tự từng cặp phần tử một so sánh với từng cặp phần tử trong dãy S Nếu trùng thì dừng so sánh và lưu chỉ số của phần tử trùng trong dãy S vào mảng Idx Tiếp tục nhảy sang so sánh cặp tiếp theo trong dãy S’* Nếu không trùng thì loại phần tử đầu tiên trong cặp đó, dùng phần tử còn lại của cặp này kết hợp với phần tử liền kề sau nó tạo thành một cặp mới rồi lặp lại bước so sánh Cứ như vậy cho đến hết tập S’* Sắp xếp các phần tử trong mảng Idx theo chiều
Trang 8tăng dần Dựa vào các phần tử trong mảng Idx sau khi đã sắp xếp gọi các phần
tử trong tập S ta được tập S* thỏa mãn yêu cầu đề bài
Như vậy bài toán sẽ gồm các bước
Bước 1: Sắp xếp lại dãy S thành S’ theo chỉ số lẻ
Bước 2: Dùng thuật toán tìm dãy tăng đơn điệu dài nhất để tìm
Bước 3: Loại bỏ các phần tử liền kề không phải là đoạn thẳng
Bước 4: Trả về đúng thứ tự của các phần tử tìm được trong tập S Tập mới
sẽ là tập S* thỏa mãn đầu bài
2 Mô tả thuật toán tìm dãy tăng đơn điệu dài nhất
Bài toán tìm dãy đơn điệu dài nhất là bài toán về thể loại xâu ký tự (Character strings) Yêu cầu của bài toán là từ một dãy ký tự cho trước ta phải tìm ra một dãy con không tăng hoặc không giảm có dộ dài lớn nhất Đối với những loại bài toán kiểu như thế này có lẽ cách giả tốt nhất là sử dụng thuật toán quy hoạch động để giải
Ví dụ: cho dãy số nguyên A = ai (i<=10000, |ai|<= 10000) Tìm cách chọn
ra một dãy con không giảm hoặc không tăng B của A (B A) (bao gồm một số các phần tử trong A nhưng vẫn giữ nguyên thứ tự) có độ dài lớn nhất
Giả sử cho dãy : A = ( 1, 2, 3, 4, 5, 9, 10, 5, 4, 6, 7, 8)
Ta tìm được dãy con B không giảm dài nhất là B = (1,2,3,4,5,5,6,7,8)
Mô tả:
Input : Chạy file De18.exe
+ Nhập số phần tử trong day S
Output: Hiện kết quả ra màn hình
Trang 9+ Dòng 1: Kết quả Tập S gồm các đoạn thẳng
+ Dòng 2: Day S
+ Dòng 3: Day SS sau khi đã sắp xếp tăng dần theo chỉ số lẻ(bi)
+ Dòng 4: Day các chỉ số L
+ Dòng 5: Kết quả dãy con tăng đơn điệu S’*
+ Dòng 6: Trả về vị trí i được chọn
+ Dòng 7: Tập S* sau khi lấy theo vị trí I đươc chọn từ dãy S
3 Ý tưởng và cách giải
Ý tưởng để giải thuật toán này như sau: Ta thêm vào dãy ban đầu 2 phần
tử S[0] = -Maxinteger và S[n+1] = Maxinteger làm hai đầu mút Vậy dãy con tăng dài nhất sẽ bắt đầu là A[0] và kết thúc là A[n+1] Với i : 0<=i<n+1 thì ta gọi Fa[i] là độ dài của dãy con bắt đầu bởi A[i] Điều đó có nghĩa là Fa[n+1] là
độ dài của dãy con bắt đầu từ N+1 Dãy này chỉ có 1 phần tử nên có độ dài là 1 Fa[n+1] = 1 Ta sẽ bắt đầu xét các phần tử A[i] với i từ 0 đến N ta tính Fa[i] dựa trên các Fa[i+1],Fa[i+2],…,Fa[n+1] đã tính được trước đó
Dãy con không giảm dài nhất bắt đầu từ A[i] có thể được cập nhật thêm A[i] theo cách như sau : Xét tất cả các chỉ số j trong khoảng i+1 đến N+1 mà A[j] >A[i], ta chọn ra chỉ số jmax có Fa[jmax] lớn nhất Và lúc này thì:
Fa[i] = Fa[jmax] +1
Để có thể lần lại được kết quả thì khi gán Fa[i]= Fa[jmax]+ 1, ta cũng gán T[i] = jmax Để biết rằng dãy con bắt đầu từ A[i] thì sẽ có phần tử thứ 2 kế tiếp
là A[jmax]
Ý tưởng thực hiện bằng đoạn code sau
for (i=1; i<m; i++)
{
int max = 1; //Do dài DCDDTDN cua dãy S[0], ,S[i]
for ( j=0; j<i; j++)
Trang 10if (SS[j] < SS[i] && max < L[j] + 1)
{
max = L[j] + 1;
T[i] = j; //De sau này truy vet: phan tu ngay phía sau S[i] là S[j]
}
L[i] = max;
}
//Buoc 2 Tìm vi trí cuoi cua DC??TDN
int lMax = 0;
int viTriMax = 0; //a[viTriMax] se là phan tu cuoi cùng trong DCDDTDN cua dãy S
for (i=1; i<m; i++)
if (L[i] > lMax)
{
lMax = L[i];
viTriMax = i;
}
//Buoc 3 Truy vet de tìm DCDDTDN (kq): dua vao T và viTriMax
int kq[lMax];
int k = lMax-1;
int Lm,imax;
for(i=lMax-1;i>-1;i )
{
imax=viTriMax;
kq[i] = SS[viTriMax];
Lm=0;
viTriMax ;
Trang 11
if(SS[j]<kq[i] && L[j]>Lm && L[j]<L[imax]) {Lm=L[j]; viTriMax=j;}
}
}
n=lMax/2;
int chiso[n];
for(i=0;i<n;i++)chiso[i]=-1;
int check;
4 Độ phức tạp của thuật toán
Đánh giá độ phức tạp của thuật toán:
- Trong vòng lặp thứ nhất có 2 vòng lập lồng nhau số phép tính phải thực hiện n2/2
- Vòng lặp thứ hai số phép gán phải thực hiện là: 2n
- Vòng lặp thứ hai số phép gán phải thực hiện là: n2/2
- Trường hợp xấu nhất số phép gán phải thực hiện sẽ là:
+ Vòng lặp 1 sẽ là: n2/2 + Vòng lặp 2 sẽ là: 2n + Vòng lặp 2 sẽ là: n2/2
=> Trường hợp xấu nhất thì số phép gán phải thực hiện sẽ là: n2+2n
Vậy độ phức tạp của thuật toán là: O(n2)
V CÀI ĐẶT CHƯƠNG TRÌNH
Toàn bộ chương trình cài đặt như sau:
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
void quicksort(int a[],int chiso[],int dau,int cuoi);
Trang 12void Input(int n,int S[]);
int main()
{
int i,n,m,j;
printf("Nhap so phan tu trong day S: ");scanf("%d",&n);
m=n;
int S[n],SS[n],L[n],T[n],I[n];
Input(n,S);
printf("Tap S gom ca doan thang: ");
for (i=0; i<m; i=i+2) printf("(%d,%d), ", S[i],S[i+1]);
printf("\nS = ");
for(i=0; i<n; i++)
printf("%i ",S[i]);
n=sizeof(S)/sizeof(int)/2;
int b[n],c[n];
for(i=0;i<n;i++){b[i]=S[i*2+1]; c[i]=S[i*2];}
quicksort(b,c,0,n-1);
printf("\nDay sau sap xep:\n");
for(i=0;i<n;i++)
{
SS[i*2]=c[i];
SS[i*2+1]=b[i];
}
printf("SS = ");
for (i=0; i<m; i++) printf("%d ", SS[i]);
// tim Day con
Trang 13for (i=1; i<m; i++)
{
int max = 1; //Do dài DCDDTDN cua dãy S[0], ,S[i]
for ( j=0; j<i; j++)
if (SS[j] < SS[i] && max < L[j] + 1)
{
max = L[j] + 1;
T[i] = j; //De sau này truy vet: phan tu ngay phía sau S[i] là S[j]
}
L[i] = max;
}
//Buoc 2 Tìm vi trí cuoi cua DC??TDN
int lMax = 0;
int viTriMax = 0; //a[viTriMax] se là phan tu cuoi cùng trong DCDDTDN cua dãy S
for (i=1; i<m; i++)
if (L[i] > lMax)
{
lMax = L[i];
viTriMax = i;
}
//Buoc 3 Truy vet de tìm DCDDTDN (kq): dua vao T và viTriMax
int kq[lMax];
int k = lMax-1;
int Lm,imax;
for(i=lMax-1;i>-1;i )
{
imax=viTriMax;