Data Structures & AlgorithmsCác thuật toán đối sánh chuỗi String Searching Nguyễn Tri Tuấn Khoa CNTT – ĐH.KHTN.Tp.HCM Email: nttuan@ fit.hcmuns.edu.vn String Searching Giới thiệu Các bướ
Trang 1Data Structures & Algorithms
Các thuật toán đối sánh chuỗi
(String Searching)
Nguyễn Tri Tuấn Khoa CNTT – ĐH.KHTN.Tp.HCM Email: nttuan@ fit.hcmuns.edu.vn
String Searching
Giới thiệu
Các bước xử lý
Các cách tiếp cận Brute-Force
Knuth-Morris-Pratt
Trang 2Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 3
Giới thiệu
ªCác thuật ngữ thường dùng:
String searching
Pattern matching
String matching
Text searching
Giới thiệu (tt)
ª“Đối sánh chuỗi” là gì ?
Cho một chuỗi T và một chuỗi P, hãy xác định P có
xuất hiện trong T hay không ?
Tương tự như lệnh:
strstr(T, P) [trong C++]
Pos(P, T) [trong Pascal] Pattern (P)
m
n
Trang 3Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 5
Giới thiệu (tt)
ª Đối sánh chuỗi là một trong những bài toán cơ
bản và “tự nhiên” nhất của ngành Tin Học
ªCông dụng:
Chức năng search trong các trình soạn thảo văn bản
và Web browser
Các công cụ search (vd Google)
Sinh học phân tử (tìm kiếm các mẫu trong DNA /
protein)
…
Giới thiệu (tt)
ªLịch sử phát triển:
Brute Force:
Trong trường hợp xấu nhất thời gian thực hiện tỷ lệ với m*n
Trong nhiều ứng dụng thực tế thời gian xử lý thực sự tỉ lệ với
m+n
Thích hợp với hầu hết các hệ máy tính
Trong năm 1970, S.A.Cook đã chứng minh một kết
quả lý thuyết giúp suy ra sự tồn tại của một thuật toán
để giải bài toán đối sánh mẫu, có thời gian tỷ lệ với
M+N trong trường hợp xấu nhất
Trang 4Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 7
Giới thiệu (tt)
ªLịch sử phát triển:
D.E.Knuth và V.R.Pratt đã kiên trì theo đuổi kiến trúc
mà Cook đã dùng để chứng minh cho cho định lý của
ông và nhận được một thuật toán tương đối đơn giản
Đồng thời J.H.Morris cũng khám phá ra thuật toán
này
Knuth, Morris, Pratt đã không giới thiệu thuật toán
của họ cho đến năm 1976, và trong thời gian này
R.S.Boyer và J.S.Moore đã khám phá ra một thuật
toán nhanh hơn nhiều
Giới thiệu (tt)
ªLịch sử phát triển:
Trong năm 1980, R.M.Karp và M.O.Rabin đã đi đến
thuật toán đơn giản gần như thuật toán Brute Force,
có thời gian thi hành thường tỉ lệ với m+n
Trang 5Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 9
Giới thiệu (tt)
ªCác thuật toán tiêu biểu:
Brute Force
Karp-Rabin
Morris-Pratt
Knuth-Morris-Pratt
Boyer-Moore
Boyer-Moore-Horspool
Apostolico-Giancarlo
Aho-Corasick
Các bước xử lý
ªCác thuật toán đối sánh chuỗi thường có 2 bước
xử lý sau:
Bước tiền xử lý (Preprocessing Phase):
Xử lý Pattern
Khởi tạo cấu trúc dữ liệu
Bước tìm kiếm (Searching Phase)
Tìm kiếm Pattern trong Text
Trang 6Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 11
Các cách tiếp cận
ª Có 4 cách tiếp cận chính để làm tăng tốc độ thuật toán:
Classical Algorithms
Thuật toán chủ yếu dựa vào phép so sánh giữa các ký tự
Thuật toán: Quick Search, Brute Force…
Suffix Automata Algorithms
Sử dụng Suffix Automaton Data Structure để nhận ra tất cả các
hậu tố của Pattern
Thuật toán: Knuth – Morris – Pratt…
Bit-Parallelism Algorithms
Khai thác bản chất song song của các dữ liệu bit để thực hiện các
thao tác cùng lúc
Thuật toán: Shift-Or…
Hashing Algorithms
Sử dụng kỹ thuật băm, tránh việc so sánh các ký tự có độ phức tạp
bậc 2
Thuật toán: Karp-Rabin
Độ phức tạp
ª Độ phức tạp của thuật toán phụ thuộc vào 3 yếu
tố sau:
Chiều dài của Pattern
Chiều dài của Text
Độ lớn của tập các ký tự: Binary, DNA, Alphabets…
Trang 7Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 13
String Searching
Giới thiệu
Các bước xử lý
Các cách tiếp cận Brute-Force Knuth-Morris-Pratt
Brute-Force
ªÝ tưởng:
Đối với vị trí thứ i của văn bản T (i=0…n-m), ta so sánh
các ký tự của P tương ứng từ trái sang phải:
P[0] với T[i], P[1] với T[i+1],…, P[m-1] với T[i+m-1]
P[j] v ới T[i+j] (j = 0 m-1)
ªVí dụ:
T = “ TWO RED ROADS CROSSING ”
n = length(T) = 22
P = “ ROADS ”
m = length(P) = 5
Trang 8Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 15
Brute-Force (tt)
ªVí dụ:
Bước 1: T WO RED ROADS CROSSING
R OADS
Bước 2: T W O RED ROADS CROSSING
R OADS
…
Bước 5: TWO R E D ROADS CROSSING
RO ADS
…
Bước 9: TWO RED ROADS CROSSING
ROADS
Brute-Force (tt)
ªCài đặt bằng C:
int stringSearchBF (char *P, char *T);
Kết quả:
-1 : nếu P không nằm trong T, hoặc
>=0: vị trí của P trong T
Trang 9Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 17
Brute-Force (tt)
int stringSearchBF(char *P, char *T)
{
for (int i=0; i<=strlen(T)-strlen(P); i++)
{
int j = 0;
while (j < strlen(P))
if (T[i+j]==P[j]) j++;
else break;
if(j==strlen(P)) return i; // tìm th ấy
}
return -1 ; // không tìm th ấy
}
Brute-Force (tt)
ª Đánh giá:
Trường hợp xấu nhất O(m*n) – tự chứng minh
Trang 10Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 19
Brute-Force (tt)
ª Đánh giá:
Trường hợp tốt nhất O(n) – tự chứng minh
Trường hợp trung bình O(n+m) – tự chứng minh
String Searching
Giới thiệu
Các bước xử lý
Các cách tiếp cận Brute-Force
Trang 11Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 21
Đặt vấn đề
ª Trong thuật toán Brute-Force: khi xảy ra không so khớp
tại một ký tự, ta đã xóa bỏ tất cả thông tin có được bởi
các phép so sánh trước đó và bắt đầu lại việc so sánh từ
ký tự đầu tiên của mẫu P [i=i+1; j=0]
ª Ví dụ:
T = 101010100111; P = 10100
1010 1 0100111
1010 0 (không so khớp tại i=0, j=4)
1 1010100111
1 0100 (Brute-Force: bắt đầu lại từ i=1; j=0)
Dịch chuyển i và j ra sao cho có lợi ?
1010 1 0100111
10 1 0 ( bắt đầu lại từ i=2; j=2)
Morris-Pratt
ªHướng giải quyết của Morris-Pratt:
Lợi dụng thông tin đã biết về các ký tự đã so sánh
Biến j thể hiện số ký tự đã được so khớp giữa mẫu
(P) và văn bản (T) Khi gặp vị trí không so khớp, thay
vì gán j = 0 để quay lại từ đầu chuỗi P, ta sẽ gán cho
j một giá trị thích hợp
Trang 12Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 23
Morris-Pratt (tt)
// T ư tưởng chính của thuật toán Morris-Pratt
// giai đoạn tìm kiếm
i = j = 0;
while (i+j < n) {
if (p[j]==t[i+j]) {
j++;
// khi đã đi hết độ dài của chuỗi P
// à đã tìm được P à trả về vị trí tìm được
if (j==m) return i;
}
else {
// khi không so kh ớp, dịch chuyển về vị
// trí NEXT[j]
i = i + j – NEXT[j];
if (j > 0) j = NEXT[j];
}
return -1; // không tìm th ấy
Morris-Pratt (tt)
ªGiai đoạn tiền xử lý – xây dựng bảng NEXT
Xây dựng mảng NEXT[0 m-1] (có m phần tử)
NEXT[j] chứa giá trị dùng để dịch chuyển con trỏ j khi
xảy ra sự không khớp tại vị trí j
Trang 13Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 25
Morris-Pratt (tt)
ª Ví dụ:
T = AATAAAATA
P = AAATA
j=2 AAATA (không so khớp à i=i+j–NEXT[2]=1
j=NEXT[2]=1)
0 2
1 0
-1 NEXT
4 3
2 1
0
Morris-Pratt (tt)
ª Ví dụ:
i=1 A A TAAAATA
j=1 AA A TA (không so khớp à i=i+j–NEXT[1]=2
j=NEXT[1]=0)
i=2 AATAAAATA
j=0 A AA TA (không so khớp à i=i+j–NEXT[0]=3
j=0)
i=3 AAT A AAATA
i=3 AAT AAA ATA
(không so khớp à i=i+j–NEXT[3]=4
j=NEXT[3]=2)
i=4 AATA AAA TA
i=4 AATA AAATA
à so khớp !
Trang 14Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 27
Morris-Pratt (tt)
ª Xét trạng thái không so khớp tại vị trí thứ j trên mẫu P
Phần đã so khớp (u), từ P[0] đến P[0 j-1]
Nếu tồn tại v = đoạn so khớp giữa phần đầu P với
phần cuối của P[0 j-1]
à v là đoạn dịch chuyển của j
Morris-Pratt (tt)
ªCách xây dựng bảng NEXT:
NEXT[0] = -1
Với j>0, giá trị của NEXT[j] là số k lớn nhất nhỏ hơn j
sao cho k ký tự đầu tiên của mẫu P khớp với k ký tự
cuối của P[0 j-1]
Ví dụ: P = AAATA
NEXT[1] = 0
(j=1)
A
Trang 15Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 29
Morris-Pratt (tt)
NEXT[3] = 2
NEXT[4] = 0
A A A
A
A A
T A A A
A T A
A
0 2
1 0
-1 NEXT
4 3
2 1
0
Morris-Pratt (tt)
// Hàm t ạo lập bảng NEXT (Morris-Pratt)
void initNEXT_MP(char *p, int NEXT[]) {
int i, j;
int m = strlen(p);
i = 0;
j = NEXT[0] = -1;
while (i < m) {
if (j == -1 || p[i] == p[j]) { i++; j++;
NEXT[i] = j;
} else j = NEXT[j];
}
}
Trang 16Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 31
Morris-Pratt (tt)
ª Cài đặt bằng C:
// Thu ật toán đối sánh Morris-Pratt
// K ết quả:
// -1 : n ếu P không nằm trong T, hoặc
// >=0 : v ị trí của P trong T
int stringSearchMP (char *P,char *T) {
int n = strlen(T);
int m = strlen(P);
int *NEXT = new int[m];
initNEXT_MP(p, NEXT);
… (xem slide #23)
}
Morris-Pratt (tt)
ªVí dụ:
Xây dựng bảng NEXT cho P = 10100
Xây dựng bảng NEXT cho P = ABACAB
Xây dựng bảng NEXT cho P = GCAGAGAG
Xây dựng bảng NEXT cho P = AABAABA
Trang 17Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 33
Morris-Pratt (tt)
2 1 0 0
-1 NEXT
4 3 2 1
0
P = 10100
0
4 1 1
0 0
-1 NEXT
5 3
2 1
0
P = ABACAB
0
5 1
6 1
4
0 0
0 0 -1 NEXT
7 3
2 1 0
P = GCAGAGAG
Morris-Pratt (tt)
ª Độ phức tạp:
Giai đoạn tiền xử lý: O(m) (tính NEXT)
Giai đoạn tìm kiếm: O(n)
Tổng: O(n+m)
Số phép so sánh lớn nhất của một ký tự <= m
Trang 18Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 35
Knuth-Morris-Pratt
ªCải tiến của KMP so với Morris-Pratt
Khi xây dựng bảng NEXT, Knuth bổ sung kiểm tra
điều kiện c ≠ a để tránh trường hợp “mis-match” ngay
vị trí đầu tiên sau khi dịch chuyển j
Giải thích: nếu a == c, tức là c ≠ b (vì a ≠ b) à sẽ
“mis-match” ngay lần so sánh đầu tiên sau khi dịch chuyển j
Knuth-Morris-Pratt (tt)
ªTóm lại: thuật toán KMP
Giai đoạn tiền xử lý có một cải tiến nhỏ:
Tính độ dịch chuyển tốt hơn à tránh so sánh cùng một ký tự
trong T hai lần
Giai đoạn tìm kiếm: hoàn toàn giống thuật toán
Morris-Pratt
Trang 19Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 37
Knuth-Morris-Pratt (tt)
// Hàm t ạo lập bảng NEXT (KMP)
void initNEXT_KMP(char *p, int NEXT[]) {
int i, j;
int m = strlen(p);
i = 0;
j = NEXT[0] = -1;
while (i < m) {
if (j == -1 || p[i] == p[j]) { i++; j++;
if (p[i] != p[j]) NEXT[i] = j;
else NEXT[i] = NEXT[j];
} else j = NEXT[j];
}
}
Knuth-Morris-Pratt (tt)
ª Độ phức tạp:
Giai đoạn tiền xử lý: O(m) (tính NEXT)
Giai đoạn tìm kiếm: O(n)
Tổng: O(n+m)
Số phép so sánh lớn nhất của một ký tự <= log a m
với a = 1+√5
2
Trang 20Autumn 2008 Data Structures & Algorithms - String Searching - Nguyen Tri Tuan, DH.KHTN Tp.HCM 39
Thank you for your attention
Q & A