コンピュータサイエンスにおいて、最長回文部分文字列または最長対称因子問題とは、与えられた文字列の中で、連続する部分文字列のうち、回文でもある最長の部分文字列を見つける問題です。例えば、「bananas」の最長回文部分文字列は「anana」です。最長回文部分文字列は一意であるとは限りません。例えば、「abracadabra」という文字列には、長さが3を超える回文部分文字列はありませんが、長さが3の回文部分文字列は「aca」と「ada」の2つあります。アプリケーションによっては、1つの部分文字列だけを返す、あるいは回文部分文字列の最大長を返すのではなく、すべての最大回文部分文字列(つまり、それ自体が回文であり、より大きな回文部分文字列に拡張できないすべての部分文字列)を返す必要がある場合があります。
マナカー(1975)は指定された長さの文字列の先頭に現れるすべての回文を列挙する時間アルゴリズムしかし、例えばApostolico、Breslauer & Galil (1995)が指摘しているように、同じアルゴリズムは入力文字列内の任意の場所にある最大回文部分文字列をすべて見つけるためにも使用できます。時間。したがって、それは最長回文部分文字列問題に対する時間解法。代替案時間の解は、 Jeuring (1994)とGusfield (1997)によって提供され、Gusfield は接尾辞木に基づく解を説明した。ワード RAMモデルの計算では、サイズが小さい場合、より高速なアルゴリズムが実現できる。入力アルファベットの特に、このアルゴリズムは時間を使う空間。[ 1 ]この問題に対する効率的な並列アルゴリズムも知られています。 [ 2 ]
最長回文部分文字列問題は、最長回文部分列を見つけるという別の問題と混同してはならない。
このアルゴリズムはマナチャーのアルゴリズムよりも処理速度は遅いものの、マナチャーのアルゴリズムを理解するための良い足がかりとなる。各文字を回文の中心とみなし、その中心を持つ最大の回文を決定するためにループ処理を行う。
関数の中央にあるループは、長さが奇数の回文に対してのみ機能します。偶数の長さの回文に対しては、入力文字列を変更することで機能します。入力文字列の各文字間と両端に「|」文字が挿入されます。したがって、入力「book」は「|b|o|o|k|」になります。「book」内の偶数の長さの回文「oo」は、奇数の長さの回文「|o|o|」になります。
// C言語の擬似コード。Longest_Palindrome_SLOW (文字列S 、文字列R ) {// R == S に偽文字 (例: '|') を挿入// 各文字間(外側の境界を含む)// R の各位置を中心とする最長回文の半径// 注: length(R) = length(PalindromeRadii) = 2 × length(S) + 1配列PalindromeRadii = [ 0 ,..., 0 ]中心= 0Center < length ( R )の間{// 最長の回文を決定する// 中心半径の位置から中心+半径へ移動半径= 0中心- (半径+ 1 ) > = 0かつ中心+ (半径+ 1 ) <長さ( R )かつR [中心- (半径+ 1 )] = R [中心+ (半径+ 1 )] {半径=半径+ 1}// 配列内の最長回文の半径を保存するPalindromeRadii [中心] =半径中央=中央+ 1}// longest_palindrome_in_S は max(PalindromeRadii) であることが示せる。// R[i] == '|' の場合、PalindromeRadii[i] は偶数になります。そうでない場合は、PalindromeRadii[i] を 1 増やすことができます。// これは、各境界線に余分な「|」を挿入することと同じです。// R の '|' を中心とする回文は、S の偶数回文に対応することに注意してください。// R[i] != '|' の場合、PalindromeRadii[i] は奇数 (同じ引数) であり、奇数回文に対応します。// この場合、回文の長さ// その文字を中心とするのは x=PalindromeRadii[i] でもあり、両側に (x-1)/2 文字あります。// 加えて中央の余分なもの ((x-1)/2*2+1=x)S における最長回文= max (回文半径)S で最長の回文を返す}このアルゴリズムの実行時間は外側のループが実行されます回、そして内側のループは最大で回。
以下はマナチャーのアルゴリズムの擬似コードです。このアルゴリズムは、回文の中に別の回文が含まれている場合を利用するため、以前のアルゴリズムよりも高速です。
例えば、入力文字列「abacaba」を考えてみましょう。「c」に到達するまでに、マナチャーのアルゴリズムは「c」より前の文字を中心としたすべての回文の長さを特定します。「c」では、ループを実行して「c」を中心とした最大の回文「abacaba」を特定します。この情報があれば、「c」以降のすべては「c」より前のすべてのものの鏡像のように見えます。「c」の後の「a」は、「c」の前の「a」と同じ最長の回文を持ちます。同様に、「c」の後の「b」は、 「c」の前の「b」を中心とした最長の回文の長さ以上の長さを持つ最長の回文を持ちます。考慮すべき特殊なケースはいくつかありますが、この手法によって計算速度が劇的に向上します。
// C言語の擬似コード。最長回文(文字列S 、文字列R ) {// R == S に偽文字 (例: '|') を挿入// 各文字間(外側の境界を含む)// R の各位置を中心とする最長回文の半径// 注: length(R) = length(PalindromeRadii) = 2 × length(S) + 1配列PalindromeRadii = [ 0 ,..., 0 ]中心= 0半径= 0Center < length ( R )の間{// ループの開始時点で、半径は既に下限値に設定されている// 最長半径の場合。最初の反復では、半径は 0 ですが、// 後続の反復では、値が高くなることがあります。// 中心半径から始まる最長の回文を決定します。// 中心+半径へ移動中心- (半径+ 1 ) > = 0かつ中心+ (半径+ 1 ) <長さ( R )かつR [中心- (半径+ 1 )] = R [中心+ (半径+ 1 )] {半径=半径+ 1}// 配列内の最長回文の半径を保存するPalindromeRadii [中心] =半径// 以下では、Center が増加します。// 事前に計算された値が再利用できる場合は、再利用します。// また、半径は0より大きい値に設定することもできます。旧センター=センターOldRadius =半径中央=中央+ 1// 半径のデフォルト値は、// 次のループ。半径= 0センターがOldCenter + OldRadius以下である間{// なぜなら、センターは古い回文の中にあり、// 回文内の文字は「鏡像」文字を持つ// 中心を横切って反射したデータを使用できます// 中心のミラーポイントに対して事前に計算されています。ミラーセンター=旧センター- (センター-旧センター)MaxMirroredRadius = OldCenter + OldRadius - CenterPalindromeRadii [ MirroredCenter ] < MaxMirroredRadiusの場合{// MirroredCenter を中心とした回文は完全に// OldCenter を中心とした回文の中に含まれています。したがって、// MirroredCenter と Center は同じサイズの回文を持つPalindromeRadii [ Center ] = PalindromeRadii [ MirroredCenter ]中央=中央+ 1} else if PalindromeRadii [ MirroredCenter ] > MaxMirroredRadius {// MirroredCenter の回文は、// OldCenter の回文 Center の回文は// OldCenter 回文の端で終了する。そうでない場合は、// OldCenter の回文はもっと大きいだろうPalindromeRadii [ Center ] = MaxMirroredRadius中央=中央+ 1} else { // PalindromeRadii[MirroredCenter] = MaxMirroredRadius// MirroredCenter の回文はちょうど// OldCenter を中心とする回文の端、// 中央の回文はもっと大きいかもしれません。半径を// 中央の回文の最小サイズなので、// 不必要に再チェックする半径= MaxMirroredRadius壊す}}}// 回文のサイズは半径 * 2 に等しい。ただし、// 変数 Radius は、偽の文字を横に考慮します// 中央、対応する回文のサイズは実際には 2 *// (半径 / 2) は、回文のサイズがその半径 / 2 に等しいことを意味します。// PalindromeRadii における対応する半径値S における最長回文= max (回文半径)S で最長の回文を返す}マナチャーのアルゴリズムは、回文の中に別の回文が存在する場合に、事前に計算されたデータを再利用するため、より高速です。これには3つのケースがあり、擬似コードでは「if / else if / else」文で表されます。
最初のケースは、回文がMirroredCenter「Old」回文の中に完全に含まれている場合です。この場合、回文の長さは、Center回文の長さと同じになりますMirroredCenter。たとえば、「Old」回文が「abcbpbcba」の場合、「p」の後の「c」を中心とした回文の長さは、「p」の前の「c」を中心とした回文の長さと同じでなければならないことがわかります。
2 番目のケースは、 の回文がMirroredCenter「Old」回文の外側に伸びている場合です。つまり、「左」に伸びている場合です (または、「Old」回文内のどの文字よりもインデックスが小さい文字が含まれている場合)。 「Old」回文は を中心とした最大の回文であるためOldCenter、その前後の文字が異なることがわかります。したがって、 の回文はCenter「Old」回文の境界まで正確に伸びます。なぜなら、次の文字は の回文内の文字とは異なるからですMirroredCenter。たとえば、文字列が「ababc」の場合、「Old」回文は「bab」となり、 はCenter2 番目の「b」、 はMirroredCenter最初の「b」になります。の回文はMirroredCenter「aba」であり、「Old」回文の境界を超えて広がっているため、2番目の「b」における最長の回文は「Old」回文の境界までしか伸びないことがわかります。これは、「Old」回文の後の文字が「c」ではなく「a」だった場合、「Old」回文がより長くなったであろうことからわかります。
3 番目で最後のケースは、 の回文がMirroredCenter「Old」回文の境界まで正確に伸びている場合です。この場合、「Old」回文の後の文字によって の回文が のCenter回文よりも長くなるかどうかはわかりません。しかし、 の回文はの回文より少なくとも長いことMirroredCenterはわかっています。この場合、は の回文の半径に初期化され、そこから検索が開始されます。例として、文字列「abcbpbcbp」があります。ここで、「Old」回文は「bcbpbcb」で、 は2 番目の「c」にあります。 は最初の「c」で、最長回文は「bcb」です。 の 2 番目の「c」にある最長回文は少なくともその長さでなければならず、この場合、 はそれよりも長くなります。CenterMirroredCenterRadiusMirroredCenterCenterMirroredCenterCenter
Centerこのアルゴリズムは線形時間で実行されます。これは、各外側ループの後に が厳密に増加し、合計が非減少であることからわかりますCenter + Radius。さらに、最初の内側ループの操作数は合計の増加に対して線形であり、Center + Radius2番目の内側ループの操作数は の増加に対して線形ですCenter。 および であるためCenter ≤ 2n+1、Radius ≤ n最初の内側ループと2番目の内側ループの操作の総数は です。また、外側のループにおける操作の総数(内側のループにおける操作を除く)も、したがって全体の実行時間は。