|
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 |
©2026 mgr Jerzy Wałaszek
|
ProblemW łańcuchu s należy znaleźć wszystkie palindromy o długości większej od 1. Przez palindrom (ang. palindrome) rozumiemy łańcuch znakowy s, który czyta się tak samo w obu kierunkach. Przykład: ABBCBBA – jest palindromem Palindromy pojawiają się w genetyce (łańcuchy DNA, RNA), w tekstach, muzyce, matematyce, geometrii, fizyce itd. Stąd duże zainteresowanie informatyków w efektywnych algorytmach ich znajdowania. W badaniach genetycznych często szuka się tzw. przybliżonych palindromów ( ang. aproximate palindromes), tzn. palindromów, w których do k-znaków może być błędnych, czyli nie pasujących do dokładnego palindromu (ang. exact palindrome). Takie palindromy występują w łańcuchach DNA, w których wystąpiły różnego rodzaju błędy genetyczne. Problemem palindromów przybliżonych nie zajmujemy się w tym artykule. Wprowadźmy symbol sR, który oznacza łańcuch znakowy o odwróconej kolejności znaków w stosunku do łańcucha s. Przykład: s =
ABCD Łańcuch s jest palindromem, jeśli da się rozłożyć na dwa podłańcuchy w i wR wg poniższego schematu: s = wwR
–
palindrom parzysty (ang. even palindrome), lub Przykład: ABCDDCBA – palindrom parzysty →
ABCD+DCBA Zauważ, iż zgodnie z tą definicją palindromem jest każdy łańcuch pusty – rozkłada się na dwa puste podłańcuchy – oraz każdy łańcuch jednoliterowy – rozkłada się na znak X i dwa puste podłańcuchy. Ponieważ są to przypadki trywialne, w zadaniu wprowadzono zastrzeżenie, iż wyszukiwane palindromy muszą być co najmniej dwuznakowe. |
Pierwszy algorytm wyszukiwania palindromów jest algorytmem naiwnym. Rozkłada on dany łańcuch znakowy s na wszystkie możliwe podłańcuchy p o długości nie mniejszej niż 2 znaki i sprawdza następnie, czy dadzą się przedstawić w postaci wwR lub wXwR. Sprawdzenie polega na porównywaniu znaków od początku i od końca podłańcucha. W tym celu wykorzystuje się dwa indeksy. Jeden z nich ustawia się na pierwszym znaku podłańcucha p, a drugi na ostatnim. Następnie porównujemy wskazywane przez te indeksy znaki podłańcucha p. Jeśli znaki są różne, to podłańcuch p nie jest palindromem. Jeśli porównywane znaki są równe, to indeksy przesuwamy – lewy w prawo, a prawy w lewo. Jeśli indeksy się miną, to zachodzi jedna z dwóch równości:
p = wwR, lub p = wXwR
W takim przypadku p jest palindromem. Wyszukanie wszystkich palindromów zawartych w łańcuchu s proponowaną metodą posiada sześcienną klasę złożoności obliczeniowej O(n3), gdzie n jest długością łańcucha s.
K01: n ← |s| K02: Dla i = 0, 1, …, n-2: ; przeglądamy łańcuch s wykonuj kroki K03…K10 K03: Dla j = i+2, i+3, …, n-1: wykonuj kroki K04…K10 K04: iL ← i ; lewy indeks na pierwszym znaku podsłowa K05: iP ← j-1 ; prawy indeks na ostatnim znaku podsłowa K06: Dopóki iL < iP, ; sprawdzamy, czy podsłowo jest palindromem wykonuj kroki K07…K09 K07: Jeśli s[iL] ≠ s[iP], to następny obieg pętli K03 K08: iL ← iL+1 ; lewy indeks przesuwamy w prawo K09: iP ← iP-1 ; a prawy w lewo K10: Pisz s[i:j] ; wyprowadzamy znaleziony palindrom K11: Zakończ
|
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 40 znakowy łańcuch zbudowany ze znaków {A, B, C, D}, wyszukuje w nim wszystkie palindromy i wypisuje je z odpowiednim przesunięciem. |
Pascal// Wyszukiwanie palindromów
// Data: 9.08.2008
// (C)2020 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 40; // długość łańcucha s
var
s : ansistring;
i, j, k, iP, iL : integer;
t : boolean;
begin
// generujemy łańcuch s
randomize;
s := '';
for i := 1 to N do
s := s+chr(65+random(4));
// wypisujemy łańcuch s
writeln(s);
// szukamy palindromów
for i := 1 to N-1 do
for j := i+2 to N+1 do
begin
iL := i; iP := j-1; t := true;
while iL < iP do
begin
if s[iL] <> s[iP] then
begin
t := false; break;
end;
inc(iL); dec(iP);
end;
if t then
begin
for k := 2 to i do write(' ');
writeln(copy(s, i, j-i));
end;
end;
writeln;
end. |
C++// Wyszukiwanie palindromów
// Data: 9.08.2008
// (C)2020 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
using namespace std;
const int N = 40; // długość łańcucha s
int main()
{
string s;
int i, j, k, iP, iL;
bool t;
// generujemy łańcuch s
srand(time(NULL));
s = "";
for(i = 0; i < N; i++)
s += char(65+rand()%4);
// wypisujemy łańcuch s
cout << s << endl;
// szukamy palindromów
for(i = 0; i < N-1; i++)
for(j = i+2; j <= N; j++)
{
iL = i; iP = j-1; t = true;
while(iL < iP)
if(s[iL++] != s[iP--])
{
t = false; break;
}
if(t)
{
for(k = 0; k < i; k++)
cout << " ";
cout << s.substr(i, j-i) << endl;
}
}
cout << endl;
return 0;
} |
Basic' Wyszukiwanie palindromów
' Data: 9.08.2008
' (C)2020 mgr Jerzy Wałaszek
'---------------------------
Const N = 40 ' długość łańcucha s
Dim As String s
Dim As Integer i, j, k, iP, iL, t
' generujemy łańcuch s
Randomize
s = ""
For i = 1 To N
s += Chr(65+Cint(Rnd*3))
Next
' wypisujemy łańcuch s
Print s
' szukamy palindromów
For i = 1 To N-1
For j = i+2 To N+1
iL = i: iP = j-1: t = 1
While iL < iP
If Mid(s, iL, 1) <> Mid(s, iP, 1) Then
t = 0: Exit While
End If
iL += 1: iP -= 1
Wend
If t = 1 Then
For k = 2 To i
Print " ";
Next
Print Mid(s, i, j-i)
End If
Next
Next
Print
End |
Python
(dodatek)# Wyszukiwanie palindromów
# Data: 21.03.2024
# (C)2020 mgr Jerzy Wałaszek
#---------------------------
import random
N = 40 # długość łańcucha s
# generujemy łańcuch s
s = ""
for i in range(N):
s += chr(random.randrange(65, 69))
# wypisujemy łańcuch s
print(s)
# szukamy palindromów
for i in range(N-1):
for j in range(i+2, N+1):
i_l, i_p, t = i, j-1, True
while i_l < i_p:
if s[i_l] != s[i_p]:
t = False
break
i_l += 1
i_p -= 1
if t:
for k in range(i):
print(" ", end="")
print(s[i:j])
print()
|
| Wynik: |
DCCAACBDADCAADDBDADBCDCCBCBAACBBADBCDDAD
CC
CAAC
AA
DAD
AA
DD
DBD
BDADB
DAD
CDC
CC
CBC
BCB
AA
BB
DD
DAD
|
JavaScript<html>
<head>
<title>
Palindrom
</title>
</head>
<body>
<div style="overflow-x: auto;"
align="center">
<table
border="0"
cellpadding="4"
style="border-collapse:
collapse">
<tr>
<td nowrap>
<form
name="frm"
style="text-align: center;
background-color:
#E7E7DA">
<b>
Wyszukiwanie palindromów
</b><br/>
(C)2026 mgr
Jerzy Wałaszek
<hr>
<input
type="button"
value="Wykonaj"
name="B1"
onclick="main();">
<hr>
<b>Wynik:</b>
<div
style="overflow-x:
auto;"
align="center"
class="small">
<table
border="0"
cellpadding="0"
style="border-collapse:
collapse"
class="nomargin">
<tr>
<td valign="top">
<pre id="out">.</pre></td>
</tr>
</table>
</div>
</form>
</td>
</tr>
</table>
</div>
<script type=text/javascript
language=javascript>
// Wyszukiwanie palindromów
// Data: 9.08.2008
// (C)2008 mgr Jerzy Wałaszek
//---------------------------
function main()
{
let N = 40;
let s,i,j,k,iP,iL,t,tx;
// generujemy łańcuch s
s = "";
for(i = 0; i < N; i++)
s += String.fromCharCode(65 +
Math.floor(
Math.random() * 4));
// wypisujemy łańcuch s
tx = "<b>" + s + "<br/>";
// szukamy palindromów
for(i = 0; i < N - 1; i++)
for(j = i + 2; j <= N; j++)
{
iL = i;
iP = j - 1;
t = true;
while(iL < iP)
if(s.charAt(iL++) !=
s.charAt(iP--))
{
t = false;
break;
}
if(t)
{
for(k = 0; k < i; k++)
tx += " ";
tx += s.substr(i,
j - i) + "<br/>";
}
}
tx += "</b>";
document.getElementById("out")
.innerHTML = tx;
}
</script>
</body>
</html>
|
Drugie podejście do rozwiązania problemu wyszukiwania wszystkich palindromów w łańcuchu znakowym s opiera się na własnościach palindromów. Przedstawiony tutaj algorytm został opracowany w 1975 przez Glenna Manachera z Computer Center and Department of Information Engineering, University of Illinois, Chicago, IL. Do opisu algorytmu Manachera wprowadzimy kilka nowych pojęć.
Niech pP będzie palindromem
parzystym o postaci
pP = wwR,
gdzie w
jest niepustym podłańcuchem.
Niech pN będzie palindromem nieparzystym o postaci
pN = wXwR.
Promieniem rP palindromu p będziemy nazywali długość podsłowa w, czyli rP = |w|
Palindrom parzysty pP posiada zawsze długość |pP| = 2rP. Palindrom nieparzysty pN posiada zawsze długość |pN| = 2rP+1.
Środkiem palindromu p jest pozycja is = rP – jest to pozycja pierwszego znaku za słowem w (można również definiować środek palindromu jako pozycję ostatniego znaku podsłowa w, lecz sądzę, iż nasz sposób jest lepszy, gdyż nie wymaga wprowadzania żadnych zmian dla palindromów nieparzystych) Dla palindromu parzystego środek wypadnie na pierwszym znaku wR natomiast dla palindromu nieparzystego środek wypadnie na znaku X:
pP[rP] = wR[0] pN[rP] = X
Algorytm Manachera nie wyznacza wszystkich palindromów, jak robi to algorytm naiwny, lecz maksymalne palindromy, których środki występują na kolejnych pozycjach znakowych przeszukiwanego łańcucha s. Dzięki takiemu podejściu redukujemy złożoność obliczeniową fazy przeszukiwania łańcucha s. Mając maksymalny palindrom możemy bez problemów wyznaczyć zawarte w nim podpalindromy. Wykorzystujemy tutaj własność symetrii palindromu:
Przykład:
| rP | palindrom parzysty | palindrom nieparzysty |
| 4 | ABCDDCBA | ABCDADCBA |
| 3 | BCDDCB | BCDADCB |
| 2 | CDDC | CDADC |
| 1 | DD | DAD |
Dla danego łańcucha s algorytm Manachera tworzy tablicę dwuwymiarową R:
R[0, …] – promienie palindromów parzystych R[1, …] – promienie palindromów nieparzystych
Indeksy tych tablic określają kolejne pozycje znakowe w łańcuchu s, natomiast elementy tablic zawierają maksymalne promienie palindromów o środkach na danej pozycji znakowej.
Używając w odpowiedni sposób tablicy R oraz własności symetrii palindromu algorytm Manachera wykorzystuje sprytnie informację o wcześniej wyznaczonych promieniach palindromów maksymalnych do wyszukiwania następnych palindromów. Otóż po wyznaczeniu promienia rP palindromu na pozycji i-tej w łańcuchu s, sprawdzane są promienie palindromów na kolejnych pozycjach poprzedzających pozycję i-tą w obszarze podsłowa w – tutaj algorytm wymaga dwóch wersji – osobnej dla palindromów parzystych i osobnej dla nieparzystych. Zasada jest identyczna dla obu wersji. Rozważmy zatem możliwe przypadki (dla palindromu parzystego):

Na pozycji i-k, k = 1, 2, …, rP, promień palindromu wynosi 0 – czyli nie istnieje palindrom o środku na pozycji i-k. Skoro tak, to przez symetrię wnioskujemy, iż na pozycji lustrzanej i+k również nie będzie żadnego palindromu. Pozycja i+k możne zostać pominięta przy dalszym wyszukiwania palindromów.

Na pozycji i-k jest palindrom o promieniu r < rP-k. Taki palindrom w całości zawiera się wewnątrz rozważanego palindromu i co więcej, nie styka się z jego brzegiem. Poprzez symetrię wnioskujemy, iż na pozycji i+k również musi występować taki sam palindrom, którego już dalej nie da się rozszerzyć. Pozycji i+k nie musimy już dalej sprawdzać.

Na pozycji i-k jest palindrom o promieniu r > rP-k. Taki palindrom wykracza z lewej strony poza obszar rozważanego palindromu. Na pozycji i+k znajduje się palindrom o promieniu r = rP-k i palindromu tego nie da się już rozszerzyć. Wyjaśnienie tego faktu jest bardzo proste: gdyby palindrom na pozycji i+k posiadał większy promień niż wyliczone r, to również z uwagi na symetrię przeglądany palindrom posiadałby promień większy od rP, a przecież jest to palindrom maksymalny. Pozycję i+k również możemy pominąć.

Pozostał ostatni przypadek – na pozycji i-k występuje palindrom o promieniu r = rP-k. Taki sam palindrom musi być na pozycji i+k, jednakże w tym przypadku palindrom ten może być rozszerzalny. Pozycję i+k musimy zatem sprawdzić na obecność palindromu o promieniu większym od r.
Z powyższych rozważań otrzymujemy następujący algorytm działający w czasie liniowym O(n):
Wszystkie palindromy zawarte w łańcuchu s.
K01: n ← |s| ; obliczamy długość łańcucha s K02: s ← w1+s+w2 ; na początku i na końcu s dodajemy wartowników, w1 ≠ w2 K03: Dla j = 0, 1: ; wyznaczamy promienie palindromów parzystych i nieparzystych wykonuj kroki K04…K17 K04: R[j, 0] ← 0 ; pierwszy promień jest zawsze zerowy K05: i ← 1 ; ustawiamy i na pierwszym znaku s za wartownikiem w1 K06: rP ← 0 ; początkowy promień palindromu jest zerowy K07: Dopóki i ≤ n: ; przechodzimy przez kolejne środki palindromów wykonuj kroki K08…K17 K08: Dopóki s[i-rP-1] = s[i+j+rP]: ; wyznaczamy promień maksymalnego wykonuj rP ← rP+1 ; palindromu o środku na pozycji i-tej K09: R[j, i] ← rP ; wyliczony promień zapamiętujemy w tablicy R K10: k ← 1 ; przeglądamy promienie wcześniejszych palindromów wewnętrznych K11: Jeśli R[j, i-k] = rP-k, ; sprawdzamy ostatni przypadek to idź do kroku K16 K12: Jeśli k ≥ rP, ; musimy być wewnątrz palindromu to idź do kroku K16 K13: R[j, i+k] ← min(R[j, i-k], rP-k) ; wyznaczamy promień lustrzanego ; palindromu wewnętrznego K14: k ← k+1 ; przechodzimy do następnego palindromu wewnętrznego K15: Idź do kroku K11 K16: rP ← max(rP-k, 0) ; wyznaczamy promień początkowy palindromu K17: i ← i+k ; pomijamy wyliczone palindromy lustrzane K18: s ← s[1:n ] ; usuwamy wartowników w1 i w2 z łańcucha s K19: Dla i = 1, 2, …, n: ; wyprowadzamy kolejne palindromy parzyste i nieparzyste wykonuj kroki K20…K21 K20: Dla j = 0, 1: wykonuj krok K21 K21: Dla rP = R[j, i], R[j, i]-1, …, 1, wykonuj: pisz s[i-rP-1:i+2rP+j] K22: Zakończ
|
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 40 znakowy łańcuch zbudowany ze znaków {A, B, C, D}, wyszukuje w nim wszystkie palindromy i wypisuje je z odpowiednim przesunięciem. |
Pascal// Wyszukiwanie palindromów
// algorytmem Manachera
// Data: 12.08.2008
// (C)2020 mgr Jerzy Wałaszek
//---------------------------
program prg;
uses Math;
const N = 40; // długość łańcucha s
var
s : ansistring;
i, j, rp, k : integer;
R : array [0..1, 1..N+1] of integer;
begin
// generujemy łańcuch s
randomize;
s := '';
for i := 1 to N do
s := s+chr(65+random(4));
// wypisujemy łańcuch s
writeln(s);
// szukamy palindromów
s := '@'+s+'#'; // wstawiamy wartowników
for j := 0 to 1 do
begin
R[j, 1] := 0; i := 2; rp := 0;
while i <= N+1 do
begin
while s[i-rp-1] = s[i+j+rp] do
inc(rp);
R[j, i] := rp;
k := 1;
while(R[j, i-k] <> rp-k) and (k < rp) do
begin
R[j, i+k] := min(R[j, i-k], rp-k);
inc(k);
end;
rp := max(rp-k, 0);
inc (i, k);
end;
end;
s := copy(s, 2, N); // usuwamy wartowników
// prezentujemy wyliczone palindromy
for i := 2 to N+1 do
begin
for j := 0 to 1 do
for rp := R[j, i] downto 1 do
begin
for k := 3 to i-rp do write(' ');
writeln(copy(s, i-rp-1, 2*rp+j));
end;
end;
writeln;
end. |
C++// Wyszukiwanie palindromów
// algorytmem Manachera
// Data: 12.08.2008
// (C)2020 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
using namespace std;
const int N = 40; // długość łańcucha s
int main()
{
string s;
int i, j, rp, k, R[2][N+1];
// generujemy łańcuch s
srand(time(NULL));
s = "";
for(i = 0; i < N; i++)
s += char(65+rand()%4);
// wypisujemy łańcuch s
cout << s << endl;
// szukamy palindromów
s = "@"+s+"#"; // wstawiamy wartowników
for(j = 0; j <= 1; j++)
{
R[j][0] = rp = 0; i = 1;
while(i <= N)
{
while(s[i-rp-1] == s[i+j+rp]) rp++;
R[j][i] = rp;
k = 1;
while((R[j][i-k] != rp-k) && (k < rp))
{
R[j][i+k] = min(R[j][i-k], rp-k);
k++;
}
rp = max(rp-k, 0);
i += k;
}
}
s = s.substr(1, N); // usuwamy wartowników
// prezentujemy wyliczone palindromy
for(i = 1; i <= N; i++)
{
for(j = 0; j <= 1; j++)
for(rp = R[j][i]; rp > 0; rp--)
{
for(k = 1; k < i-rp; k++)
cout << " ";
cout << s.substr(i-rp-1, 2*rp+j)
<< endl;
}
}
cout << endl;
return 0;
} |
Basic' Wyszukiwanie palindromów
' algorytmem Manachera
' Data: 12.08.2008
' (C)2020 mgr Jerzy Wałaszek
'---------------------------
Function max(Byval a As Integer, _
Byval b As Integer) _
As Integer
If a > b Then
Return a
Else
Return b
End If
End Function
Function min(Byval a As Integer, _
Byval b As Integer) _
As Integer
If a < b Then
Return a
Else
Return b
End If
End Function
Const N = 40 ' długość łańcucha s
Dim As String s
Dim As Integer i, j, rp, k, R(1, N+1)
' generujemy łańcuch s
Randomize
s = ""
For i = 1 To N
s += Chr(65+Cint(Rnd*3))
Next
' wypisujemy łańcuch s
Print s
' szukamy palindromów
s = "@"+s+"#" ' wstawiamy wartowników
For j = 0 To 1
R(j, 1) = 0: i = 2: rp = 0
While i <= N+1
While Mid(s, i-rp-1, 1) = Mid(s, i+j+rp, 1)
rp += 1
Wend
R(j, i) = rp
k = 1
while(R(j, i-k) <> rp-k) And (k < rp)
R(j, i+k) = min(R(j, i-k), rp-k)
k += 1
Wend
rp = max(rp-k, 0)
i += k
Wend
Next
s = Mid(s, 2, N) ' usuwamy wartownikw
' prezentujemy wyliczone palindromy
For i = 2 To N+1
For j = 0 To 1
For rp = R(j, i) To 1 Step -1
For k = 3 To i-rp
Print " ";
Next
Print Mid(s, i-rp-1, 2*rp+j)
Next
Next
Next
Print
End |
Python
(dodatek)# Wyszukiwanie palindromów
# algorytmem Manachera
# Data: 23.03.2024
# (C)2024 mgr Jerzy Wałaszek
#---------------------------
import random
N = 40 # długość łańcucha s
# tworzymy tablicę R
r = [[0 for j in range(N+1)] for i in range(2)]
# generujemy łańcuch s
s = ""
for i in range(N):
s += chr(random.randrange(65, 69))
# wypisujemy łańcuch s
print(s)
# szukamy palindromów
s = "@"+s+"#" # wstawiamy wartowników
for j in range(2):
r[j][0], rp, i = 0, 0, 1
while i <= N:
while s[i-rp-1] == s[i+j+rp]:
rp += 1
r[j][i], k = rp, 1
while (r[j][i-k] != rp-k) and (k < rp):
r[j][i+k] = min(r[j][i-k], rp-k)
k += 1
rp = max(rp-k, 0)
i += k
s = s[1:-1] # usuwamy wartowników
# prezentujemy wyliczone palindromy
for i in range(1, N+1):
for j in range(2):
for rp in range(r[j][i], 0, -1):
for k in range(1, i-rp):
print(" ", end="")
print(s[i-rp-1:i-1+rp+j])
print()
|
![]() |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2026 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.