KMP アルゴリズムの詳細な説明と練習問題

KMPアルゴリズム

1. 簡単な紹介

KMP アルゴリズムは文字列マッチングに使用され、正常に一致した文字列の開始位置を返します。時間計算量は O(N) です。

IndexOf 関数は Java に付属しており、indexOf 関数は KMP の最適化バージョンであり、定数時間のみを最適化します。

2.次の配列

効果

  • 暴力的なマッチングを行わずにマッチングプロセスをスピードアップできます
  • 次の配列は、プレフィックス文字列とサフィックス文字列の一致する最大長を保存します (文字列自体は含まれません)。

実装プロセス

  • next[0]デフォルト値は -1 です。これは人為的に指定され、以降の判定に使用されます。
  • next[1]=0i=1場合、[0,i-1]範囲内に文字が 1 つしかないため、プレフィックスの長さとサフィックスの長さは 0 になります。これは、プレフィックスとサフィックスの長さには計算時にそれ自体が含まれないためです。
  • i=2文字列を最初 からたどると、次の 3 つの一般的な状況がわかります。
    • ケース 1:i-1その位置の文字が、一致するプレフィックスの開始位置に等しい、next[i]プレフィックスの開始位置に 1 を加えたものに等しい、式next[i]=++index
    • ケース 2: 接頭辞と接尾辞がうまく一致しない場合は、next配列からインデックス添え字内の対応する接頭辞の位置を見つけ、式を計算します。index=next[index]
    • ケース 3: プレフィックスとサフィックスが正常に一致せず、次の配列で値を検索できなくなります。next[i]=0

グラフィカルな次の配列実装プロセス
ターゲット部分文字列ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入

次の配列コード

   vector<int> getNext(string str) {
    
    
   	// 每个位置字符串的前缀与后缀最大匹配长度,不包含整串
   	vector<int> next(str.size());
   	next[0] = -1; //人为规定,0号位置的值是-1
   	next[1] = 0;
   	int i = 2; // 从2开始遍历str
   	// index代表当前是哪个位置的字符,在和index+1也就是i位置比较
   	int index = 0; // index既用来作为下标访问,也作为值
   	while (i < next.size()) {
    
    
   		// str[i-1]代表后缀开始的位置, str[index]代表前缀开始的位置
   		// index保存了上一次匹配的最大长度, str[index]代表了当前前缀位置, 可以通过这个来进行加速匹配
   		if (str[i - 1] == str[index]) {
    
    
   			// 如果str[i-1](后缀待匹配的字符) 等于 str[index](前缀待匹配的字符) 
   			// next数组i位置的值 直接等于上次最大匹配长度+1
   			next[i++] = ++index;
   		}
   		else if (index > 0) {
    
    
   			// 后缀与前缀没有匹配成功, 并且index还可以往前找 next[index]的前缀, 也就是找当前前缀的前缀开始位置
   			index = next[index]; 
   		}
   		else{
    
    
   			// index=0, 没有前缀了, 长度记为0
   			next[i++] = 0;
   		}
   	}
   	return next;
   }

3. 主文字列と部分文字列の比較機能

プロセス

  • getNext 関数を呼び出して、部分文字列の次の配列を取得します。
  • iと を添え字として使用して、jメイン文字列str1とサブ文字列をそれぞれ走査します。str2
  • どちらでもないi場合jは 3 つあります。
  • ケース 1: メイン文字列の現在位置の文字がサブ文字列の現在位置の文字と等しく、i両方jの合計が増分されます。
  • 状況 2: 部分文字列の次の配列が -1、つまりnext[0]人為的に指定された値、またはj0 に等しい場合、マッチングが失敗したことを意味し、0 を変更せずにiインクリメントされます。j
  • ケース 3: 主文字列の現在位置の文字が部分文字列の現在位置の文字と等しくない場合、次のj>0配列内の前の接頭辞の位置を見つけます。
  • 最後jにチェックする値は、部分文字列の長さと等しいかどうかです。部分文字列の長さと等しい場合は、一致が成功したことを意味し、戻り値は、一致がメイン文字列の位置から開始されることを意味しますi-j。弦。i-j
  • 一致が失敗した場合は -1 を返します

コード

   int getIndex(string str1, string str2) {
    
    
   	vector<int> next = getNext(str2);
   	int i = 0;
   	int j = 0;
   	while (i < str1.size() && j < str2.size()) {
    
    
   		if (str1[i] == str2[j]) {
    
    
   			i++;
   			j++;
   		}else if (next[j] == -1) {
    
    
   			i++;
   		}else{
    
    
   			j = next[j];
   		}
   	}
   	if (j == str2.size()) {
    
    
   		return i - j;
   	}
   	return -1;
   }

4. コード全体

#include<iostream>
#include<string>
#include<vector>
using namespace std;

vector<int> getNext(string str) {
    
    
	// 每个位置字符串的前缀与后缀最大匹配长度,不包含整串
	vector<int> next(str.size());
	next[0] = -1; //人为规定,0号位置的值是-1
	next[1] = 0;
	int i = 2; // 从2开始遍历str
	// index代表当前是哪个位置的字符,在和index+1也就是i位置比较
	int index = 0; // index既用来作为下标访问,也作为值
	while (i < next.size()) {
    
    
		// str[i-1]代表后缀开始的位置, str[index]代表前缀开始的位置
		// index保存了上一次匹配的最大长度, str[index]代表了当前前缀位置, 可以通过这个来进行加速匹配
		if (str[i - 1] == str[index]) {
    
    
			// 如果str[i-1](后缀待匹配的字符) 等于 str[index](前缀待匹配的字符) 
			// next数组i位置的值 直接等于上次最大匹配长度+1
			next[i++] = ++index;
		}
		else if (index > 0) {
    
    
			// 后缀与前缀没有匹配成功, 并且index还可以往前找 next[index]的前缀, 也就是找当前前缀的前缀开始位置
			index = next[index]; 
		}
		else{
    
    
			// index=0, 没有前缀了, 长度记为0
			next[i++] = 0;
		}
	}
	return next;
}

int getIndex(string str1, string str2) {
    
    
	vector<int> next = getNext(str2);
	int i = 0;
	int j = 0;
	while (i < str1.size() && j < str2.size()) {
    
    
		if (str1[i] == str2[j]) {
    
    
			i++;
			j++;
		}else if (next[j] == -1) {
    
    
			i++;
		}else{
    
    
			j = next[j];
		}
	}
	if (j == str2.size()) {
    
    
		return i - j;
	}
	return -1;
}

int main() {
    
    
	// 在str1中查找有没有子串str2
	string str1 = "abbcabcccc";
	string str2 = "abcabc";
	//cin >> str1 >> str2;
	int index = getIndex(str1, str2);
	cout << index;
	return 0;
}

5. KMP に関する関連トピック

追加する最小文字数

文字列 str を指定すると、str の後に文字を追加して長い文字列を生成することしかできません。長い文字列には 2 つの str が含まれている必要があり、2 つの str の開始位置を同じにすることはできません。追加する最小文字数を見つけます。

入力説明:
元の文字列を示す行を入力します。

出力の説明:
追加する必要がある最小文字数を示す整数を出力します。

例 1
入力
123123
出力
3

例2
入力
11111
出力
1

一連の考え

  • 質問の意味に応じて、次の 3 つの状況が考えられます。
    • ケース 1: 1 文字、答えは 1、1 文字追加するだけ
    • 状況 2: 2 つの文字、2 つの文字が同じかどうかを判断します。同じであれば、答えは文字列の長さです。この文字列を追加する必要があるため、それらが異なる場合、答えは 1 です。最初の文字を追加するだけです
    • ケース 3: 複数の文字。次の配列は文字列の最後の位置にあります。次の配列の意味は、プレフィックスとサフィックスの間で一致する最大の長さであるためです。したがって、答えは文字列の長さからnext[str.length()]1 を引いたものになります。
#include<iostream>
#include<vector>
#include<string>
using namespace std;

int getNext(string str) {
    
    
	vector<int> next(str.size());
	next[0] = -1; //人为规定,0号位置的值是-1
	next[1] = 0;
	int i = 2; // 从2开始遍历str
	int val = 0; // val既用来作为下标访问,也作为值
	while (i < next.size()) {
    
    
		if (str[i - 1] == str[val]) {
    
    
			next[i++] = ++val;
		}
		else if (val > 0) {
    
    
			val = next[val]; // 取出前一个next数组的值
		}
		else {
    
    
			next[i++] = 0;
		}
	}
	return next[str.size() - 1];
}

int main() {
    
    
	string str;
	cin >> str;
	int ans = 0;
	if (str.size() == 0) {
    
    
		cout << 0;
		return 0;
	}
	else if (str.size() == 1) {
    
    
		ans = str.size() + str.size();
	}
	else if (str.size() == 2) {
    
    
		ans = str[0] == str[1] ? str.size() + 1 : str.size() + str.size();
	}
	else {
    
    
		int next = getNext(str);
		ans = str.size() + str.size() -1 - next;
	}
	ans -= str.size();
	cout << ans;
	return 0;
}

おすすめ記事

マンチャーアルゴリズムを練習問題付きで詳しく解説

おすすめ

転載: blog.csdn.net/weixin_44839362/article/details/117134881