118-文字列照合のためのKMPアルゴリズムの実現

ここに画像の説明を挿入
ここに画像の説明を挿入

#include<stdio.h>
#include<stdlib.h>
#include<string.h> 
#include<assert.h>

void GetNext(const char *p, int *next)//获取k的值 
{
    
    
	next[0] = -1;
	next[1] = 0;
	int lenp = strlen(p);
	int i = 1;
	int k = 0;
	while (i + 1 < lenp)
	{
    
    
		if (k == -1 || p[i] == p[k])
		{
    
    
			next[++i] = ++k;
			/*
			next[i + 1] = k + 1;
			i++;
			k++;
			*/
		}
		else
		{
    
    
			k = next[k];
			/*
			if (k == -1)
			{
				next[i + 1] = k+1;
				i++;
				k++;
			}
			*/
		}
	}
}

// 时间复杂度O(n+m)   空间复杂度O(m)
int KMP(const char *s, const char *p, int pos)//KMP算法的实现 
{
    
    
	int lens = strlen(s);
	int lenp = strlen(p);
	if (s == NULL || p == NULL || lenp > lens)  return -1;
	int i = pos;
	int j = 0;
	int *next = (int *)malloc(sizeof(int) * lenp);
	assert(next != NULL);
	GetNext(p, next);
	while (i < lens && j < lenp)
	{
    
    
		if (j == -1 || s[i] == p[j])
		{
    
    
			i++;
			j++;
		}
		else
		{
    
    
			j = next[j];
			/*
			if (j == -1)
			{
				i++;
				j++;
			}
			*/
		}
	}
	free(next);
	if (j == lenp)
	{
    
    
		return i - j;
	}

	return -1;
}

int main()
{
    
    
	const char *s = "ababcabcdabcde";
	const char *p = "abcd";

	printf("%d\n", KMP(s, p, 6));
	return 0;
}

操作の結果は次のとおりです
ここに画像の説明を挿入
。BFメソッド
は、メイン文字列sとサブ文字列tの最初の文字から開始し、2つの文字列の文字を1つずつ比較します。文字が一致しない場合、メイン文字列はトレースします。 2番目の文字に戻り、部分文字列がトレースバックします。最初の文字が1つずつ比較されます。特定の文字が一致しない場合、メイン文字列は3番目の文字までさかのぼり、部分文字列は最初の文字までさかのぼって、すべての部分文字列が正常に一致するまで1つずつ比較します。
垂直線は等しいことを表し、稲妻線は等しくないことを表します。
ここに画像の説明を挿入
ここに画像の説明を挿入
最良の場合、このアルゴリズムの時間計算量はO(n)です。つまり、部分文字列のn文字は、主文字列の最初のn文字と正確に等しく、最悪の場合、時間計算量はO(m * n)です。対照的に、このアルゴリズムのスペースの複雑さはO(1)です。つまり、スペースを消費しませんが、時間を消費します。

KMPの主なアイデアは次のとおりです:「時間のスペース」
は概念を提案します:文字列の最長の等しいプレフィックスとサフィックス。
文字列abcda
プレフィックスのセット:{a、ab、abc、abcd、abcda}
サフィックスのセット:{b、ab、dab、cdab、bcdab}
次に、最も長い等しいサフィックスはabです
。小さな演習を行います:
string: abcabfabcab真ん中で最も長い等しい
接頭辞と接尾辞は何ですか:abcab

グラフィックKMP

最初のバーはメインストリングを表し、2番目のバーはサブストリングを表します。赤い部分は2つの文字列の一致する部分を表し、緑と青い部分はそれぞれメイン文字列とサブ文字列の一致しない文字を表します。
具体的には、この画像はメイン文字列「abcabeabcabcmn」とサブ文字列「abcabcmn」を表しています。
ここに画像の説明を挿入
不一致が見つかったので、KMPのアイデアに従って部分文字列を後方に移動する必要があります。次に、移動する量の問題を解決します。前述の最長の等しい接頭辞と接尾辞の概念は便利です。赤い部分にも最長の等しい接頭辞と接尾辞があるためです。次の図に示すように、
ここに画像の説明を挿入
ここに画像の説明を挿入
灰色の部分は文字列の赤い部分の最長の等しい接頭辞と接尾辞です。部分文字列の移動の結果、部分文字列の赤い部分の最長の等しい接頭辞を最長の等しい接尾辞に揃えます。メインストリングの赤い部分の。
ここに画像の説明を挿入
各文字の前の文字列には最長の等しい接尾辞があり、最長の等しい接尾辞の長さがシフトの鍵であるため、次の配列を使用して部分文字列の最長の等しい接尾辞の長さを格納します。そして、次の配列の値は部分文字列自体にのみ関連しています。
したがって、next [i] = jの場合、意味は次のとおりです。添え字iがjになる前の文字列の最長の等しい接頭辞と接尾辞の長さ。
部分文字列t = "abcabcmn"の次の配列はnext [0] = -1(個別に文字列処理はありません)
next [1] = 0; next [2] = 0; next [3] = 0であると計算できます。 ; Next [4] = 1; next [5] = 2; next [6] = 3; next [7] = 0;
ここに画像の説明を挿入
次の配列next [5]の次の配列に保存する必要のある値部分文字列でもあるmatchバックトラック後の対応する文字の添え字。など?= next [5] = 2。次のステップは、文字s [5]とt [next [5]]を比較することです。これはまた、最も素晴らしい場所であり、KMPアルゴリズムのコードが非常にシンプルでエレガントな理由の鍵でもあります。
ここに画像の説明を挿入
KMPアルゴリズムには、配列を見つける追加のプロセスがあり、これはもう少しスペースを消費します。メイン文字列sの長さをnに設定し、サブ文字列tの長さをmに設定します。次の配列を見つける時間計算量はO(m)です。メイン文字列は後続のマッチングでバックトラックされないため、比較の数はnとして記録できます。したがって、KMPアルゴリズムの合計時間計算量はO(m + n)、空間計算量はO(m)として記録されます。O(m * n)の単純なパターンマッチング時間計算量と比較して、KMPアルゴリズムの速度は非常に大きいです。この小さなスペース消費が非常に高い時間速度を交換することは非常に意味があり、このアイデアも非常に重要です。

次の配列
構築におけるバックトラッキングの問題を説明する次のバーは部分文字列を表し、赤い部分は現在の一致の最長の等しいプレフィックスとサフィックスを表し、青い部分はt.data [j]を表します。
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入

void GetNextval(SqString t,int nextval[])  
//由模式串t求出nextval值
{
    
    
	int j=0,k=-1;
	nextval[0]=-1;
   	while (j<t.length) 
	{
    
    
       	if (k==-1 || t.data[j]==t.data[k]) 
		{
    
    	
			j++;k++;
			if (t.data[j]!=t.data[k]) 
//这里的t.data[k]是t.data[j]处字符不匹配而会回溯到的字符
//为什么?因为没有这处if判断的话,此处代码是next[j]=k;
//next[j]不就是t.data[j]不匹配时应该回溯到的字符位置嘛
				nextval[j]=k;
           	else  
				nextval[j]=nextval[k];
//这一个代码含义是不是呼之欲出了?
//此时nextval[j]的值就是就是t.data[j]不匹配时应该回溯到的字符的nextval值
//用较为粗鄙语言表诉:即字符不匹配时回溯两层后对应的字符下标
       	}
       	else  k=nextval[k];    	
	}

}
int KMPIndex1(SqString s,SqString t)    
//修正的KMP算法
//只是next换成了nextval
{
    
    
	int nextval[MaxSize],i=0,j=0;
	GetNextval(t,nextval);
	while (i<s.length && j<t.length) 
	{
    
    
		if (j==-1 || s.data[i]==t.data[j]) 
		{
    
    	
			i++;j++;	
		}
		else j=nextval[j];
	}
	if (j>=t.length)  
		return(i-t.length);
	else
		return(-1);
}

おすすめ

転載: blog.csdn.net/LINZEYU666/article/details/111758677