Serwis Edukacyjny w I-LO w Tarnowie Materiały dla uczniów liceum |
Wyjście Spis treści Wstecz Dalej Autor artykułu: mgr Jerzy Wałaszek |
©2024 mgr Jerzy Wałaszek |
ProblemDla danych, niepustych łańcuchów tekstowych s1 i s2 należy wyznaczyć najdłuższy wspólny podłańcuch s. W porównaniu z poprzednimi zadaniami ten
problem jest dosyć trudny, ponieważ nie wiemy, czego szukamy. Nie
możemy zatem w normalny sposób sprawdzić, czy to coś występuje
jednocześnie w łańcuchach |
Pierwsze rozwiązanie problemu wyszukiwania najdłuższego
wspólnego podłańcucha (ang. the longest common
substring – LCS)
jest rozwiązaniem nieoptymalnym. Nasz algorytm będzie się starał dopasowywać jak
największe fragmenty
Wybieramy kolejne znaki
Tak określony algorytm znajdowania najdłuższego wspólnego podciągu posiada
sześcienną klasę złożoności obliczeniowej
Przykład:
Wyszukać najdłuższy wspólny podłańcuch dla:
s1
= AAABBA
s2 = ABAABBAAA
Lp. | Kolejne dopasowania s2 do s1 | Najdłuższy wspólny podłańcuch |
1. |
A A A B B A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A |
AAA |
2. |
A A A B B A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A |
AABBA |
3. |
A A A B B A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A |
ABBA |
4. |
A A A B B A A B A A B B A A A A B A A B B A A A A B A A B B A A A |
BBA |
5. |
A A A B B A A B A A B B A A A A B A A B B A A A A B A A B B A A A |
BA |
6. |
A A A B B A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A A B A A B B A A A |
A |
K01: m ← |s1| ; obliczamy długość łańcucha s1 K02: n ← |s2| ; obliczamy długość łańcucha s2 K03: s1 ← wartownik1+s1+wartownik1 ; dodajemy wartownika do s1 K04: s2 ← wartownik2+s2+wartownik2 ; dodajemy innego wartownika do s2 K05: lmax ← 0 ; zerujemy długość podłańcucha K06: Dla i = 1, 2, …, m: wykonuj kroki K07…K14 K07: Dla j = 1, 2, …, n: wykonuj kroki K08…K14 K08: Jeśli s1[i] ≠ s2[j], ; szukamy zgodnego znaku w s1 i s2 to następny obieg pętli K07 K09: lm ← 1 ; ustawiamy wstępną długość podłańcucha K10: Dopóki s1[i+lm] = s2[j+lm]: ; rozszerzamy podciąg w prawo wykonuj lm ← lm+1 K11: Jeśli lm ≤ lmax, ; sprawdzamy, czy podłańcuch jest to następny obieg pętli K07 ; dłuższy od zapamiętanego K12 lmax ← lm ; jeśli tak, zapamiętujemy go K13: p1 ← i-1 ; pozycja skorygowana z uwagi na strażnika K14: p2 ← j-1 ; pozycja skorygowana z uwagi na strażnika K15: Usuń wartowników z s1 i s2 ; przywracamy stan s1 i s2 K16: Zakończ z wynikiem lmax, p1, p2
Uwaga: Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób korzystania z nich. |
Program generuje dwa losowe
łańcuchy |
Pascal// Wyszukiwanie najdłuższego podłańcucha // Data: 10.07.2008 // (C)2020 mgr Jerzy Wałaszek //-------------------------------------- program prg; var s1, s2 : ansistring; i, j, p1, p2, lm, lmax : integer; begin randomize; // generujemy łańcuchy s1 i s2 s1 := ''; s2 := ''; for i := 1 to 40 do begin s1 := s1+char(65+random(2)); s2 := s2+char(65+random(2)); end; // wypisujemy łańcuchy s1 i s2 writeln('s1 = ', s1); writeln('s2 = ', s2); // szukamy najdłuższego wspólnego podłańcucha s1 := '$'+s1+'$'; // dodajemy wartowników do s1 s2 := '#'+s2+'#'; // dodajemy wartowników do s2 lmax := 0; for i := 2 to 41 do for j := 2 to 41 do if s1[i] = s2[j] then begin lm := 1; while s1[i+lm] = s2[j+lm] do inc(lm); if lm > lmax then begin lmax := lm; p1 := i-1; p2 := j-1; end; end; s1 := copy(s1, 2, 40); // usuwamy wartowników z s1 s2 := copy(s2, 2, 40); // usuwamy wartowników z s2 // prezentujemy wyniki writeln; if lmax = 0 then writeln('BRAK') else begin repeat if p1 > p2 then begin s2 := ' '+s2; inc(p2); end else if p2 > p1 then begin s1 := ' '+s1; inc(p1); end; until p1 = p2; writeln(s1); for i := 1 to p1-1 do write(' '); writeln(copy(s1, p1, lmax), ' : ', lmax); writeln(s2); end; end. |
// Wyszukiwanie najdłuższego podłańcucha // Data: 10.07.2008 // (C)2020 mgr Jerzy Wałaszek //-------------------------------------- #include <iostream> #include <string> #include <cstdlib> #include <ctime> using namespace std; int main() { string s1, s2; int i, j, p1, p2, lm, lmax; srand(time(NULL)); // generujemy łańcuchy s1 i s2 s1 = ""; s2 = ""; for(i = 0; i < 40; i++) { s1 += 65+rand() % 2; s2 += 65+rand() % 2; } // wypisujemy łańcuchy s1 i s2 cout << "s1 = " << s1 << endl << "s2 = " << s2 << endl; // szukamy najdłuższego wspólnego podłańcucha s1 = "$"+s1+"$"; // dodajemy wartowników do s1 s2 = "#"+s2+"#"; // dodajemy wartowników do s2 lmax = 0; for(i = 1; i <= 40; i++) for(j = 1; j <= 40; j++) if(s1[i] == s2[j]) { lm = 1; while(s1[i+lm] == s2[j+lm]) lm++; if(lm > lmax) { lmax = lm; p1 = i-1; p2 = j-1; } } s1 = s1.substr(1, 40); // usuwamy wartowników z s1 s2 = s2.substr(1, 40); // usuwamy wartowników z s2 // prezentujemy wyniki cout << endl; if(lmax == 0) cout << "BRAK\n"; else { do { if(p1 > p2) { s2 = " "+s2; p2++; } else if(p2 > p1) { s1 = " "+s1; p1++; } } while(p1 != p2); cout << s1 << endl; for(i = 0; i < p1; i++) cout << " "; cout << s1.substr(p1, lmax) << ": " << lmax << endl << s2 << endl; } return 0; } |
Basic' Wyszukiwanie najdłuższego podłańcucha ' Data: 10.07.2008 ' (C)2020 mgr Jerzy Wałaszek '-------------------------------------- Dim As String s1, s2 Dim As Integer i, j, p1, p2, lm, lmax Randomize ' generujemy łańcuchy s1 i s2 s1 = "": s2 = "" For i = 1 To 40 s1 += Chr(65+Cint(Rnd)) s2 += Chr(65+Cint(Rnd)) Next ' wypisujemy łańcuchy s1 i s2 Print "s1 = ";s1 Print "s2 = ";s2 ' szukamy najdłuższego wspólnego podłańcucha s1 = "$"+s1+"$" ' dodajemy wartowników do s1 s2 = "#"+s2+"#" ' dodajemy wartowników do s2 lmax = 0 For i = 2 To 41 For j = 2 To 41 If Mid(s1, i, 1) = Mid(s2, j, 1) Then lm = 1 While Mid(s1, i+lm, 1) = Mid(s2, j+lm, 1) lm += 1 Wend If lm > lmax Then lmax = lm p1 = i-1 p2 = j-1 End If End If Next Next s1 = Mid(s1, 2, 40) ' usuwamy wartowników z s1 s2 = Mid(s2, 2, 40) ' usuwamy wartowników z s2 ' prezentujemy wyniki Print If lmax = 0 Then Print "BRAK" Else Do If p1 > p2 Then s2 = " "+s2: p2 += 1 Elseif p2 > p1 Then s1 = " "+s1: p1 += 1 End If Loop Until p1 = p2 Print s1 For i = 1 To p1-1 Print " "; Next Print Mid(s1, p1, lmax);": ";lmax Print s2 End If End |
Python
(dodatek)# Wyszukiwanie najdłuższego podłańcucha # Data: 15.03.2024 # (C)2024 mgr Jerzy Wałaszek #-------------------------------------- import random p1,p2 = 0, 0 # generujemy łańcuchy s1 i s2 s1, s2 = "", "" for i in range(40): s1 += chr(random.randrange(65, 67)) s2 += chr(random.randrange(65, 67)) # wypisujemy łańcuchy s1 i s2 print("s1 = %s\ns2 = %s" % (s1, s2)) # szukamy najdłuższego wspólnego podłańcucha s1 = "$"+s1+"$" # dodajemy wartowników do s1 s2 = "#"+s2+"#" # dodajemy wartowników do s2 lmax = 0 for i in range(1, 41): for j in range(1, 41): if s1[i] == s2[j]: lm = 1 while s1[i+lm] == s2[j+lm]: lm += 1 if lm > lmax: lmax = lm p1, p2 = i-1, j-1 s1 = s1[1:40] # usuwamy wartowników z s1 s2 = s2[1:40] # usuwamy wartowników z s2 # prezentujemy wyniki print() if not lmax: print("BRAK") else: while True: if p1 > p2: s2 = " "+s2 p2 += 1 elif p2 > p1: s1 = " "+s1 p1 += 1 if p1 == p2: break print(s1) for i in range(p1): print(" ", end="") print(s1[p1:p1+lmax], ":", lmax) print(s2) |
Wynik: |
s1 = BBABABBABBBBABBAABAABBBAAAABABBAAAAABBBB s2 = AAAABBBBBBBAAAABBBABBABBABBAAABAAABBBABB BBABABBABBBBABBAABAABBBAAAABABBAAAAABBB BBBAAAAB : 8 AAAABBBBBBBAAAABBBABBABBABBAAABAAABBBAB |
Drugie rozwiązanie wykorzystuje programowanie dynamiczne, gdzie zapamiętujemy
w osobnej tablicy długości pasujących podłańcuchów w obu łańcuchach s1
i s2. Dzięki temu klasa złożoności obliczeniowej
algorytmu spada do
Oznaczmy przez
Jeśli s1[i] ≠ s2[j], to L[i+1, j+1] ← 0 Jeśli s1[i] = s2[j], to L[i+1, j+1] ← 1+L[i, j]
Wynika z tego, iż długość wspólnego podłańcucha rośnie o 1, jeśli znaki
na pozycjach i oraz
j w obu łańcuchach są zgodne.
W przeciwnym razie długość zeruje się. Wyliczone długości są zapamiętywane
K01: m ← |s1| ; obliczamy długość łańcucha s1 K02: n ← |s2| ; obliczamy długość łańcucha s2 K03: Utwórz tablicę L[m+1, n+1] K04: Dla i = 0, 1, …, m: ; zerujemy pierwszą kolumnę tablicy L wykonuj L[i, 0] ← 0 K05: Dla j = 0, 1, …, n: ; zerujemy pierwszy wiersz tablicy L wykonuj L[0, j] ← 0 K06: lmax ← 0 ; zerujemy największą długość wspólnego podłańcucha K07: Dla i = 0, 1, …, m-1: ; przebiegamy przez kolejne znaki s1 wykonuj kroki K08…K16 K08: Dla j = 0, 1, …, n-1: ; przebiegamy przez kolejne znaki s2 wykonuj kroki K09…K16 K09: Jeśli s1[i] = s2[j], ; sprawdzamy równość znaków w łańcuchach to idź do kroku K12 K10: L[i+1, j+1] ← 0 ; znaki są różne, długość zerujemy K11: Następny obieg pętli K07 K12: L[i+1, j+1] ← 1+L[i, j] ; obliczamy nową długość wspólnego podłańcucha K13: Jeśli L[i+1, j+1] ≤ lmax, ; sprawdzamy, czy jest to najdłuższy podłańcuch to następny obieg pętli K07 K14: lmax ← L[i+1, j+1] ; zapamiętujemy długość wspólnego podłańcucha K15: p1 ← i-lmax+1 ; oraz jego pozycję w s1 K16 p2 ← j-lmax+1 ; i w s2 K17: Zakończ z wynikiem lmax, p1, p2
Uwaga: Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób korzystania z nich. |
Program generuje dwa losowe łańcuchy po 40 znaków zbudowane z liter A i B. Wyszukuje w nich najdłuższy wspólny podłańcuch i wypisuje go odpowiednio pozycjonując łańcuchy względem siebie i wyznaczonego podłańcucha. Jeśli łańcuchy nie posiadają wspólnego podłańcucha, wypisywane jest słowo BRAK. |
Pascal// Wyszukiwanie najdłuższego podłańcucha // Data: 16.07.2008 // (C)2020 mgr Jerzy Wałaszek //-------------------------------------- program prg; var s1, s2 : ansistring; i, j, p1, p2, lmax : integer; L : array [0..40, 0..40] of integer; begin randomize; // generujemy łańcuchy s1 i s2 s1 := ''; s2 := ''; for i := 1 to 40 do begin s1 := s1+char(65+random(2)); s2 := s2+char(65+random(2)); end; // wypisujemy łańcuchy s1 i s2 writeln('s1 = ', s1); writeln('s2 = ', s2); // szukamy najdłuższego wspólnego podłańcucha for i := 0 to 40 do begin L[i, 0] := 0; L[0, i] := 0; end; lmax := 0; for i := 1 to 40 do for j := 1 to 40 do if s1[i] <> s2[j] then L[i, j] := 0 else begin L[i, j] := 1+L[i-1, j-1]; if L[i, j] > lmax then begin lmax := L[i, j]; p1 := i-lmax+1; p2 := j-lmax+1; end; end; // prezentujemy wyniki writeln; if lmax = 0 then writeln('BRAK') else begin repeat if p1 > p2 then begin s2 := ' '+s2; inc(p2); end else if p2 > p1 then begin s1 := ' '+s1; inc(p1); end; until p1 = p2; writeln(s1); for i := 1 to p1-1 do write(' '); writeln(copy(s1, p1, lmax), ' : ', lmax); writeln(s2); end; end. |
// Wyszukiwanie najdłuższego podłańcucha // Data: 16.07.2008 // (C)2020 mgr Jerzy Wałaszek //-------------------------------------- #include <iostream> #include <string> #include <cstdlib> #include <ctime> using namespace std; int main() { string s1, s2; int i, j, p1, p2, lmax, L[41][41]; srand(time(NULL)); // generujemy łańcuchy s1 i s2 s1 = ""; s2 = ""; for(i = 0; i < 40; i++) { s1 += 65+rand() % 2; s2 += 65+rand() % 2; } // wypisujemy łańcuchy s1 i s2 cout << "s1 = " << s1 << endl << "s2 = " << s2 << endl; // szukamy najdłuższego wspólnego podłańcucha for(i = 0; i <= 40; i++) L[i][0] = L[0][i] = 0; lmax = 0; for(i = 0; i < 40; i++) for(j = 0; j < 40; j++) if(s1[i] != s2[j]) L[i+1][j+1] = 0; else { L[i+1][j+1] = 1+L[i][j]; if(L[i+1][j+1] > lmax) { lmax = L[i+1][j+1]; p1 = i-lmax+1; p2 = j-lmax+1; } } // prezentujemy wyniki cout << endl; if(lmax == 0) cout << "BRAK\n"; else { do { if(p1 > p2) { s2 = " "+s2; p2++; } else if(p2 > p1) { s1 = " "+s1; p1++; } } while(p1 != p2); cout << s1 << endl; for(i = 0; i < p1; i++) cout << " "; cout << s1.substr(p1, lmax) << " : " << lmax << endl << s2 << endl; } return 0; } |
Basic' Wyszukiwanie najdłuższego podłańcucha ' Data: 16.07.2008 ' (C)2020 mgr Jerzy Wałaszek '-------------------------------------- Dim As String s1, s2 Dim As Integer i, j, p1, p2, lmax, L(40, 40) Randomize ' generujemy łańcuchy s1 i s2 s1 = "": s2 = "" For i = 1 To 40 s1 += Chr(65+Cint(Rnd)) s2 += Chr(65+Cint(Rnd)) Next ' wypisujemy łańcuchy s1 i s2 Print "s1 = ";s1 Print "s2 = ";s2 ' szukamy najdłuższego wspólnego podłańcucha For i = 0 To 40 L(i, 0) = 0 L(0, i) = 0 Next lmax = 0 For i = 1 To 40 For j = 1 To 40 If Mid(s1, i, 1) <> Mid(s2, j, 1) Then L(i, j) = 0 Else L(i, j) = 1+L(i-1, j-1) If L(i, j) > lmax Then lmax = L(i, j) p1 = i-lmax+1 p2 = j-lmax+1 End If End If Next Next ' prezentujemy wyniki Print If lmax = 0 Then Print "BRAK" Else Do If p1 > p2 Then s2 = " "+s2: p2 += 1 Elseif p2 > p1 Then s1 = " "+s1: p1 += 1 End If Loop Until p1 = p2 Print s1 For i = 1 To p1-1 Print " "; Next Print Mid(s1, p1, lmax);" : ";lmax Print s2 End If End |
Python
(dodatek)# Wyszukiwanie najdłuższego podłańcucha # Data: 16.03.2024 # (C)2024 mgr Jerzy Wałaszek #-------------------------------------- import random N = 40 # Długość łańcuchów p1, p2 = 0, 0 # konstruujemy tablicę dwuwymiarową t_l = [[0 for j in range(N+1)] for i in range(41)] # generujemy łańcuchy s1 i s2 s1 = "" s2 = "" for i in range(N): s1 += chr(random.randrange(65, 67)) s2 += chr(random.randrange(65, 67)) # wypisujemy łańcuchy s1 i s2 print("s1 =", s1) print("s2 =", s2) # szukamy najdłuższego wspólnego podłańcucha lmax = 0 for i in range(40): for j in range(40): if s1[i] != s2[j]: t_l[i+1][j+1] = 0 else: t_l[i+1][j+1] = 1+t_l[i][j] if t_l[i+1][j+1] > lmax: lmax = t_l[i+1][j+1] p1 = i-lmax+1 p2 = j-lmax+1 # prezentujemy wyniki print() if lmax == 0: print("BRAK") else: while True: if p1 > p2: s2 = " "+s2 p2 += 1 elif p2 > p1: s1 = " "+s1 p1 += 1 else: break print(s1) for i in range(p1): print(" ", end="") print(s1[p1:p1+lmax], ":", lmax) print(s2) print() |
Zwróć uwagę, iż w prezentowanym powyżej algorytmie stosujemy dwuwymiarową
tablicę
K01: m ← |s1| ; obliczamy długość łańcucha s1 K02: n ← |s2| ; obliczamy długość łańcucha s2 K03: Twórz tablicę L[2, n+1] K04: Dla j = 0, 1, …, n: ; zerujemy pierwszy wiersz tablicy L wykonuj L[0, j] ← 0 K05: lmax ← 0 ; zerujemy największą długość wspólnego podłańcucha K06: Dla i = 0, 1, …, m-1: ; przebiegamy przez kolejne znaki s1 wykonuj kroki K07…K16 K07: Dla j = 0, 1, …, n-1: ; przebiegamy przez kolejne znaki s2 wykonuj kroki K08…K15 K08: Jeśli s1[i] = s2[j], ; sprawdzamy równość znaków to idź do kroku K11 K09: L[1, j+1] ← 0 ; znaki są różne, długość zerujemy K10: Następny obieg pętli K06 K11: L[1, j+1] ← 1+L[0, j] ; obliczamy nową długość wspólnego podłańcucha K12: Jeśli L[1, j+1] ≤ lmax, ; sprawdzamy, czy jest to najdłuższy podłańcuch to następny obieg pętli K06 K13: lmax ← L[1, j+1] ; zapamiętujemy długość wspólnego podłańcucha K14: p1 ← i-lmax+1 ; oraz jego pozycję w s1 K15: p2 ← j-lmax+1 ; i w s2 K16: Dla j = 0, 1, …, n: ; przepisujemy wiersz 1 do wiersza 0 tablicy L wykonuj L[0, j] ← L[1, j] K17: Zakończ z wynikiem lmax, p1, p2
Zmodyfikowanie przykładowych programów wg nowego algorytmu pozostawiam czytelnikowi jako proste ćwiczenie w programowaniu. A może wymyślisz sposób przełączania wierszy tablicy L bez konieczności ich przepisywania – zastanów się nad dwoma dodatkowymi zmiennymi indeksującymi wiersze tej tablicy.
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2024 mgr Jerzy Wałaszek |
Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email:
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.