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

Tìm xâu con chung dài nhất của hai xâu

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 (243.63 KB, 15 trang )

HỌC VIỆN KỸ THUẬT QUÂN SỰ
KHOA CÔNG NGHỆ THÔNG TIN

BÁO CÁO MÔN HỌC

PHÂN TÍCH VÀ THIẾT KẾ GIẢI THUẬT
Đề 21:

Bài toán xâu con chung dài nhất và thuật toán LCS
(Longest Common Subsequence

Giáo viên hướng dẫn: Hà Đại Dương
Sinh viên thực hiện: Nguyễn Thị Ngọc Hà
Lớp: HTTT14

Hà Nội, 1/2018


MỤC LỤC

I. GIỚI THIỆU CHUNG VỀ PHƯƠNG PHÁP QUY HOẠCH ĐỘNG..........3
1.

Ý tưởng....................................................................................................3

2.

Mô hình...................................................................................................3

II. THUẬT TOÁN LCS ( Longest Common Subsequence).................................4
1.



Bài toán...................................................................................................4

2.

Mô tả chi tiết thuật toán........................................................................4

3.

Đánh giá độ phức tạp thuật toán..........................................................6

4. Tự xác định 2 bộ dữ liệu (với số phần tử N>=5), với mỗi bộ dữ liệu
thực hiện từng thuật toán đã mô tả ở mục 2 và ghi ra kết quả mỗi bước. 7
5.

Viết chương trình sử dụng C, C++.......................................................9


I.

GIỚI THIỆU CHUNG VỀ PHƯƠNG PHÁP QUY HOẠCH ĐỘNG
1. Ý tưởng
Qui hoạch động (DP – Dynamic Programming), một thuật ngữ được nhà
toán học Rechard Bellman đưa ra vào năm 1957, là một phương pháp
giải bài toán bằng cách kết hợp các lời giải cho các bài toán con của nó
giống như phương pháp chia để trị (devide-and-conquer).
Các bài thuật toán chia để trị để phân hoạch bài toán cần giải quyết thành
các bài toán con độc lập với nhau, sau đó giải quyết bằng phương pháp
đệ qui (recursive) và kết hợp các lời giải lại để được lời giải của bài toán
ban đầu.

Ngược lại qui hoạch động là phương pháp được áp dụng khi mà các bài
toán con của bài toán ban đầu (bài toán gốc) là không độc lập với nhau,
chúng có chung các bài toán nhỏ hơn. Trong các trường hợp như vậy một
thuật toán chia để trị sẽ thực hiện nhiều việc hơn những gì thực sự cần
thiết, nó sẽ lặp lại việc giải quyết các bài toán con nhỏ hơn đó. Một thuật
toán qui hoạch động sẽ chỉ giải quyết các bài toán con nhỏ một lần duy
nhất sau đó lưu kết quả vào một bảng và điều này giúp nó tránh không
phải tính toán lại các kết quả mỗi khi gặp một bài toán nhỏ nào đó.
Các bài toán qui hoạch động thường được áp dụng trong các bài toán tối
ưu. Trong các bài toán tối ưu đó thường có nhiều nghiệm (lời giải). Mỗi
lời giải của một giá trị được lượng giá bằng cách sử dụng một hàm đánh
giá tùy thuộc vào các bài toán cụ thể và yêu cầu của bài toán là tìm ra
một nghiệm có giá trị của hàm đánh giá tối ưu (lớn nhất hoặc nhỏ nhất).
Qui hoạch động là một phương pháp chung rất hiệu quả để giải quyết các
vấn đề tối ưu chẳng hạn như trên các đối tượng sắp thứ tự từ trái qua
phải, vấn đề tìm đường đi ngắn nhất, vấn đề điều khiển tối ưu… Khi đã
hiểu rõ về qui hoạch động thì việc ứng dụng vào giải các bài toán tối ưu
không phải là quá khó khăn nhưng rất nhiều lập trình viên ban đầu phải
mất rất nhiều thời gian mới có thể hiểu được.
2. Mô hình
Quá trình phát triển của một thuật toán qui hoạch động có thể chia làm 4
bước như sau:
- Bước 1: Xác định đặc điểm cấu trúc của giải pháp tối ưu của bài toán
- Bước 2: Tìm công thức truy hồi (đệ qui) xác định giá trị của một giải
pháp tối ưu.
- Bước 3: Tính giá trị tối ưu của bài toán dựa vào các giá trị tối ưu của
các bài toán con của nó (bottom-up).
- Bước 4: Xây dựng nghiệm đạt giá trị tối ưu từ các thông tin đã tính.



Các bước 1-3 là các bước cơ bản trong việc giải quyết bất cứ bài toán tối
ưu nào bằng phương pháp qui hoạch động. Bước 4 có thể bỏ qua nếu như
bài toán chỉ yêu cầu tìm ra giá trị tối ưu chứ không cần chỉ ra nghiệm cụ
thể. Thông thường 2 bước đầu là quan trọng và cũng là khó khăn hơn cả,
việc xác định cấu trúc nghiệm cũng như công thức truy hồi cần dựa vào
kinh nghiệm và sự quan sát các trường hợp cụ thể của bài toán. Do vậy
trong quá trình xây dựng thuật toán qui hoạch động cho các bài toán tối
ưu chúng ta cần khảo sát các bộ giá trị thực tế của bài toán, giá trị tối ưu
và nghiệm của bài toán ứng với các bộ giá trị đó.
II.
THUẬT TOÁN LCS ( Longest Common Subsequence)
1. Bài toán
Cho hai xâu A và xâu B. Tìm xâu con chung dài nhất của hai xâu A và
xâu B.
Ứng dụng: Nó giải quyết rất nhiều bài toán trong thực tế nhằm đưa ra
mức độ tương đương giữa hai xâu.
2. Mô tả chi tiết thuật toán
Cho hai xâu ký tự: A = (a1, a2… am),
B = (b1, b2, …, bn) và
P = (p1, p2… pn) là xâu co chung dài nhất của A và B.
Áp dụng nguyên lý quy hoạch động ta có thể giải quyết bài toán LCS.
Gọi
T [i, j] là độ dài xâu con chung dài nhất của hai xâu A [1… i]i m và
B[1,…,j] j n , điều này có nghĩa là T[i,j] là độ dài xâu con chung dài nhất
của i ký tự đầu của xâu A và j ký tự đầu của xâu B. Do đó T [m, n] chính
là độ dài xâu con chung dài nhất của A và B.
a). Định lý:
Cho hai xâu ký tự: A = (a1, a2…, am),
B = ( b1, b2, …, bn) và
P = (p1, p2… pn) là xâu con chung dài nhất của A và B.

- Nếu am = bn thì pk = am = bn và Pk-1 là LCS của Am-1 và Bn-1.
- Nếu am bn và pk am thì P là LCS của Am-1 và B.
- Nếu am bn và pk bn thì P là LCS của A và Bn-1.
Chứng minh:
- Nếu pk am thì chúng ta có thể thêm am = bn vào P chứa xâu con chung
của A và B sẽ có độ dài là k+1, mâu thuẫn với giả thiết rằng P là LCS


của A và B, như vậy chúng ta phải có p k = am = bn. Bây giờ tiền tố P k-1
có độ dài k-1 là xâu chung của A m-1 và Bn-1. Chúng ta chứng minh nó
là một LCS, giả sử có một xâu chung W kết quả là W có độ dài lớn
hơn k, đó là mâu thuẫn.
- Nếu pk am, thì P là xâu chung của Am-1 và B. Nếu tồn tại một xâu
chung W của Am-1 và B với chiều dài lớn hơn k, khi đó W có thể là
xâu chung của Am-1 và B, trái với giả thiết rằng P là LCS của A và B.
- Chứng minh tương tự như trên với pk bn
b). Công thức truy hồi cho LCS:
Qua định lý trên ta có thể rút ra một số vấn đề:
- Trường hợp i=0 hoặc j = 0 thì độ dài xâu con chung dài nhất của A và
B là 0, nghĩa là T [0, j] = T [i, 0] = 0.
- Trường hợp xét ai = bj thì ta thêm pk = am = bn vào P và độ dài xâu con
chung dài nhất T[i, j] sẽ bằng độ dài xâu con chung dài nhất của A i-1
và Bj-1 là T[i-1, j-1] cộng thêm 1.
- Ngược lại nếu ai bj ta phải tìm LCS của Ai-1 và B, tìm LCS của A và
Bj-1 là T [i-1, j] và T [i, j-1]. Xâu nào lớn hơn sẽ là LCS của A và B.
Ta có : T[i, j] =
Giải thuật 1 :
1) Input (A[1,…, m], B[1,…, n])
2) For i = 0 to m T [i, 0] =0;
For j = 0 to n T [0, j] = 0;



3) For i = 1 to m
For j = 1 to n
If A[i] = B[j] then T [i, j] = T [i-1, j-1] +1
Else T [i, j] = max {T [i-1, j], T [i, j-1] }
4) Output ( T[m,n])
c). Truy vết tìm xâu con chung dài nhất
Ta dựa vào bảng phương án để truy vết tìm xâu
chung. Xuất phát từ ô T[m,n], giả sử đang đứng tại ô C[i,j]
ta sẽ xét 2 ô trước đó là ô C[i-1,j], C[i,j-1], nếu một trong
hai ô có giá trị bằng ô T[i,j] thì ta sẽ lùi về ô đó cho tới khi
hai ô trước có giá trị nhỏ hơn ô đang đứng C[i’,j’], Khi đó
X[i’] = Y[j’]. Kết nạp A[i’] vào chuỗi P. Lùi về ô T[i’-1,j’-1] và
tiếp tục lặp lại cho tới T[0,0].
Giải thuật 2
1). P := ‘’;
m := length(A); n := length(B);A;B; T[m,n]
2). While (n>0) and (m>0) do
begin
a). if (A[m-1]=B[n-1])
begin
P := A[m] +P;
m := m -1 ;
n := n-1 ;
b). else if (T[m-1,n] >= T[m,n-1]) then m:=m-1;
else then n:=n-1;
end ;
3). Output(P);


3. Đánh giá độ phức tạp thuật toán
a) Quy hoạch động cho 2 xâu : Khảo sát độ phức tạp trên số phép gán
Input (A [1… m], B [1… n])


For i = 0 to m T [i, 0] =0;
For j = 0 to n T [0, j] = 0;
For i = 1 to m
For j = 1 to n

Phép gán (m)
Phép gán (n)
Phép gán (m)
Phép gán (m.n)

If A[i] = B[j] then T [i, j] = T [i-1, j-1] +1
Else T [i, j] = max {T [i-1, j], T [i, j-1] }

Số phép gán
(m.n)

Output (T [m, n])
 Tổng số phép gán = m+ n+ m+ m.n+ m.n = 2m.n + 2m + n =
O(mn)
b) Truy vết tìm xâu con chung dài nhất:
1). P := ‘’;
m := length(A); n := length(B);A;B; T[m,n]
2). While (n>0) and (m>0) do
begin
a). if (A[m-1]=B[n-1])

begin
P := A[m] +P;
m := m -1 ;
n := n-1 ;
b). else if (T[m-1,n] >= T[m,n-1]) then m:=m-1;
else then n:=n-1;
end ;
3). Output(P);
Số phép so sánh của vòng lặp while là min(m,n)
Số phép gán trong vòng lặp m+n
Thuật toán có độ phức tạp là
O((m+n)*min(m,n))=O(m+n).


4. Tự xác định 2 bộ dữ liệu (với số phần tử N>=5), với mỗi bộ dữ liệu
thực hiện từng thuật toán đã mô tả ở mục 2 và ghi ra kết quả mỗi
bước
a). Bộ dữ liệu 1
Hai chuỗi: A= “AABEF”
B = “ EABCEGF”
Công thức truy hồi cho LCS:
B[j]

E

A

B

C


E

G

F

0

0 +1

0

0

0

0

0

0

A

0

0+1

1


1

1

1

1

1

A

0

0

1+1

1

1

1

1

1

B


0+1

0

1

2

2+1

2

2

2

E

0

1

1

2

2

3


3+1

3

F

0

1

1

2

2

3

3

4

A[i]

Bảng phương án tìm độ dài xâu con chung dài nhất

Truy vết tìm xâu con chung dài nhất



B[j]

E

A

B

C

E

G

F

0

0

0

0

0

0

0


0

A

0

0

1

1

1

1

1

1

A

0

0

1

1


1

1

1

1

B

0

0

1

2

2

2

2

2

E

0


1

1

2

2

3

3

3

F

0

1

1

2

2

3

3


4

A[i]

Bảng truy vết tìm xâu chung dài nhất
 Xâu con chung dài nhất cần tìm là “ ABEF”
b). Bộ

dữ liệu 2:
Hai chuỗi :

A= ”123457”
B =”13545”

Công thức truy hồi cho LCS:


B[j]

1

3

5

4

5

0+1


0

0

0

0

0

1

0

1

1

1

1

1

2

0

1+1


1

1

1

1

3

0

1

2

2+1

2

2

4

0

1

2+1


2

3+1

3

5

0

1

2

3

3

4

7

0

1

2

3


3

4

A[i]

Bảng phương án tìm độ dài xâu con chung dài nhất
Truy vết xâu con chung dài nhất
1

3

5

4

5

0

0

0

0

0

0


0

1

1

1

1

1

0

1

1

1

1

1

0

1

2


2

2

2


0

1

2

2

3

3

0

1

2

3

3


4

0

1

2

3

3

4

Bảng truy vết tìm xâu chung dài nhất
 Xâu con chung lớn nhất là :”1345”

5. Viết chương trình sử dụng C, C++
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<string.h>
#include<cstring>
#include<stdio.h>
#define MAX 101
using namespace std;
void ini(string X ,string Y , int L[MAX][MAX], int n , int m)
{
int i,j;
for (i=0;i

{
L[i][0]=0;
}
for (i=0;i{


L[0][i]=0;
}
}
int LCS(string X ,string Y , int L[MAX][MAX], int n , int m )
{
int i,j;
for ( i=1;i{
for(j=1;j{
if(X[i-1]==Y[j-1])
{
L[i][j]=L[i-1][j-1]+1;
}
else
{
if(L[i-1][j]>=L[i][j-1])
{
L[i][j]=L[i-1][j];
}
else
{
L[i][j]=L[i][j-1];

}
/*L[i][j]=Math.max(L[i-1][j],L[i][j-1]);*/
}
}
}
return L[n-1][m-1];
}
string findPath(string X ,string Y ,int L[][MAX],int n ,int m)
{


string word;
while(n>0 && m>0)
{
if(X[n-1]==Y[m-1])
{
word += X[n-1];
n--;
m--;
}
else
{
if(L[n-1][m]>= L[n][m-1])
{
n--;
}
else
{
m--;
}

}
}
return word;
}
int main()
{
string X,Y;
int L[MAX][MAX];
cin >> X>>Y;
int n = X.length() + 1;
int m = Y.length() + 1;
L[n][m];


ini(X,Y,L,n,m);
int sol = LCS(X,Y,L,n,m);
for (int i=0; i{
for (int j =0; j{
cout<}
cout << endl;
}
cout <<"LCS is " << sol << endl;
string path = findPath(X,Y,L,n-1,m-1);
reverse(path.begin(),path.end());
cout<< "Word is : " << path << endl;
}
Kết quả với 2 bộ dữ liệu:



KẾT LUẬN
Trong bài báo cáo này đã phân tích bài toán, sử dụng các giải pháp để giải
quyết bài toán trong trường hợp của k xâu ký tự.



×