ĐỀ TÀI :
BÀI TOÁN SUBSET SUM VÀ LONGEST INCREASING SEQUENCE
GVHD : PGS.TS Nguyễn Đức Nghĩa
1
Mục lục
I.phần 1:Tìm hiểu về phương pháp quy hoạch động (dynamic programming).
II.phần 2:Tìm hiểu về bài toán subset sum va cài đặt thuật toán.
III.phần 3: Tìm hiểu bài toán longest increasing sequence.
IV.phần 4: Thống kê thời gian tính của thuật toán.
V.kết luận.
VI.chương trình nguồn.
2
I.Tìm hiểu về phương pháp thiết kế thuật toán quy hoạch động:
I.1 giới thiệu về phương pháp quy hoạch động:
Nhà toán học Richard Bellman đã phát minh phương pháp lập trình động vào
năm 1953. Ngành này đã được thành lập như là một chủ đề về kỹ nghệ và phân
tích hệ thống đã được tổ chức IEEE thừa nhận. Lập trình động thường được áp
dụng cho các bài toán tôi ưu hóa ở đó một tập hợp các chọn lựa phải được thực
hiện để đạt đến một giải pháp tối ưu.Khi thực hiện chọn lựa thì các bài toán con
cùng dạng tương ướng nảy sinh.Lập trình động tỏ ra hiệu quả khi một bài toán con
đã cho có thể nảy sinh từ nhiều tập hợp bộ phận chọn lựa; kỹ thuật đó là lưu trữ
hoặc “memo hóa”(memoize) giải pháp của từng bài toán con đề phòng trường hợp
nó tái xuất hiên ta có thể tra cứu kết quả ma không cần phải giải lại bài toán con
đó.Với ý tưởng đơn giản này mà ta có thể dễ dàng biến đổi các thuật toán thời
gian mũ (exponentinal time) thành các thuật toán thời gian đa thức (polynomial
time ).
Giống như phương pháp chia đê trị, lập trình động giải quyết bài toán bằng
cách tổ hợp các nghiệm của bài toan con(Lập trình động co nghĩa là một phương
pháp điền bảng ).Như đã biết các thuật toán chia để trị phân hoạch bài toán thành
các bài toán con độc lập, giải quyết các bài toán con một cách đệ quy, rồi tổ hợp
các nghiệm của chúng để giải quyết bài toán ban đầu. Ngược lại lập trình động có
thể áp dụng khi các bài toán con không độc lập nghĩa là các bài toán con chia sẻ
các bài toán “cháu” (subsubproblems). Một thuật toán chia để trị thực hiện công
việc nhiều hơn mức cần thiết, lien tục giải quyết các bài toán cháu chung. Thuật
toán lập trình động giải quyết mọi bài toán cháu rồi lưu đáp án vào một bảng, nhờ
đó ma tránh được việc tính lại các đáp án mỗi lần gặp bài toán cháu.
Lập trình động thường được áp dụng cho các bài toán tối ưu hóa. Trong các bài
toán như vậy thường có nhiều giải pháp khả dĩ. Mỗi giải pháp có một giá trị,ta
muốn tìm một giải pháp có giái trị tối ưu (cực đại hoặc cực tiểu).Ta gọi kiểu giải
pháp đó là một giải pháp tối ưu cho bài toán khác với tối ưu nghiệm vì cò thể có
vài nghiệm đạt giái trị tôi ưu.
3
Một thuật toán lập trình đông có thể tách thành bốn bước cơ sơ sau:
1.Định rõ các đăc điểm cấu trúc của một giải pháp tối ưu.
2.Định nghĩa theo đệ quy giá trị của một giải pháp tối ưu.
3.Tính toán các giá trị của một giải pháp tôi ưu theo kiểu botton-up.
4.Kiến tạo một giải pháp tôi ưu từ thông tin đã tính toán.
Các bước 1,2,3 là cơ sở của một thuật toán lập trình động. Khi thực hiện bước 4 ta
thường duy trì một bảng thông tin bổ sung khi tính toán trong bước 3 để tạo thuận
lợi cho việc kiến tạo một giải pháp tôi ưu.
I.2 Các thành phần của một bài toán lập trình động:
Trong ngành khoa học máy tính, lập trình độ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).
I.2.1 Cấu trúc tối ưu:
Bước đầu tiên trong tiến trình giải quyết một bài toán tối ưu hóa bằng lập trình
động đó là định rõ các đặc điểm của cấu trúc của một giải pháp tối ưu. Ta nói rằng
một bài toán biểu lộ cấu trúc tôi ưu nếu một giải pháp tối ưu cho bài toán chứa
trong nó các giải pháp tối ưu cho các bài toán con. Mỗi khi một bài toán biểu lộ
cấu trúc tôi ưu nó gơi ý cho ta có thể giải bằng phương pháp lập trình động. Cấu
trúc tối ưu của một bài toán thường gợi ý cho ta một không gian của các bài toán
con. Mà ở đó có vài lớp bài toán là dễ giải. Một thuật toán lập trình đông dựa trên
không gian bài toán con này sẽ giải quyết thêm nhiều bài toán hơn so với mức bắt
buộc. Tiến trình tham tra cấu trúc tối ưu của một bài toán được lặp lại trên các bài
toán con là một cách tốt để suy ra một không gian các bài toán con thích hợp cho
phương pháp lập trình động.
I.2.2 Các bài toán con phủ chồng:
Thành tố thứ hai một bài toán tối ưu phải có để áp dụng kỹ thuật lập trình động
để giải đó là không gian các bài toán con phải “nhỏ “ theo nghĩa là một thuật toán
đệ quy cho bài toán sẽ giải quyết lặp lại các bài toán con tương tự, thay vì luôn
phát sinh bài toán con mới. Khi một thuật toán đệ quy thăm hoài cùng một bài
4
toán, ta nói rằng bài toán tối ưu hóa có các bài toán con phủ chồng. Ngược lại môt
bài toán thích hợp với cách tiếp cận chia để trị thường phát sinh các bài toán con
hoàn toàn mới. Các thuật toán lập trình động thường vận dụng các bài toán con phủ
chồng bằng cách giải quyết từng bài toán con một rồi lưu trữ giải pháp trong một
bảng có thể dùng để tra cứu khi cần, thời gian tra cứu là nhỏ hơn với viêc giải lại
bài toán đó.
I.2.3 Phép memo:
Có một biến thể của kỹ thuật lập trình động thường cung cấp hiêu năng của
cách tiếp cận lập trình động bình thường trong khi vẫn duy trì một chiến lược từ
trên xuống. Ý tưởng đó là phép “memo hóa” (memoize) thuật toán đệ quy tự nhiên
song không hiệu quả. Giống như trong lập trình động bình thường, ta duy trì một
bảng chứa các giải pháp của bài toán con, nhưng cấu trúc điều khiển lại để ghi vào
bảng lại giống với thuật toán đệ quy hơn.
Một thuật toán đệ quy memo duy trì một khoản nhập trong một bảng giải pháp
của mỗi bài toán con. Thoạt đầu, mỗi khoản nhập bảng chứa một giái trị đặc biệt
để nêu rõ khoản nhập phải được điền. Khi gặp bài toán con lần đầu tiên trong khi
thi hành thuật toán đệ quy, giải pháp của nó là tinh toán rồi lưu trữ vào trong bảng.
Mỗi lần gặp lại bài toán con đó, giá trị đã được lưu trong bảng đơn giản chỉ viêc tra
cứu và trả về.
Tóm lại: Nếu tất cả các bài toán con phải được giải quyết ít nhất một lần , một
thuật toán lập trình động từ dưới lên sẽ làm tốt hơn một thuật toán memo từ trên
xuống theo một thừa số bất biến, bởi không có phân viêc chung cho đệ quy và ít
phần việc chung hơn để duy trì bảng. Mặt khác nếu có một vài bài toán con trong
không gian bài toán con mà không cần giải quyết gì cả, giải pháp memo có ưu thế
hơn vì chỉ phải giải quyết những bài toán con nào được yêu cầu.
I.3 Các bước giải một bài toán lập trình động:
+Xác định công thức truy hồi của bài toán.
+Xác định cơ sở lập trình động đó là tập các bài toán con nhỏ nhất có thể thấy
ngay lời giải.
+ Lập bảng phương án đó là không gian lưu trữ lời giải của các bài toán con.
5
+Dựa vào bảng phương án ,truy vết tìm nghiệm tối ưu.
Cho đến nay vẫn chưa có một định lí nào cho biết chính xác những bài toán
nào thì có thể giải quyết bằng phương pháp lập trình động.Tuy nhiên để biết được
bài toán có thể giải bằng phương pháp lâp trình động hay không, ta có thể trả lời
các câu hỏi sau: “ Một nghiệm tối ưu của bài toán lớn có phải là sự phối hợp các
nghiệm tối ưu của các bài toán con hay không” và ” Liệu có thể lưu trữ được
nghiệm của bài toán con dưới hình thức nào để tìm cách phối hợp tìm được nghiệm
của bài toán lơn không?”.
I.4 Các thuật toán sử dụng lập trình động
• Nhiều thuật toán xử lý xâu ký tự, trong đó có bài toán dãy con chung lớn
nhất
• Thuật toán CYK xác định xem một xâu cho trước có thể được sinh từ một
ngữ pháp phi ngữ cảnh (context-free grammar) như thế nào.
• The use of transposition tables and refutation tables in computer chess
• Thuật toán Viterbi
• Thuật toán Earley
• Thuật toán Needleman-Wunsch và các thuật toán sắp chuỗi (sequence
alignment) khác dùng trong Tin sinh học
• Levenshtein distance (edit distance)
• Thuật toán Bellman-Ford
• Thuật toán Dijkstra : thuật toán tìm đường đi ngắn nhất giữa hai đỉnh
• Thuật toán Floyd : tìm đường đi ngắn nhất giữa mọi cặp đỉnh
• Tối ưu hóa thứ tự của phép nhân ma trận theo chuỗi (chain matrix
multiplication)
• Thuật toán tổng tập con (subset sum)
• Bài toán xếp ba lô (knapsack problem)
II.Bài toán tổng tập con(subset sum):
Phát biểu của bài toán:
Cho một tập X các số nguyên dương và một đích T .Hỏi có tồn tại một tập con
của X mà có tổng là T hay không. Ví dụ nếu X={ 1, 4 ,5 ,7 ,2 ,11 ,13} và T=18 thì
tập con X’={ 1, 4, 2, 11 } là một tập con thỏa mãn.
6
Giải quyết bài toán:
Dữ liệu vào: Là một mảng số nguyên dương X[1 n] và số nguyên T;
Dữ liệu ra: Có tồn tại một tâp con của X mà có tổng là T hay không?
Giải:
1Tìm công thức truy hồi của bài toán:
Ta gọi hàm S(i,t ) là một hàm cho ta biết có tồn tại hay không một tập con của
tập X[i n] mà có tổng là t?
Nếu không chọn X[i] để thêm vào tập con của tập X[i+1 n] để có tổng là t thì
ta có: S(i,t)=S(i+1,t);
Nếu mà chọn X[i] thêm vào tập con của tập X[i+1 n] để được tập có tổng là t
thì hiên nhiển nhiên trước khi thêm phần tử X[i] thì những tập con mà thỏa mãn để
thêm X[i] phải có đích là t-X[i] do dó ta có:
S(i ,t)=S(i+1,t-X[i]);
2.Cơ sở lập trình động:
Để dễ dàng tính toán ta đặt S( i,t)=True, kỹ thuật này gọi là kỹ thuật lính cầm
canh.
Ta cũng dễ dàng nhận thấy S( i ,t)=False nếu t <0 và i>n.
3.Điền bảng phương án :
Công thức truy hồi để tinh S( 1,t):
TRUE
FALSE
S(i + 1,t) V S(i + 1,t - X [i] )
if t = 0
if t < 0 or i >n,
otherwise
S(i,j) =
7
Bảng phương án gồm có T+1 cột và n+1 hàng trước tiên ta sẽ điền cơ sở quy hoạch
điền dòng thứ n+1 trước sau đó điền các dòng tiếp theo rồi cuối cùng mới điền
dòng 1.
0 1 ……. ……. T
1
2
….
……
…
n +1
4.Truy vết:
Dựa vào bảng phương án tan ta có :
+Nếu S(1,T)=False tức là không tồn tại tập con của tập X đã cho mà có tổng là T
kết thúc bài toán.
+Nếu S(1,T)=True tức là tồn tại tập con của X mà có tổng là T vậy đó là nhưng tập
con nào cho một vi dụ về tập con do. Như đã phân tích ở trên nếu X[i] đươc thêm
vào thì S(i.t)=S(i+1,t-X[i]) thì song song với việc duy tri một bảng phương án
S[n+1][T] ta cũng duy trì một bảng B[n+1][T] để ghi nhân lại các vị trí của phần tử
X[i] được thêm vào cụ thể: Nếu S(i,t)=S(i+1,t-X[i]) thì B[i][t]=i;
Sau đó dựa vào bảng B ta bắt đầu lần ngược lại từ ô B[1][T] cho tới khi nào t=0
thì ta thu được một phương án.
5. thuật toán:
Dựa vào công thức truy hồi của bài toán ta thấy bài toán co nT tham số đầu vào
và cần O(mn) không gian nhớ để lưu trữ các bài toán con.Nếu biết được S[i+1,t] và
S[i+1,t-X[i]] thì ta có thể tính được S[i,t] Đó cũng là gợi ý cho ta một thuật toán
memo hóa đệ quy như dưới dây:
8
Subsetsum(X[1 n],T)
S[n+1,0]=TRUE
For t=1 to T
S[n+1,t]=FALSE
For i=n to 1
S[i,t]=TRUE
For t=1 to X[i]-1
S[i,t]=S[i+1,t] // tránh t<0
For t=X[i] to T
S[i,t]=S[i+1,t] v S[i+1,t-X[i]]
Return S[1,T]
Thuật toán này luôn luôn sử dụng O(nT) không gian nhớ và chạy với thời gian
O(nT). Tuy nhiên với T>2^n thì thuật toán tỏ ra không hiệu quả và chạy chậm.
Ta có một vài cải tiến của thuật toán trên để giảm không gian sử dụng bộ nhớ do
phải duy tri một bảng 2 chiều.Bằng cách ta sử dụng một bảng một chiều S(t) lưu
trư câu trả lời có tồn tại hay không một tâp con của tập X[1 j-1 ] và phần tử X[j]
để có tổng là t. Do dó S(t) còn lưu trữ luôn vị trí của phần tử được thêm để tạo
thuận lợi cho quá trình kiến tạo một giải pháp sau này. Như vậy không gian nhớ
của bài toán là O(T).
Subsetsum2(X[1 n],T)
X[0]=0
for i=0 to T
S[i]=(-1) // khởi tạo đánh dấu nhưng vi trí chưa tính
S[0]=0
9
for i=0 to T-1
if S[i]<>1
for j=A[i]+1 to n
{
k=i+S[j];
if (k>T)
Continue;
if (S[k]==(-1) || S[k]>j )
S[k]=j;
}
Return S[T]
6.cài đặt:
Sử dụng cấp phát động để tăng khả năng sử dụng hiệu quả bộ nhớ và có thể
tính với các số lớn:
Chương trình gồm:
+nhập bàn phím: cho phép người dung có thể nhập dữ liệu từ bàn phím;
+nhập ngâu nhiên.
+nhâp từ file.
10
+sửa dữ liệu từ bàn phím hoặc từ file.
+chọn thuật toán thực hiên
+lưu file dữ lieu và file kết quả
+menu lựa chọn.
Chương trình cho phép thông kê thời gian tính của từng thuật toán đã bao gồm cả
thời gian kiến tạo một giải pháp tối ưu.
Giao diện của chương trình:
7.kết luận
Trong khoa học máy tính bài toán SUBSET SUM là một vấn đề quan trọng trong
vấn đề lý thuyết độ phức tạp và mã hóa. Nó cũng là một bài toán thuộc lớp NP và
là trường hợp riêng của vấn đề knapsack.
III. Bài toán dãy con tăng đơn điệu dài nhất(longest increasing sequence):
11
Bài toán: Cho một dãy số nguyên A[1 n] hãy tìm dãy con tăng đơn điệu lớn nhất
của dãy A.
Giải:
+Trước hết ta bổ sung phần tử A[0]=VCB để chắc chắn rằng dãy con tăng đơn điệu
bắt đầu từ A[0].
+ L[i,j] là độ dài của dãy con tăng đơn điệu dài nhất bắt đầu từ A[i]
1.công thức truy hồi:
Giải sử sét i chạy từ n về 0. Để L[i,j] là độ dài của dãy con tăng đơn điệu dài nhất
bắt đầu từ A[i] thì L[i,j+1] và L[j,j+1];
Dãy con tăng đơn điệu dài nhất bắt đầu từ A[i] được thành lập như sau: Thêm A[i]
vào đầu dãy một trong số nhưng dãy con tăng đơn điệu dài nhất bắt đậu tại vị trí
A[j] đứng sau A[i]. Ta sẽ chọn dãy nào để thêm A[i] vào đây? Tất nhiên chỉ được
ghép A[i] vào những dãy con bắt đầu tại A[j] lớn hơn A[i] mà thôi (để đảm bảo
tính tăng) và dĩ nhiên chỉ thêm vào những dãy con dài nhất để ghép A[i] vào đầu
(để đảm bảo tính dài nhất):
Ta có công thức truy toán sau:
0
L(i,j + 1)
max{L(i,j + 1), 1 + L( j,j + 1)
if j > n
if A[i] >= A[j]
otherwise
L(i,j) =
2.điền bảng:
Để điền L[i,j] ta phải biết các ô L[i,j+1] và L[j,j+1] như thế ta se phải điển từ
trái sang phải bắt đầu từ ô thứ L[0,n+1] với cơ sở lập trình đông là L[i,n+1]=0 với i
chạy từ 0 đến n.
1 2 … … N+1
0
1
2
12
…
…
N
3.truy vết phần tử:
Tại bước xây dựng L[i,j] ta duy trì thêm một bảng B[i][j] để ghi nhận lại vị trí của
phần tử A[j] đứng trước A[i] cụ thể là: nếu L[i][j]=1+L[j][j+1] thì B[i][j]=j; sau đó
từ bảng B này bắt đầu từ ô B[0,1] ta lần xuống phái dưới cho tới khi nào hết bảng
thi sẽ cho ta giải pháp của bài toán
4.thuật toán:
Từ công thức truy hồi và những phân tích trên ta có thuật toán:
LIS( A[1 n]):
A[0]=VCB
For i=0 to n
L[i,n+1]=0; //điền cơ sở lập trình động
For j=n downto 1
For i=0 to j-1
If A[i]>=A[j]
L[i,j]=L[i,j+1]
Else
L[i,j]= max{L[i,j+1],1+L[j,j+1]}
Return L[0]
13
Thuật toán trên sử dung O(n^2) không gian nhớ và thời gian tính cũng là O(n^2)
Ta có thể có bằng sử dụng mảng hai chiều để điển bảng phương án bằng cách
sử dụng một bảng 1 chiều để lưu kết quả tính và khi dó không gian nhớ của thuật
toán la O(n); Như phân tich ở trên ta có công thức truy hồi để tinh L’[i] với L’[i] là
độ dài của dãy con tăng đơn điệu dài nhất bắt đầu từ A[i]:
L’[i]=1+max{L’[j] | j>I and A[j] > A[i]};
Để kiến tạo một phương án: tại bước xây dựng L’ ta duy trì thêm một bảng C để
lưu lại vị trí của phần tử A[j] đứng trước A[i]. Sauk hi tinh xong L va C ta bắt đầu
từ C[0]
+C[0] chính là phần tử đầu tiên được chọn;
+C[C[0]] là phần tử thứ 2;
+C[C[C[0]]] là phần tử thứ 3…
Thuật toán cải tiến1:
LIS2(A[1 n])
A[0]=VCB
For i=n downto 0
L’[i]=1
For j=i+1 to n
If A[j]>A[i] and 1+L’[j]>L’[i]
L’[i] =1+L’[j]
Return L[0]-1
Nhận xét thuật toán trên sử dụng không gian nhớ là O(n) nhưng thời gian chạy
vẫn là O(n^2)
Ta còn có thể cải tiến thuật toán trên với thời gian tinh là O(nlogn ) bằng cách sử
dung một kỹ thuật sau:
14
Trước tiên thêm vào dãy đã cho hai phần tử A[0]=VCB, và A[n+1]=VCL để
dảm bảo dãy con tăng đơn điệu dài nhất của ta bắt đầu từ A[0] và kết thúc tại
A[n+1], và ta có cơ sơ lập trình động là L[n+1]=1
Với mỗi số k, ta gọi C[k] là chỉ số i của phần tử A[i] thỏa mãn: dãy đơn điệu tăng
dài nhất bắt đầu từ phần tử A[i] có độ dài k. Nếu có nhiều phần tử A[.] cùng thỏa
mãn điều kiên này thì ta chọn phần tử A[i] lớn nhất trong số những phần tử đó.
Việc tính C[.] được thực hiện đồng thời với việc tính L[.].
Khi bắt đầu mỗi lần lặp với mỗi giá tri i ta đã biết được:
+m: Độ dài dãy con đơn điệu dài nhất của dãy A[i+1…n+1]
+C[k]( 1=< k <= n): Phần tử A[C[k]] là phần tử lớn nhất trong số các phần tử
thỏa mãn dãy con tăng đơn điệu bắt đầu từ A[C[k]] mà có độ dài là k suy ra ta có
A[C[k]]<A[C[k-1]]<…<A[C[1]]
+Điều kiện để có dãy con đơn điệu tăng độ dài là p+1 bắt đầu tai A[i] chính là
A[C[p]]>A[i](vì theo thứ tự tính toán thì mỗi khi bắt đầu một lần lặp với giá trị I
A[C[p]] luôn đứng sau A[i]). Mặt khác nếu đem A[i] ghép vào đầu dãy con tăng
đơn điệu dài nhất bắt đầu tại A[C[p]] mà thu được dãy tăng thì đêm A[i] ghép vào
đầu dãy con tăng đơn điệu dài nhất bắt dầu tại A[C[p-1] ta cũng thu được dãy
tăng.Vậy để tính L[i] ta có thể tìm số p lớn nhất thỏa mãn A[C[p]]>A[i] bằng thuật
toán tìm kiếm nhị phân rồi đặt L[i]=p+1; và muốn truy vết ta cũng sử dụng một
mảng T và tất nhiên T[i]=C[p];
Thuât toán:
LIS3(A[1 n]):
A[0]=VCB // khởi tạo ban đầu
A[n+1]=VCL
m=1
L[n+1]=1
C[1]=n+1
For i=n downto
j=find(i)
k=L[j]+1
if k>m
{
15
m=k // cập nhât lại chiều dài
C[k]=I
}
Else if (A[C[k]]< A[i]) // nếu có nhiều dãy đơn điêu có độ k
C[k]=i //thì chỉ ghi nhận lại dãy có phần tử bắt
đầu lớn nhất
L[i]=k
Return L[0]-2 // không tính hai phần tử cầm canh
Find(i)
lower=1
upper= m+1
While (lower+1<upper) // tìm kiếm nhị phân
{
mid=(lower+upper)/2;
j=C[mid]
If A[j] > A[i]
lower=mid // luôn để A[C[lower]]>A[i]>=A[C[lpper]]
Else upper= mid
}
Return C[lower]
Ta thấy thuật toán tìm kiếm nhị phân chạy mất O(logn) do dó thuật toán trên
chạy mất O(nlogn) và không gian bộ nhớ là O(2n) do phải duy trì thêm một mảng
C[k].
5.Cài đăt và sử dụng chương trình:
Chương trình gồm :
+nhập từ bàn phím:
+nhập dữ liệu từ file;
+nhập ngẫu nhiên;
16
+lưu file dữ liệu và file kết quả;
+chon thuật toán muốn thực hiện
+menu chương trình
Chương trình có cả thống kê thời gian tính của từng thuật toán đã gồm cả thời gian
kiến tạo một giải pháp.
Giao diện của chương trình:
Một số các chức năng:
17
18
6.kết luận:
Longest increasing sequence dược nghiên cứu rất nhiều trong các lĩnh vực như là
algrothmics, lý thuyết ngẫu nhiên ma trận lý thuyết đại diện và vật lý.
IV.Thống kê thời gian tính toán của các thuật toán:
19
Thời gian tính của thuật SUBSETSUM1và thuật toán SUBSETSUM2 theo lý
thuyết là O(n*T),
Thời gian tính của thuật toán LIS1 và thuật toán LIS2 là O(n^2) còn thuật toán
LIS3 là O(nlogn).
Bảng thống kê:
Thuật toán n T time n T time n T time
Subsetsum1 10^3 10^3 34 10^3 10^4 249k 10^4 10^4 44179
Subsetsum2 10^3 10^3 0 10^3 10^4 141 10^4 10^4 1077
Subsetsum2 10^4 10^5 11186 10^5 10^6 10806
2
10^5 10^6 1074909
Thuật
toán
N=10^3 time N=10^4 time N=10^
5
time N=10^
6
time
LIS1 62 7161 Ko đủ
bộ nhớ
LIS2 46 686 48756 ??
LIS3 31 109 421 733
V.KẾT LUẬN:
Hai bài toán Subset Sumvà Longest Increasing Sequence là hài vấn đề quan
trọng của toán học và thuật toán cũng như các ngành khác như nghiên cứu lý
thuyết phức tạp lý thuyết đại diện mã hóa vật lý và lĩnh vưc tin sinh học.Bài toán
Lis là một vấn đề đặc biệt của bài toán LCS nổi tiếng được nghiên cứu rất nhiều
trong nghiên cứu cấu trúc gen.Bài toán Subset sum có vai trò rất quan trọng trong
lý mã hóa và lĩnh vực đóng gói trong sản xuất.
Qua đợt thực tập này em lại có thêm một phương pháp giải quyết bài toán trong tin
học đó là phương pháp lập trình động một phương pháp thiết kế thuật toán hiên
đại và hiệu quả.Và hơn thế nữa chúng ta thấy được vai trò của việc nghiên cứu
thiết kế các thuật toán quan trong như thế nào trong một sản phẩm phần mền và
trong các lĩnh vực cuộc sống.
20
Cuối cùng em xin chân thành cảm ơn PGS.TS NGUYỄN ĐỨC NGHĨA đã tận tình
hướng dẫn em hoàn thành đợt thực tập này.Em cũng xin chân thanh cảm ơn những
người bạn trong lớp đã giup đỡ trong quá trình làm đề tài nay!
Hà nội, ngày 1tháng 6 năm 2009
Chương trình nguồn được code trên môi trường DEV C++ 4.9.9.2
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#define enter 13
#define TRUE 1
#define FALSE 0
int *x;//dung de truy vet ket qua tinh cho phuong an cai dat1;
int T;
int n,okv,b1v,n1v,p1v,kt;
int subsetsum1(int x[],int T,int n);
int subsetsum2(int x[],int T,int n);
// //
void banphim()
{
int i;
printf("\n Nhap du lieu tu ban phim:\n");
printf("\n Nhap so phan tu: ");
scanf("%d",&n);
printf("\n Nhap tong can tim: ");
scanf("%d",&T);
x=(int*)malloc((n+1)*sizeof(int));
if(x==NULL)
21
{
printf("\n khong du bo nho!!! ");
exit(0);
};
printf("\n Nhap cac phan tu cua day so: ");
for (i=1;i<=n;i++)
{
printf("\n x[%d]=",i);
scanf("%d",&x[i]);
};
okv=1;
b1v=1;
}
// //
void ngaunhien()
{
int i;
printf("\n Nhap du lieu ngau nhien:\n");
printf("\n Nhap so phan tu: ");
scanf("%d",&n);
printf("\n Nhap tong can tim: ");
scanf("%d",&T);
x=(int*)malloc((n+1)*sizeof(int));
if(x==NULL)
{
printf("\n khong du bo nho!!! ");
exit(0);
};
srand(200);
for (i=1;i<=n;i++)
x[i]=rand();
for (i=1;i<=n;i++)
printf("\n x[%d]=%3d",i,x[i]);
okv=1;
n1v=1;
}
// //
void hiendl()
22
{
int i;
if (!okv)
{
printf("\n Du lieu chua nhap!!!!!!");
getch();
exit (1);
}
printf("\n so phan tu n= %d",n);
printf("\n tong can tinh la T= %d",T);
for (i=1;i<=n;i++)
printf("\n a[%d]=%3d",i,x[i]);
}
//
void luufile()
{
int t,i;
FILE *f;
char namef[20];
if (!okv)
{
printf("\n Du lieu chua duoc nhap!!!");
getch();
exit (1);
}
printf("\n Doc ten file luu du lieu:");
scanf("%s",&namef);
f=fopen(namef,"wb");
if(f==NULL)
{
printf("loi mo file!!!");
getch();
exit(0);
};
fprintf(f,"%d \n",n);
fprintf(f,"%d \n",T);
for (i=1;i<=n;i++)
{
fprintf(f,"%d ",x[i]);
fprintf(f,"\n");
};
fclose(f);
}
//
23
void docfile()
{
int i;
FILE *f;
char namef[20];
printf("\n Nhap ten file can doc:");
scanf("%s",namef);
f=fopen(namef,"rb");
if (f==NULL)
{
printf("\n file %s khong ton tai!!!",namef);
getch();
exit(1);
}
fscanf(f,"%d \n",&n);
fscanf(f,"%d \n",&T);
printf("\n n= %d",n);
printf("\n T= %d",T);
x=(int*)malloc((n+1)*sizeof(int));
if(x==NULL)
{
printf("khong du bo nho!");
getch();
exit(0);
};
for(i=1;i<=n;i++)
{
fscanf(f,"%d \n",&x[i]);
fscanf(f,"\n");
}
for(i=1;i<=n;i++)
printf("\n x[%d]=%d ",i,x[i]);
fclose(f);
okv=1;
p1v=1;
}
//
void suabp()
{
int i,tg;
char ok1;
24
lap_lai:
printf("\n Ban co muon sua tong T hok?(c/k)");
ok1=getch();
printf("\n tra loi la:",ok1);
if(ok1=='c' || ok1=='C')
{
printf("\n tong duoc sua lai T=");
scanf("%d",&tg);
T=tg;
}
printf("/n ban co muon sua so phan tu n hok?(c/k)");
ok1=getch();
printf("\n tra loi la:",ok1);
if(ok1=='c' || ok1=='C')
{
printf("chu y n phai nho hon n ban dau");// do cap phat dong
printf("\n so phan tu duoc sua lai n=");
scanf("%d",&tg);
n=tg;
}
printf(" ban co muon sua a[i] hok?(c/k)");
ok1=getch();
printf("\n tra loi la:",ok1);
if(ok1=='c' || ok1=='C')
{
printf("\n ban cho biet vi tri can sua?" );
printf("\n i=");
scanf("%d",&i);
printf("\n gia tri moi la a[%d]=",i);
scanf("%d",&tg);
x[i]=tg;
}
printf("\n ban co muon thay doi lai hok?(c/k)");
ok1=getch();
printf("\n cau tra loi cua ban la:",ok1);
if(ok1=='c' || ok1=='C')
goto lap_lai;
}
//
25