Thuật toán Manacher dùng để tìm đoạn con đối xứng dài nhất trong xâu. Một số bài tập về xâu có thể tham khảo. Vì sự học luôn khó khăn, mình tải về rồi Edit lại cho mọi người có thể tham khảo nâng cao sự hiểu biết của mình. Hi vọng được chia sẻ, nâng cao kiến thức cùng mọi người
MỘT SỐ THUẬT TỐN TÌM XÂU CON ĐỐI XỨNG DÀI NHẤT I Mở đầu Xâu đối xứng (xâu Palindrome) xâu đọc từ trái sang phải hay đọc từ phải qua trái giống nhau, ví dụ: xâu aba, aaa, abccba… Trên thực tế, hay gặp tốn có liên quan đến xâu đối xứng mức độ phức tạp khác Để giải chúng có số thuật tốn áp dụng, Tuy nhiên, lựa chọn thuật toán vấn đề cần quan tâm Trong viết này, tơi xin trình bày số thuật tốn tìm xâu đối xứng dài nhất, đặc biệt thuật tốn tìm xâu đối xứng dài thời gian tuyến tính với phân tích cụ thể độ phức tạp tính tốn thuật tốn Mục đích, để có nhìn hệ thống đưa cách làm tối ưu toán cụ thể Chúng ta xem lại toán quen thuộc tin học sau: Bài toán: Cho xâu S, tìm xâu (một dãy ký tự liên tiếp) dài của S đối xứng Dữ liệu vào: PALIN.INP Gồm dòng chứa xâu S Dữ liệu ra: PALIN.OUT Ghi độ dài xâu dài của S đối xứng Ví dụ: PALIN.INP PALIN.OUT abccbghjkaaaaaaaaaakfwg 12 II Nội dung Đặt n=length(S) 1.Thuật toán duyệt toàn (thời gian O(n3), nhớ O(1)) Rõ ràng xâu S có n(n-1)/2 xâu con, với xâu lại kiểm tra xem có đối xứng khơng Độ phức tạp của thuật tốn O(n3) Dưới trình bày hàm IsPalin(i,j:longint) trả giá trị True đoạn S[i j] đối xứng trả giá trị False trường hợp ngược lại Function IsPalin(i,j:longint):boolean; var k:longint; begin for k:=0 to (j-i+1)div if s[i+k]s[j-k] then exit(False); Exit(True); end; Thủ tục Process xét xâu của S xem có đối xứng khơng, có cập nhật lại kết longPalin: độ dài xâu đối xứng dài nhất, dau, cuoi: vị trí bắt đầu kết thúc của xâu đối xứng dài procedure process; var i,j:longint; begin LongPalin:=1; cuoi:=1; cuoi:=1; for i:=1 to n-1 for j:=n downto i+1 if IsPalin(i,j) then if j-i+1>longPalin then begin Longpalin:=j-i+1; dau:=i; cuoi:=j; end; end; Thuật toán quy hoạch động (thời gian O(n2), nhớ O(n2)) Gọi L[i,j] độ dài xâu lớn của đoạn từ s[i j] - Khởi tạo: L[i,j]=0 với ij, L[i,i]=1 - Công thức: Nếu s[i]=s[j] L[i,j]=L[i+1,j-1]+2 Nếu s[i]s[j] L[i,j]=max(L[i,j-1], L[i+1,j]) - Đáp số: L[1,n] Chương trình: procedure optimize; var i,j:longint; begin fillchar(L,sizeof(L),0); for i:=1 to n L[i,i]:=1; for i:=n-1 downto for j:=i+1 to n if s[i]=s[j] then L[i,j]:=L[i+1,j-1]+2 else L[i,j]:=max(L[i+1,j],L[i,j-1]); end; Thuật toán duyệt trung tâm (thời gian O(n2), nhớ O(n)) Dễ dàng nhận thấy vị trí trung tâm của xâu đối xứng chỉ ký tự chỗ trống Như vậy, xâu S có 2n+1 trung tâm, với trung tâm đó, ta tìm xâu dài đối xứng bằng cách xuất phát từ trung tâm, đồng thời phát triển đồng thời bên trái bên phải tìm xâu đối xứng dài trung tâm Độ phức tạp thuật toán O(n2) Gọi P[i] độ dài xâu đối xứng dài nhận vị trí i làm trung tâm, khai báo P:array[0 2*n] of longint Chương trình: procedure xuly; var i,dau,cuoi:longint; begin n:=length(s); m:=2*n; fillchar(P,sizeof(P),0); P[1]:=1; P[m-1]:=1; max:=1;//max: độ dài xâu đối xứng dài nhất e1:=1;//e1: vị trí kết thúc của xâu đối xứng dài nhất s1:=1;// s1: vị trí bắt đầu của xâu đối xứng dài nhất for i:=2 to m-2 begin if (i mod 2=0) then begin dau:=i div 2; cuoi:=dau+1; end else begin P[i]:=1; dau:=(i div 2) ; cuoi:=dau+2; end; while (dau>=1) and (cuoimax then begin max:=P[i]; e1:=cuoi-1; s1:=dau+1; end; end; end; Thuật toán Manacher (thời gian O(n), nhớ O(n)) Có cách tính mảng P (ở thuật tốn duyệt trung tâm) nhanh khơng? Xét xâu S chứa nhiều xâu đối xứng chờng chéo, ví dụ: “aaaaaaaaa” “cabcbabcbabcba” Thực tế, ta tận dụng ưu điểm của tính đối xứng tránh tính tốn khơng cần thiết Cụ thể ta làm sau: Đầu tiên, ta thay xâu S bằng xâu T bằng cách thêm ký tự “#” vào chỗ trống xâu S, ví dụ: S = “abaaba” T = “#a#b#a#a#b#a#” Để tìm xâu đối xứng dài của S, ta cần mở rộng Ti hai phía khoảng d dài cho Ti-d Ti+d đối xứng d gọi bán kính của xâu đối xứng nhận Ti làm trung tâm Gọi P[i] độ dài xâu đối xứng dài nhận Ti làm trung tâm, đáp số của tốn max(P[i]), i=0 2*n Với ví dụ trên, mảng P tương ứng là: T=#a#b#a#a#b#a# P=0103016103010 Nhận thấy, độ dài xâu đối xứng dài “abaaba”, vị trí P6=6 Xét ví dụ khác, S= “babcbabcbaccba”, giả sử trạng thái sau: C vị trí trung tâm, L R vị trí mở rộng tối đa quanh trung tâm C, nghĩa đoạn T[L R] đối xứng có tâm TC Ta đã tính mảng P trên, làm để tính tiếp P[i] cách hiệu quả? Với i=13, i’ vị trí đối xứng của i qua tâm C, P[ i' ] = P[ ] = Rõ ràng P[i]=1 tính đối xứng của xâu Tương tự, ta tính P[ 12 ] = P[ 10 ] = 0, P[ 13 ] = P[9 ] = 1, P[ 14 ] = P[ ] = Tiếp theo, với i=15, theo tính chất đối xứng P[15]=P[7]=7 còn khơng? Rõ ràng sai, trường hợp này, mở rộng quanh trung tâm T15 ta xâu đối xứng “a#b#c#b#a”, nghĩa P[15]=5, sao? Trên hình vẽ đường thẳng biểu diễn cho xâu đối xứng dài nhât quanh trung tâm i’ (đường bên trên) xâu đối xứng dài quanh trung tâm i, mà i i’ đối xứng qua tâm C, đường xanh nét liền thể tính đối xứng quanh tâm C, đường xanh nét đứt thể tính đối xứng xuyên qua tâm C, đường nét liền màu đỏ thể ta mở rộng tối đa phía thỏa mãn tính đối xứng của xâu Ta có: P[i’]=7, P[i]≥5 (do tính đối xứng), vị trí xét R=20, muốn tính P[i] ta phải mở rộng sang phải của R, nghĩa phải so sánh T[21] T[9], mà T[21]≠T[9] nên P[i]=5 Từ đó, ta có phần thuật tốn sau: if P[ i' ] ≤ R – i, then P[ i ] ← P[ i' ] else P[ i ] ≥ P[ i' ] (khi ta mở rợng bên phải R để tìm P[i]) Tḥt tốn Manacher tìm xâu đối xứng dài xâu S: - B1: Tính T mở rộng của xâu S bằng cách thêm ký tự ‘#’ vào chỗ trống - B2: Đặt n length(T) C:=1; // C vị trí trung tâm R:=1; // R vị trí xét bên phải trung tâm C Res := 0; // Res độ dài xâu đối xứng dài của xâu S - B3: For i:=2 to n Tính i’:=2*C-i; // I’ vị trí đối xứng bên trái trung tâm C; Nếu i // mở rộng bán kính đối xứng quanh trung tâm i while (T[i-1-P[i]]=T[i+1+P[i]]) P[i] P[i]+1; // mở rộng thành cơng đặt lại trung tâm I, bán kính mở rộng bên phải R = i+P[i] Nếu i+P[i]>R thì: C :=i; R:=i+P[i]; Nếu res < P[i] Res P[i]; // cập nhật lại kết Chương trình: // T xâu mở rộng của S T:='#'; for i:=1 to n begin T:=T+s[i]+'#'; end; n:=length(T); C:=1; // C vị trí trung tâm R:=1; // R vị trí xét bên phải trung tâm C res:=0; // res: Độ dài xâu đối xứng dài P[1]:=1; for i:=2 to n begin i1:=2*C-i; // i1 vị trí đối xứng bên trái trung tâm C if (i1>0) and (R>i) then P[i]:=min(R-i, P[i1]) else P[i]:=0; while ((i-P[i]-1)>0) and ((i+P[i]+1)R then // mở rộng thành cơng đặt lại trung tâm i begin C:=i; R:=i+P[i]; end; if res