STEP 3.2

<問題> kmp法で、文字列照合を行う

<実行結果>

探したい文字列を入力してください :ababc
t[0] = a
t[1] = b
t[2] = a
t[3] = b
t[4] = d
t[5] = a
t[6] = b
t[7] = a
t[8] = b
t[9] = c
t[10] = c
t[11] = b
t[12] = d
t[13] = c
t[14] = a
t[15] = b
t[16] = c
t[17] = a
t[18] = d
t[19] = b
f[0] = -1
f[1] = 0
f[2] = 0
f[3] = 1
f[4] = 2
cmp = 12
Pattern ababc is matched at 5

探したい文字列を入力してください :aaaab
t[0] = a
t[1] = a
t[2] = a
t[3] = a
t[4] = a
t[5] = a
t[6] = a
t[7] = a
t[8] = a
t[9] = b
f[0] = -1
f[1] = 0
f[2] = 1
f[3] = 2
f[4] = 3
cmp = 15
Pattern aaaab is matched at 5


例題集へ



解答例

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

/* プロトタイプ宣言 */
void print_text(char []); /* 文字列配列tの表示 */
int kmp(char [], char []); /* 引数は文字の配列。別の書き方もある */
void compute_f(char [], int []); /* 引数は文字の配列。別の書き方もある */
void print_f(int [], int); /* 失敗関数値の配列を表示 */

int main (void) {
    char t[30]; /* 最後は \0 ヌル文字なので、最大29文字 */
    char p[10]; /* パターンは9文字 */
    int j; /* 関数の戻り値。照合成功か判定 */

    /* テキストは、教科書の例 p98 */
    strcpy(t, "ababdababccbdcabcadb"); /* 文字列の代入 よくわかるC p68 */
    /*   よくない例 */
//   strcpy(t, "aaaaaaaaab");  /* pはaaaab。bで不一致のあと、あまり進めない */

    /* パターンは入力 p98 */
    printf("探したい文字列を入力してください :");
    scanf("%s", p);

    print_text(t);  /* テキストの出力 */
    j = kmp(t,p);   /* kmp法でいこう。pについて失敗関数を作成し、照合開始 */

    /* 戻り値に応じて、出力文 */
    if (j == -1) /* 見つからない */
        printf("Pattern %s is not found \n", p);
    else /* テキスト配列tの添え字jから見つかった */
        printf("Pattern %s is matched at %d \n", p,j);

    return 0;
}

int kmp (char t[], char p[]){
    int i = 0;
    int j = 0;
    int f[10]; /* 失敗関数値の配列 */
    int m = strlen(p);
    int n = strlen(t);
    int cmp = 0; /* 比較回数カウント用 */

    compute_f(p, f); /* 配列アドレス渡しに注意 たとえばpは&p[0] のこと*/
    print_f(f, m);   /* 失敗関数の作成結果を出力 */

    while (i < m && j < n) { /* どちらかを満たさなくなったら、ループを出る */
        if (i == -1) { /* 頭から不一致の場合: tは次のjへ移動 pはi=0から比較 */
            i = i + 1;
            j = j + 1;
        } else if (p[i] == t[j]) { /* tとpが一致している。進める */
            i = i + 1;
            j = j + 1;
            cmp++;
        } else { /* 一致しない。tのjは戻さない。pのiを移動 iが小さくなるほどうれしい。-1 < f[i] < iのはず i=0のときは -1*/
            i = f[i];
            cmp++;
        }
    }

   /* 比較回数をカウントしない場合は、以下のようにすっきり
    while (i < m && j < n) {
        if (i == -1 || p[i] == t[j]) {
            i = i + 1;
            j = j + 1;
        } else {
            i = f[i];
        }
    }
    */

    printf("cmp = %d \n", cmp);

    if (i == m) /* すべて一致してループを抜けた場合 */
       return (j - i);
    else
       return (-1);

}

/* 失敗関数の値を、fに */
/* i-1まで一致しているとき、iで不一致。f[i]を求める */
void compute_f (char p[], int f[]) { /* p同士を比較していく */
    int j = 0;  /* jは戻らない */
    int i = -1;
    int m = strlen(p);
    f[0] = -1; /* i==0で不一致のときの特例。先頭から異なるときの値の設定 */

    while (j < m) {
       if (i==-1 || p[i] == p[j]) { /* i==-1の特例処理か、一致している場合 */
           i++;
           j++; /* 次へ進む */
           f[j] = i; /* 失敗関数の確定 */
       } else {
           i = f[i]; /* 一致しないならjを進めず、iを進めて、再度比較へ */
       }
    }

}


/* 文字列の最後には\0 ヌル記号。番兵とみることもできる */
void print_text(char t[]) {
    int j = 0;
    while (t[j] != '\0') {  /* 最後のヌル文字ではない */
        printf("t[%d] = %c\n", j, t[j]); /* 1文字をプリント */
        j++;
    }
}

/* 失敗関数のプリント  */
void print_f(int f[], int m) {
    int i = 0;
    while (i < m) {
        printf("f[%d] = %d\n", i, f[i]); /* i-1まで一致 iで不一致のとき、f[i] */
        i++;
    }
}