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 .
Trang 1TRƯỜ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
Giảng viên hướng dẫn : VŨ VĂN ĐỊNH
Hà Nội, tháng 10 năm 2019
Trang 2Sinh viên thực hiện
Nguyễn Hồng Kỳ
Giảng viên chấm
Giảng viên chấm 1:
Giảng viên chấm 2:
Trang 3LỜ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
Trang 4LỜ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
Trang 5I 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
Trang 6Tí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
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)
Trang 7Giố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
Ư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
Trang 81 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 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] = { L[i−1, j−1]+1 nếu S 1[i−1]=S 2[ j−1]
max ( L[i−1, j], L[i, j−1]nếu S 1[i−1]≠ S 2[ j−1])
Giả sử với đề bài trên ta có ma trận của L[I, j] như sau:
Trang 9chuỗi có
chuỗi con
chung lớn
nhất là
abcx.
j] cuối
cùng (Đá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
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++) 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];
Trang 10}
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] = {L[i−1, j−1]+1 nếu S 1[i−1]=S 2[ j−1]
0
Ta có thể viết gọn lại như sau:
L[i, j] = {L[i−1, j−1]+1 nếu S 1[i−1]=S 2[ j−1]
0
Giả sử với đề bài trên ta có ma trận của L[I, j] như sau:
Trang 11Từ 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
Trang 121 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
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];
}
Trang 132 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++)
{
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] = 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Longest_Common_Subsequence
{
class DynamicPrograming
{
private string s1;
private string s2;
public void Input()
{
Trang 14Console.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++)
{
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] = 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];
}
Trang 15{
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] = 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++)
{
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
int key;
do
{
Console.WriteLine("VUI LONG CHON MOT TRONG HAI BAI TOAN \n");
Trang 16Console.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
Console.ReadKey();
}
else if (key == 2)
{
DPLCS.Input();
int LCSMax =
DPLCS.DynamicProgarmingLCSubsequence();
Console.WriteLine("Do dai cua
Console.ReadKey();
}
}
while (key < 1 || key > 2);
}
}
}
Trang 174 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