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

Đồ Án Cấu Trúc Dữ Liệu Và Giải Thuật: ỨNG DỤNG CHIẾN LƯỢC QUY HOẠCH ĐỘNG ĐỂ TÌM DÃY CON CHUNG LỚN NHẤT

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 (180.74 KB, 19 trang )

TRƯỜNG ĐẠI HỌC ĐIỆN LỰC
KHOA CÔNG NGHỆ THÔNG TIN

BÁO CÁO CHUYÊN ĐỀ
CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT NÂNG CAO

ĐỀ TÀI
ỨNG DỤNG CHIẾN LƯỢC QUY HOẠCH ĐỘNG ĐỂ TÌM DÃY
CON CHUNG LỚN NHẤT

Họ và tên

: NGUYỄN HỒNG KỲ

Giảng viên hướng dẫn

: VŨ VĂN ĐỊNH

Ngành

: CÔNG NGHỆ THÔNG TIN

Chuyên ngành

: CÔNG NGHỆ PHẦN MỀM

Lớp

: D12 – CNPM2

Khóa



: 2017 - 2022

Hà Nội, tháng 10 năm 2019


PHIẾU CHẤM
Sinh viên thực hiện
Họ tên

Chữ ký

Ghi chú

Chữ ký

Ghi chú

Nguyễn Hồng Kỳ

Giảng viên chấm
Họ tên
Giảng viên chấm 1:

Giảng viên chấm 2:


MỤC LỤC
LỜI NÓI ĐẦU……………………………………………………………….1
I. TỔNG QUAN CHIẾN LƯỢC QUY HOẠCH ĐỘNG TRONG XÂY

DỰNG THUẬT TOÁN……………………………………………………..2
1. Giới thiệu quy hoạch động…………………………………………….2
2. Tính chất của quy hoạch động………………………………………...2
a. Tính chất bài toán con gối nhau…………………………………….2
b. Tính chất cấu trúc con tối ưu……………………………………….2
c. Những khó khăn gặp phải khi sử dụng quy hoạch động………….3
3. Cách xây dựng bài toán quy hoạch động……………………………..3
4. So sánh quy hoạch động với chia để trị……………………………….3
5. Ưu điểm, nhược điểm của quy hoạch động…………………………..4
6. Ứng dụng của quy hoạch động………………………………………..4
II. ỨNG DỤNG CHIẾN LƯỢC QUY HOẠCH ĐỘNG ĐỂ TÌM DÃY
CON CHUNG LỚN NHẤT………………………………………………...5
1. Bài toán dãy con chung lớn nhất……………………………………...5
2. Bài toán Longest common subsequence……………………………....5
a. Đặt vấn đề……………………………………………………………..5
b. Giải quyết bài toán…………………………………………………...5
c. Mã giả………………………………………………………………....6
3. Bài toán Longest common substring……………………………….....7
a. Đặt vấn đề……………………………………………………………..7
b. Giải quyết bài toán…………………………………………………...7
c. Mã giả……………………………………………………………..…...8
III. CÀI ĐẶT………………………………………………………………...9
1. Code hàm DynamicProgramingLCSubsequence…………………….9
2. Code hàm DynamicProgramingLCSubstring………………………10
3. Code toàn bài………………………………………………………….10
4. Hình ảnh code chạy…………………………………………………...14
KẾT LUẬN………………………………………………………………...15


4


LỜI NÓI ĐẦU
Cấu trúc dữ liệu và giải thuật là một trong những nền tảng quan trọng
của Khoa học máy tính nói chung và Kỹ thuật lập trình nói riêng. Nó giúp
cho người lập trình hiểu được cấu trúc dữ liệu, các giải thuật và những chiến
lược thiết kế các thuật toán để giải quyết những bài toán của lập trình. Một
trong những phần chính của cấu trúc dữ liệu và giải thuật là phần Chiến lược
thiết kế thuật toán. Trong phần Chiến lược thiết kế thuật toán này có phần
chiến lược vô cùng quan trọng mà người lập trình viên nào cũng phải biết đó
là: Chiến lược thiết kế thuật toán quy hoạch động (Dynamic Programing).
Trong bài viết dưới đây sẽ nói về phần chiến lược quy hoạch động này và
ứng dụng quy hoạch động để tìm chuỗi con chung lớn nhất .
Qua đây em cũng xin cảm ơn thầy Vũ Văn Định đã giúp em thực hiện
hoàn thành đề tài của mình.


5
I. TỔNG QUAN CHIẾN LƯỢC QUY HOẠCH ĐỘNG TRONG

XÂY DỰNG THUẬT TOÁN
1. Giới thiệu quy hoạch động
- Quy hoạch động(Dynamic Progarming) là chia bài toán lớn thành các bài
toán nhỏ hơn có tính chất gối nhau sau đó tổng hợp lời giải của các bài
toán con đó thành lời giải của bài toán lớn ban đầu.
- Quy hoạch động được nhà toán học Richard Bellman phát minh năm
1953. Trong ngành Khoa học máy tính (Computer Science), quy hoạch
động là một trong những chiến lược thiết kế thuật toán vô cùng quan
trọng. Quy hoạch động là một trong những phương pháp làm giảm thời
gian chạy của các thuật toán thể hiện 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. Tính chất của quy hoạch động
Như đề cập ở phần giới thiệu quy hoạch động thể hiện ở hai tính
chất đó là: Các bài toán con gối nhau (Overlapping subproblem) và cấu
trúc con tối ưu (Optimal substructure)
a. Tính chất bài toán con gối nhau
Tương tự như thuật toán chia để trị, quy hoạch động cũng chia bài
toán lớn thành các bài toán con nhỏ hơn. Quy hoạch động được sử dụng
khi các bài toán con này được gọi đi gọi lại. Phương pháp quy hoạch
động sẽ lưu kết quả của bài toán con này, và khi được gọi, nó sẽ không
cần phải tính lại, do đó làm giảm thời gian tính toán.
Quy hoạch động sẽ không thể áp dụng được (hoặc nói đúng hơn là
áp dụng cũng không có tác dụng gì) khi các bài toán con không gối
nhau. Ví dụ với thuật toán tìm kiếm nhị phân, quy hoạch động cũng
không thể tối ưu được gì cả, bởi vì mỗi khi chia nhỏ bài toán lớn thành
các bài toán con, mỗi bài toán cũng chỉ cần giải một lần mà không bao
giờ được gọi lại.
b. Tính chất cấu trúc con tối ưu
Cấu trúc con tối ưu là một tính chất là lời giải của bài toán lớn sẽ là
tập hợp lời giải từ các bài toán nhỏ hơn.
Tính chất cấu trúc con tối ưu rất quan trọng. Nó cho phép chúng ta
giải bài toán lớn dựa vào các bài toán con đã giải được. Nếu không có
tính chất này, chúng ta không thể áp dụng quy hoạch động được.
c. Những khó khăn gặp phải khi sử dụng quy hoạch động


6
Không phải lúc nào sự kết hợp lời giải của bài toán con cũng cho
ra lời giải bài toán lớn hơn.
Số lượng các 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. Cho đến nay, chưa ai xác định được
một cách chính xác những bài nào có thể được giải quyết hiệu quả bằng
phương pháp quy hoạch động. Có những vấn đề quá phức tạp và khó
khăn mà xem ra không thể ứng dụng quy hoạch động để giải quyết
được, trong khi cũng có những bài toán quá đơn giản khiến cho việc sử
dụng quy hoạch động để giải quyết lại kém hiệu quả hơn so với dùng
các thuật toán kinh điển.
3. Cách xây dựng bài toán quy hoạch động
Xây dựng bài toán quy hoạch động gồm 3 giai đoạn:
- GD 1: Chia bài toán lớn thành các bài toán con nhỏ hơn cùng dạng với
bài toán ban đầu, chia bài toán đến khi có thể giải bài toán con đó một
cách trực tiếp.
- GD 2: Giải các bài toán con cơ sở sau đó lưu trữ các bài toán con đó để
sử dụng nhiều lần trong quá trình lặp của bài toán.
- GD 3: Gộp các lời giải của bài toán con thành bài toán lớn hơn, tiếp tục
cho đến khi gặp bài toán yêu cầu
Bài toán cơ sở là bài toán hiển nhiên đúng hoặc dễ dàng giải được.
Xây dựng công thức truy hồi để giải bài toán lớn hơn. Từ lời giải của bài
toán cơ sở với công thức truy hồi ta có thể sử dụng mảng một chiều hoặc
mảng nhiều chiều để lưu trữ kết quả của bài toán cơ sở . Dựa vào phương
án và công thức truy hồi để giải bài toán ban đầu.
4. So sánh quy hoạch động (Dynamic progarming) với chia để trị

(Devide and conquer)
Giống nhau: Đều chia các bài toán lớn thành các bài toán con nhỏ
hơn và tổng hợp lời giải để giải bài toán lớn.
Khác nhau:
Quy hoạch động: Từ bài toán cơ sở giải các bài toán lớn hơn
tiếp tục như thế cho đến khi gặp bài toán đề ra. (Bottom up)
Chia để trị: Từ bài toán lớn chia thành các bài toán nhỏ hơn sau

đó tổng hợp lại được kết quả của bài toán ban đầu. (Top down)
5. Ưu điểm, nhược điểm của quy hoạch động


7
Ưu điểm: Các lời giải của bài toán con lưu trữ theo phương án , khi
cần chỉ tổng hợp và gọi lời giải, giảm thời gian chạy của thuật toán.
Nhược điểm: Tốn vùng nhớ để lưu trữ lời giải các bài toán con
6. Ứng dụng của quy hoạch động
Quy hoạch động có ứng dụng rất lớn trong việc giảm thời gian
chạy của một số thuật toán và khử đệ quy.

II. ỨNG DỤNG CHIẾN LƯỢC QUY HOẠCH ĐỘNG ĐỂ TÌM DÃY

CON CHUNG LỚN NHẤT
1. Giới thiệu chung về bài toán dãy con chung lớn nhất.
Dãy con chung lớn nhất là một trong nhưng bài toán cơ bản của
quy hoạch động.
Với bài toán dãy con chung lớn nhất có 2 dạng bài toán đó là:
Longest common subsequence và Longest common substring
Có thể hiểu hai bài toán này này qua ví dụ sau:
Ta có 2 chuỗi s1 = abcxyz và s2 = abcjkx thì Longest common
subsequence cho ra kết quả là: abcx với độ dài chuỗi con là 4 còn
Longest common substring cho ra kết quả là: abc với độ dài chuỗi con
là 3.

2. Bài toán Longest common subsequence
a. Đặt vấn đề
Vấn đề của bài toán là: Cho hai chuỗi S1 gồm n phần tử và S2 gồm
m phần tử và tìm chuỗi con chung dài nhất của 2 chuỗi S1 và S2. (Với



8
chuỗi con của một chuỗi thu được khi xóa một số ký tự kế tiếp thuộc
chuỗi đó hoặc không xóa ký tự nào.)
Ví dụ nhập vào chuỗi S1 = abcxyz và chuỗi S2 = abcjkx thì hàm
trả về độ dài chuỗi con (abcx) lớn nhất là 4

b. Giải quyết bài toán
Ta gọi L[i, j] là độ dài của dãy con chung lớn nhất của chuỗi S1
gồm i ký tự phần đầu của S1(chạy từ 0 đến i) và chuỗi S2 gồm j ký tự
phần đầu của S2(chạy từ 0 đến j).
Ta có công thức truy hồi quy hoạch động như sau:
L[0, j] = L[i, 0] = 0
L[i, j] =
Giả sử với đề bài trên ta có ma trận của L[I, j] như sau:

trên ta
thấy 2

a

b

c

j

k


x

0

0

0

0

0

0

0

Từ

bảng



thể

chuỗi



chuỗi


a

0

1

1

1

1

1

1

con chung

lớn

b

0

1

2

2


2

2

2

nhất



c

0

1

2

3

3

3

3

Phần

tử


x

0

1

2

3

3

3

4

y

0

1

2

3

3

3


4

z

0

1

2

3

3

3

4X

abcx.
L[i, j]
(Đánh
chính
dãy
lớn

cuối cùng
dấu

X)


là độ dài
con chung

nhất.
Và cũng từ bảng trên ta thấy được thuật toán chạy so sánh từng
phần tử trong chuỗi s1 với từng phần tử trong chuỗi s2

c. Mã giả(Pseudo code)
Hàm DynamicProgramingLCSubsequence sẽ trả về độ dài của
chuỗi con lớn nhất.
int DynamicProgramingLCSubsequence(string s1, string s2)
{
int m = độ dài chuỗi S1;
int n = dộ dài chuỗi S2;
int L[m + 1, n + 1];
for(int i = 0; i <= m; i++)


9
for(int j = 0; j <= n; j++1)
{
Nếu i = 0 hoặc j = 0 thì L[i, j] = 0;
Nếu không thì nếu S1[i - 1] = S2[j - 1]
thì L[i, j] = L[i - 1, j - 1] + 1;
Nếu không thì L[i, j] = max(L[i - 1, j], L[i, j - 1]);
}
return L[m, n];
}

3. Bài toán Longest common substring

a. Đặt vấn đề
Vấn đề của bài toán là: Cho hai chuỗi S1 gồm n phần tử và S2 gồm
m phần tử và tìm chuỗi con chung dài nhất của 2 chuỗi S1 và S2. (Với
chuỗi con của một chuỗi thu được là các phần tử trong chuỗi con liền kề
nhau.)
Ví dụ nhập vào chuỗi S1 = abcxyz và chuỗi S2 = abcjkx thì hàm
trả về độ dài chuỗi con (abc) lớn nhất là 3

b. Giải quyết bài toán
Cũng tương tự Longest common subsequence,ta gọi L[i, j] là độ
dài của dãy con chung lớn nhất của chuỗi S1 gồm i ký tự phần đầu của
S1(chạy từ 0 đến i) và chuỗi S2 gồm j ký tự phần đầu của S2(chạy từ 0
đến j).
Tuy nhiên đến công thức truy hồi quy hoạch động của LCSubstring
có phần khác với so LCSubsequence:
L[0, j] = L[i, 0] = 0
L[i, j] =
Ta có thể viết gọn lại như sau:
L[i, j] =

Giả sử với đề bài trên ta có ma trận của L[I, j] như sau:


10
a

b

c


j

k

x

0

0

0

0

0

0

0

a

0

1

0

0


0

0

0

b

0

0

2

0

0

0

0

c

0

0

0


3 X

0

0

0

x

0

0

0

0

0

0

1

y

0

0


0

0

0

0

0

z

0

0

0

0

0

0

0

Từ bảng trên ta có thể thấy 2 chuỗi có chuỗi con chung lớn nhất là
abc. Phần tử L[i, j] (Đánh dấu X) chính là độ dài dãy con chung lớn nhất.
Và cũng từ bảng trên ta thấy được thuật toán chạy so sánh từng
phần tử trong chuỗi s1 với từng phần tử trong chuỗi s2


III. CÀI ĐẶT
1. Code hàm DynamicProgramingLCSubsequence
Ta sẽ sử dụng toán tử 3 ngôi thay cho việc phải viết thêm hàm max
so sánh L[i – 1, j] và L[i, j - 1].
Đoạn code sử dụng ngôn ngữ C# nền tảng Console với IDE Visual
Studio 2015 Professional.


11
public int DynamicProgarmingLCS()
{
int m = s1.Length;
int n = s2.Length;
int[,] L = new int[m + 1, n + 1];
for (int i = 0; i <= m; i++)
for (int j = 0; j <= n; j++)
{
if (i == 0 || j == 0) // L[0, j] = L[i, 0] = 0
L[i, j] = 0;
else if (s1[i - 1] == s2[j - 1]) // L[i, j] = L[i - 1,
j - 1] + 1 if s1[i] = s2[j]
L[i, j] = L[i - 1, j - 1] + 1;
else
L[i, j] = (L[i - 1, j] > L[i, j - 1] ? L[i - 1, j] :
L[i, j - 1]); // L[i, j] = max(L[i - 1, j], L[i, j - 1]) if s1[i] <>
s2[j]
}
return L[m, n];
}


2. Code hàm DynamicProgramingLCSubstring
public int DynamicProgramingLCSubstring()
{
int n = s1.Length;
int m = s2.Length;
int result = 0; // Bien lay tra tri lon nhat trong L[i, j]
int[,] L = new int[n + 1, m + 1]; // do i va j chay tu
0 den n va m nen phai co (n + 1) x (m + 1) phan tu
for (int i = 0; i <= n; i++)
for(int j = 0; j <= m; j++)
{


12
if (i == 0 || j == 0) // L[0, j] = L[i, 0] = 0 neu i
= 0 va j = 0
L[i, j] = 0;
else if (s1[i - 1] == s2[j - 1]) // L[i, j] = L[i - 1, j
- 1] + 1 neu s1[i - 1] = s2[j - 1]
{
L[i, j] = L[i - 1, j - 1] + 1;
result = (result > L[i, j] ? result : L[i, j]); //
Lay gia tri lon nhat trong L[i, j]
}
}
return result;
}
3. Code toàn bài
using

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;

namespace Longest_Common_Subsequence
{
class DynamicPrograming
{
private string s1;
private string s2;
public void Input()
{
Console.WriteLine("\nNhap chuoi s1: ");
s1 = Console.ReadLine();
Console.WriteLine("\nNhap chuoi s2: ");
s2 = Console.ReadLine();
}
//Ham Longest common subsequence
public int DynamicProgarmingLCSubsequence()
{
int n = s1.Length;
int m = s2.Length;
int[,] L = new int[n + 1, m + 1]; // do i va j chay tu

0 den n va m nen phai co (n + 1) x (m + 1) phan tu
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++)
{


13
if (i == 0 || j == 0) // L[0, j] = L[i, 0] = 0 neu i
= 0 va j = 0
L[i, j] = 0;
else if (s1[i - 1] == s2[j - 1]) // L[i, j] = L[i - 1, j
- 1] + 1 neu s1[i - 1] = s2[j - 1]
L[i, j] = L[i - 1, j - 1] + 1;
else
L[i, j] = (L[i - 1, j] > L[i, j - 1] ? L[i - 1, j] :
L[i, j - 1]); // L[i, j] = max(L[i - 1, j], L[i, j - 1]) neu s1[i] <>
s2[j]
}
// Hien thi bang phuong an L[i, j]
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= m; j++)
Console.Write("{0} ", L[i, j]);
Console.WriteLine();
}
return L[n, m];
}
//Ham Longest common substring
public int DynamicProgramingLCSubstring()
{

int n = s1.Length;
int m = s2.Length;
int result = 0; // Bien lay tra tri lon nhat trong L[i, j]
int[,] L = new int[n + 1, m + 1]; // do i va j chay tu
0 den n va m nen phai co (n + 1) x (m + 1) phan tu
for (int i = 0; i <= n; i++)
for(int j = 0; j <= m; j++)
{
if (i == 0 || j == 0) // L[0, j] = L[i, 0] = 0 neu i
= 0 va j = 0
L[i, j] = 0;
else if (s1[i - 1] == s2[j - 1]) // L[i, j] = L[i - 1, j
- 1] + 1 neu s1[i - 1] = s2[j - 1]
{
L[i, j] = L[i - 1, j - 1] + 1;
result = (result > L[i, j] ? result : L[i, j]); //
Lay gia tri lon nhat trong L[i, j]
}
}
// Hien thi bang phuong an L[i, j]
for (int i = 0; i <= n; i++)
{


14
for (int j = 0; j <= m; j++)
Console.Write("{0} ", L[i, j]);
Console.WriteLine();
}
return result;

}
}
class Program
{
static void Main(string[] args)
{
DynamicPrograming DPLCS = new
DynamicPrograming();
int key;
do
{
Console.WriteLine("VUI LONG CHON MOT
TRONG HAI BAI TOAN \n");
Console.WriteLine("1. Bai toan chuoi con trong
hai chuoi lon nhat (Longest common substring)");
Console.WriteLine("2. Bai toan day con chung
lon nhat (Longest common subsequence)");
Console.Write("Chon bai toan: ");
key = int.Parse(Console.ReadLine());
Console.WriteLine();
if(key == 1)
{
DPLCS.Input();
int LCSMax =
DPLCS.DynamicProgramingLCSubstring();
Console.WriteLine("Do dai cua xau con chung
lon nhat: {0}", LCSMax);
Console.ReadKey();
}
else if (key == 2)

{
DPLCS.Input();
int LCSMax =
DPLCS.DynamicProgarmingLCSubsequence();
Console.WriteLine("Do dai cua xau con chung
lon nhat: {0}", LCSMax);
Console.ReadKey();
}
}
while (key < 1 || key > 2);


15
}
}
}

4. Hình ảnh code chạy
Hình ảnh dưới đây code của bài toán Longest common
subsequence với chuỗi S1 = abcxyz và chuỗi S2 = abcjkx thì hàm trả về
độ dài chuỗi con (abc) lớn nhất là 4 và bảng phương án L[i, j] của bài
toán.

Hình ảnh dưới đây code của bài toán Longest common substring
với chuỗi S1 = abcxyz và chuỗi S2 = abcjkx thì hàm trả về độ dài chuỗi
con (abc) lớn nhất là 4 và bảng phương án L[i, j] của bài toán.


16


KẾT LUẬN
Từ trên ta có thể thấy ý tưởng của chiến lược quy hoạch khá đơn
giản đó là chia bài toán lớn thành các bài toán con nhỏ hơn sau đó tổng
hợp lời giải của bài toán con để giải bài toán ban đầu. Tuy nhiên khi áp
dụng vào các bài toán cụ thể lại không hề dễ dàng một chút nào (điều này
khá giống nguyên lý Dirichlet trong toán rời rạc). Vì vậy khi ta giải bài
toán bằng phương pháp này thì ta phải thực hiện hai yêu cầu đó là:
• Thứ nhất, xác định công thức truy hồi của bài toán ban đầu, xác
định nghiệm của bài toán lớn qua các bài toán con.
• Thứ hai, với mỗi bài toán cụ thể, ta đề ra phương án lưu trữ
nghiệm một cách hợp lý từ đó ta có thể truy cập các nghiệm của
các bài toán con để tổng hợp cho bài toán lớn một cách dễ dàng.
Quy hoạch động là một chiến lược thiết kế thuật toán vô cùng phổ
biến. Nó có thể áp dụng để khử đệ quy của một số bài toán qua đó làm
giảm thời gian chạy của thuật toán đó. Tuy nhiên quy hoạch động chiếm
nhiều vùng nhớ để lưu kết quả của các bài toán con. Nhưng ngày nay
máy tính điện tử có bộ nhớ rất lớn lên đến hàng chục thậm chí trăm
gigabyte nên có thể giải quyết phần nào nhược điểm chiếm nhiều vùng
nhớ của quy hoạch động và phát huy thế mạnh làm giảm thời gian chạy
chương trình.


17
Có thể nói quy hoạch động là một trong những chiến lược thiết kế
thuật toán vô cùng quan trọng của ngành Khoa học máy tính. Nó giúp
chúng ta thiết kế những thuật toán tối ưu về thời gian chạy và từ những
thuật toán đó nó giúp chúng ta xây dựng những chương trình máy tính có
tốc độ thực thi nhanh, đặc biệt là những bài toán cần ưu tiên về tốc độ
tính toán.


DANH MỤC TÀI LIỆU THAM KHẢO
Giáo trình cấu trúc dữ liệu và giải thuật – UIT – Nhà xuất bản Đại học quốc gia
thành phố Hồ Chí Minh
/> /> /> /> /> /> /> />


×