ỨNG DỤNG MẢNG BĂM (HASH) ĐỂ GIẢI CÁC BÀI TOÁN VỀ SO KHỚP XÂU (CHUỖI)

27 2.4K 7
ỨNG DỤNG MẢNG BĂM (HASH) ĐỂ GIẢI CÁC BÀI TOÁN VỀ SO KHỚP XÂU (CHUỖI)

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

SỞ GIÁO DỤC VÀ ĐÀO TẠO NAM ĐỊNH TRƯỜNG THPT CHUYÊN LÊ HỒNG PHONG SÁNG KIẾN DỰ THI CẤP TỈNH BÁO CÁO SÁNG KIẾN ỨNG DỤNG MẢNG BĂM (HASH) ĐỂ GIẢI CÁC BÀI TOÁN VỀ SO KHỚP XÂU (CHUỖI) Tác giả: Ngô Trung Tưởng Trình độ chuyên môn: Đại học Chức vụ: Giáo viên Nơi công tác: Trường THPT chuyên Lê Hồng Phong Nam Định, tháng 05 năm 2014 THÔNG TIN CHUNG VỀ SÁNG KIẾN 1. Tên sáng kiến: ỨNG DỤNG MẢNG BĂM (HASH) ĐỂ GIẢI CÁC BÀI TOÁN VỀ SO KHỚP XÂU (CHUỖI) 2. Lĩnh vực áp dụng sáng kiến: Giảng dạy lớp 10, 11 chuyên tin, đội tuyển HSGQG môn tin học 3. Thời gian áp dụng sáng kiến: Từ năm học 2012-2013 đến nay 4. Tác giả: Họ và tên: Ngô Trung Tưởng Năm sinh: 1982 Nơi thường trú: 22/34 Trần Thái Tông, P Thống Nhất, TP Nam Định Trình độ chuyên môn: Đại học Chức vụ công tác: Giáo viên Nơi làm việc: Trường THPT chuyên Lê Hồng Phong Địa chỉ liên hệ: 22/34 Trần Thái Tông, P Thống Nhất, TP Nam Định Điện thoại: 0982.209.259 5. Đơn vị áp dụng sáng kiến: Tên đơn vị: Trường THPT chuyên Lê Hồng Phong Địa chỉ: 76 Vỵ Xuyên Điện thoại: 03503.640297 I. Điều kiện hoàn cảnh: Trong lĩnh vực khoa học máy tính nói chung và trong thi cử nói riêng một lớp các bài toán rất được quan tâm đó là bài toán xử lý xâu chuỗi. Một trong những bài toán phổ biến mà chúng ta hay gặp là tìm kiếm xâu con trong xâu cho trước. II. Thực trạng Bài toán về xử lí xâu rất quen thuộc và thường gặp trong các bài tập về tin học, nhưng trong quá trình giảng các lớp chuyên tin và đội tuyển HSGQG môn Tin học, tôi nhận thấy các em học sinh giải quyết các bài toán này chưa thật sự thuần thục và không hiệu quả, đặc biệt là trong thi cử. Chính vì vậy, tôi viết chuyên đề này để giúp các em giải quyết các bài toán về xâu hiệu quả hơn. III. Nội dung chuyên đề 1. Phát biểu bài toán - Cho một xâu T gồm m kí tự - Cho một xâu mẫu P gồm n kí tự - Yêu cầu: cho biết xâu mẫu P xuất hiện bao nhiêu lần trong T và chỉ ra các vị trí xuất hiện của P. 2. Thuật toán Có rất nhiều thuật toán khác nhau để giải bài toán này như Brute-force approach (vét cạn-độ phức tạp O(n*m)), KMP (độ phức tạp O(n+m)), Suffix Array… Trong chuyên đề này tôi không đề cập đến các thuật toán trên mà chỉ tập trung vào một thuật toán sử dụng mảng băm hay còn gọi một tên gọi khác là HASH. Đây là một thuật toán rất hiệu quả đặc biệt là trong thi cử vì tốc độ thực thi nhanh, linh hoạt trong xử lí và rất dễ cài đặt. Mô tả thuật toán HASH a. Các kí hiệu: - Tập các chữ cái trong bảng chữ cái kí hiệu ∑ - Xâu T gồm m kí tự: T[1 m] - Xâu mẫu P gồm n kí tự: P[1 n] - Xâu con từ i đến j của xâu T là: T[i j] - Chúng ta cần tìm tất cả các vị trí i thỏa mãn (1≤i≤m-n+1) mà T[i i+n-1]=P[1 n] b. Mô tả thuật toán Để đơn giản, giả sử rằng Σ = {a, b,…, z}, nghĩa là Σ chỉ gồm các chữ cái Latin in thường. Để biểu diễn một xâu, thay vì dùng chữ cái, chúng ta sẽ chuyển sang biểu diễn số ở hệ 26. Ví dụ: xâu ‘abcd’ biểu diễn hệ 26: ‘a’*26 3 + ‘b’*26 2 + ‘c’*26 1 + ‘d’*26 0 đổi ra số ở hệ cơ số 10 tương ứng là: 65*26 3 +66*26 2 +67*26+68= 1188866. Dễ thấy rằng, muốn so sánh 2 xâu bằng nhau khi và chỉ khi biểu diễn của 2 xâu ở hệ cơ số 10 giống nhau. Ví dụ xâu A=B ↔ Mã A = Mã B Tuy nhiên nếu xâu quá dài thì Mã A, Mã B cũng rất lớn. Chính vì thế, ta lấy mod cho 1 số base nguyên tố rất lớn nào đó ví dụ 10 9 +7, hay 2.10 9 +11… A=B  Mã A mod base = Mã B mod base Dễ dàng nhận thấy việc so sánh Mã A mod base với Mã B mod base rồi kết luận A có bằng với B hay không là sai. Mã A mod base = Mã B mod base chỉ là điều kiện cần để A bằng B chứ chưa phải điều kiện đủ. Tuy nhiên, chúng ta sẽ chấp nhận lập luận sai này trong thuật toán Hash. Và coi điều kiện cần như điều kiện đủ. Trên thực tế, lập luận sai này có những lúc dẫn đến so sánh xâu không chính xác và chương trình bị chạy ra kết quả sai. Nhưng cũng thực tế cho thấy rằng, khi chọn base là một số nguyên lớn, số lượng những trường hợp sai rất ít, và ta có thể coi Hash là một thuật toán chính xác. Trở lại bài toán ban đầu, chúng ta cần chỉ ra P xuất hiện ở những vị trí nào trong T. Để làm được việc này, chúng ta chỉ cần duyệt qua mọi vị trí xuất phát có thể của P trong T. Giả sử vị trí đó là i, chúng ta sẽ kiểm tra T[i. . i + n − 1] có bằng với P hay không. Để kiểm tra điều này, chúng ta cần tính được mã Hash của đoạn T[i. . i + n − 1] và mã Hash của xâu P. Để tính mã Hash của xâu P chúng ta chỉ cần làm đơn giản như sau: HashP=0; for (i=1;i<=n;i++) HashP = (HashP*26 + P[i]) % base; Phần khó hơn của thuật toán Hash là: Tính mã Hash của một đoạn con từ i đến j T[i. . j] của xâu T (1 ≤ i ≤ j ≤ m). Ví dụ sau: Xét xâu s= “cdeacx” và biểu diễn của nó dưới cơ số 26. Chúng ta cần lấy mã Hash của đoạn con từ phần tử thứ 3 đến phần tử thứ 6, chỉ cần lấy mã Hash của s[1. .6] trừ đi mã Hash của s[1. .2] nhân với 26 4 . Để cài đặt ý tưởng này, chúng ta cần khởi tạo 26 � mod base (0 ≤ i ≤ m) và mã Hash của tất cả những tiền tố của s, cụ thể là mã Hash của những xâu s[1. . i] (1 ≤ i ≤ m). //tinh 26 x mod base POW[0]=1; for (i=1;i<=m;i++) POW[i] = POW[i-1]*26 % base; //tinh ma Hash xau s[1 i] HashT[0]=0; for(i=1;i<=m;i++) HashT[i]=(HashT[i-1]*26+T[i])%base; Để lấy mã Hash của T[i j] ta viết hàm sau: long long getHash(long i, long j){ return(HashT[j]-HashT[i-1]*POW[j-i+1]+ base*base)%base; } Bài toán chính được giải quyết như sau: for (i=1;i<=m-n+1;i++) if (getHash(i,i+n-1)==HashP) cout<<i<<” “; c. Viết chương trình Đây là chương trình đầy đủ và đã được chấm thành công trên hệ thống chấm bài SPOJ http://vn.spoj.com/problems/SUBSTR/ Chương trình viết bằng C++ #include <iostream> #include <string> #include <algorithm> #include <fstream> using namespace std; const long long base=1000000000+7; long long hasha[1000005], hashb,POW[1000005]; string a,b; long n,m; ifstream fi ("substr.inp"); ofstream fo ("substr.out"); long long gethash(long i, long j){ return(hasha[j]-hasha[i-1]*POW[j-i+1] +base*base) %base; } int main(){ //getline(cin,a,'\n'); getline(fi,a); getline(fi,b); n=a.size(); a=" "+a; //getline(cin,b,'\n'); m=b.size(); b=" "+b; //tinh hashb hashb=0; for (long i=1; i<=m; i++) hashb= (hashb*26 + b[i])% base; //tinh ham mu POW[0]=1; for (long i=1;i<=n;i++) POW[i]=(POW[i-1]*26)% base; //tinh hash xau a hasha[0]=0; for (long i=1; i<=n;i++) hasha[i]=(hasha[i-1]*26+a[i]) % base; //tim vi tri xau b trong a for (long i=1; i<=n-m+1;i++) if (hashb==gethash(i,i+m-1)) //cout<<i<<" "; fo<<i<<" "; //cin.get(); fi.close(); fo.close(); return 0; } d. Đánh giá: Độ phức tạp của thuật toán là O(n + m). Nhưng điều quan trọng là: chúng ta có thể kiểm tra 2 xâu có giống nhau hay không trong O(1). Đây là điều tạo nên sự linh động cho thuật toán Hash. 4. Ứng dụng Bài 1: Tiền tố và hậu tố http://vn.spoj.com/problems/C11STR2/ Xâu a được gọi là tiền tố của xâu b nếu xâu a trùng với phần đầu của xâu b. Ví dụ pre là tiền tố củaprefix Xâu a được gọi là hậu tố của xâu b nếu xâu a trùng với phần cuối của xâu b. Ví dụ fix là hậu tố củasuffix yenthanh132 vừa mới học về tiền tố và hậu tố nên hôm nay anh ta sẽ đố các bạn một bài toán đơn giản về tiền tố và hậu tố như sau: • Cho 2 xâu a,b gồm các kí tự latin thường ('a' đến 'z') • Tìm 1 xâu c thỏa mãng: 1. Xâu a là tiền tố của xâu c 2. Xâu b là hậu tố của xâu c 3. Độ xài xâu c là ngắn nhất. Input • Dòng 1: Xâu a • Dòng 2: Xâu b Output • Một dòng duy nhất là xâu c. Giới hạn: • 40% số test có độ dài 2 xâu a,b <= 1000 kí tự • Trong toàn bộ test, độ dài 2 xâu a,b <= 10 5 kí tự Ví dụ: Input 1: abca cab Output 1: abcab Input 2: abc abc Output 2: abc (2 xâu a,b không nhất thiết phải khác nhau). Hướng dẫn giải thuật: - Tìm hậu tố dài nhất xâu a có phần chung tiền tố xâu b: + Với xâu a gọi ha[i] là mã hash của xâu a gồm các kí tự từ 1 đến i. + Với xâu b gọi hb[i] là mã hash cảu xâu b gồm các kí tự từ 1 đến i. + Tìm vị trí x mà tiền tố xâu b chung hậu tố xâu a ta chỉ so sánh mã hash của xâu con b[1],…,b[x] với a[n-x+1],…,a[n] với n là độ dài xâu a. Duyệt x từ 1 đến độ dài xâu b tìm ra kết quả. - Độ phức tạp O(N) Chương trình: #include <iostream> #include <string> #include <algorithm> using namespace std; typedef long long ll; const ll BASE=1000000000+11; string a,b; ll ha[100005],hb[100005],POW[100005]; long n,m; bool kt(long x){ ll xa,xb; long n=a.size()-1; xb=hb[x]; xa=(ha[n]-ha[n-x]*POW[x]+BASE*BASE)%BASE; if (xa==xb) return 1; else return 0; } int main(){ getline(cin,a); a='@'+a; getline(cin,b); b='@'+b; //tinh ham mu POW[0]=1; [...]... } Bài 5 - Palindrome dài nhất – PALINY http://vn.spoj.com/problems/PALINY/ Cho xâu S Tìm xâu đối xứng dài nhất gồm các kí tự liên tiếp trong S Input Dòng 1: N (số ký tự của xâu S; N . Định, tháng 05 năm 2014 THÔNG TIN CHUNG VỀ SÁNG KIẾN 1. Tên sáng kiến: ỨNG DỤNG MẢNG BĂM (HASH) ĐỂ GIẢI CÁC BÀI TOÁN VỀ SO KHỚP XÂU (CHUỖI) 2. Lĩnh vực áp dụng sáng kiến: Giảng dạy lớp 10, 11. CHUYÊN LÊ HỒNG PHONG SÁNG KIẾN DỰ THI CẤP TỈNH BÁO CÁO SÁNG KIẾN ỨNG DỤNG MẢNG BĂM (HASH) ĐỂ GIẢI CÁC BÀI TOÁN VỀ SO KHỚP XÂU (CHUỖI) Tác giả: Ngô Trung Tưởng Trình độ chuyên môn: Đại học Chức. một lớp các bài toán rất được quan tâm đó là bài toán xử lý xâu chuỗi. Một trong những bài toán phổ biến mà chúng ta hay gặp là tìm kiếm xâu con trong xâu cho trước. II. Thực trạng Bài toán về xử

Ngày đăng: 03/04/2015, 16:38

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan