用有限自動機實現正則表示式的匹配
阿新 • • 發佈:2019-02-09
問題:在主串中查詢是否存在正則表示式為 abc*d?e 的匹配,下面用有限自動機的方法查詢,該正則表示式的最簡 DFA 如下
狀態轉換表如下圖所示
程式中用二維陣列定義如下
演算法是暴力匹配,其中 is_matched 函式模仿了 C 語言中的庫函式 strstr(Linux 下的原始碼,魯棒性高)。#define STATES_NUMBER 5 #define LETTER_NUMBER 5 // 表示 abc*d?e 的最簡 DFA 的狀態轉換表(-1表示不接受) int trans_table[STATES_NUMBER][LETTER_NUMBER] = { 1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 2, 3, 4, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1 };
程式如下
/************************************************************************** created: 2014/03/08 filename: main.c author: Justme0(http://blog.csdn.net/justme0) purpose: 模擬 DFA,在主串中搜索模式 abc*d?e,查詢是否存在匹配 **************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #define MAX_LENGTH 100 #define STATES_NUMBER 5 #define LETTER_NUMBER 5 // 表示 abc*d?e 的最簡 DFA 的狀態轉換表(-1表示不接受) // 查詢能否匹配上,無需貪婪匹配,故作為接受狀態的最後一行實際上不會被訪問到 int trans_table[STATES_NUMBER][LETTER_NUMBER] = { 1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 2, 3, 4, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1 }; /* ** 是否是接受狀態 */ int is_end(const int state) { return STATES_NUMBER - 1 == state; } /* ** state 是否接受 letter */ int is_acceptable(const int state, const char letter) { int column = letter - 'a'; assert(0 <= state && state < STATES_NUMBER); if (!(0 <= column && column < LETTER_NUMBER)) { return 0; } return -1 != trans_table[state][column]; } int move(const int state, const char letter) { int column = letter - 'a'; return trans_table[state][column]; // is_acceptable 與 move 有冗餘,待改進 } /* ** 若主串中匹配上了模式則返回1,否則返回0 */ int is_matched(const char *const str) { const char *head = NULL; // head 是當前模式的頭在 str 中的位置 const char *p = NULL; // p 指示主串 int state = 0; // state 代表模式 for (head = str; '\0' != *head; ++head) { state = 0; for (p = head; '\0' != *p && (!is_end(state)) && is_acceptable(state, *p); ++p) { state = move(state, *p); } if (is_end(state)) { return 1; } } return 0; } int main(int argc, char **argv) { char str[MAX_LENGTH]; int ans; FILE * in_stream = freopen("test.txt", "r", stdin); if (NULL == in_stream) { printf("Can not open file!\n"); exit(1); } while (EOF != scanf("%s", str)) { scanf("%d", &ans); assert(ans == is_matched(str)); if(is_matched(str)) { printf("找到 abc*d?e 匹配\n"); } else { printf("沒有找到 abc*d?e 匹配\n"); } } fclose(in_stream); return 0; } /*test.txt abe 1 abee 1 abde 1 eabcce 1 bb33_aabcabdee 1 -*+68abcdababaabcccccccdeeeesabc 1 a 0 abc 0 b 0 . 0 eab 0 eabcccd 0 abdff 0 &*%&(* 0 */
有幾點感受:
1、ACM 中常常用到的 AC 自動機與這個應該有區別,那個常用 Trie 樹實現。
2、上面也提到了,用的是暴力匹配,也就是說此次沒匹配上,模式向前移動一個字元,又重新匹配,我想應該有類似 KMP 的演算法,沒匹配上可滑動多個字元。
C++11 中加入了正則表示式,程式如下:
/******************************************************************** created: 2014/03/09 0:51 filename: test.cpp author: Justme0 (http://blog.csdn.net/justme0) purpose: 正則匹配 *********************************************************************/ #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <regex> #include <cassert> using namespace std; int main() { FILE * in_stream = freopen("test.txt", "r", stdin); if (NULL == in_stream) { printf("Can not open file!\n"); exit(1); } regex rgx("abc*d?e"); string text; bool ans; while (cin >> text) { cin >> ans; assert(ans == regex_search(text, rgx)); if(regex_search(text, rgx)) { printf("找到 abc*d?e 匹配\n"); } else { printf("沒有找到 abc*d?e 匹配\n"); } } fclose(in_stream); return 0; }
regex 庫能滿足多種正則匹配的需求,上面的 regex_search 用法是最簡單的一種,返回布林值,查詢是否存在匹配。
20140310
這兩天查了資料,發現上面所說的第二個問題是可以解決的,原理與 KMP 類似,得把 DFA 修改一下,只需對主串掃一遍,讓它在 DFA 上跑。修改後的 DFA 如下,other 指某狀態未標出的所有其他符號。
狀態轉換表
程式如下,時間複雜度 O(n),n 為主串長度
/**************************************************************************
created: 2014/03/10
filename: main.c
author: Justme0 (http://blog.csdn.net/justme0)
purpose: 模擬 DFA,在主串中搜索模式 abc*d?e,查詢是否存在匹配
只需對主串掃一遍
**************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define MAX_LENGTH 100
#define STATES_NUMBER 5
#define LETTER_NUMBER 6
// 查詢能否存在匹配,故無需貪婪匹配,作為接受狀態的最後一行實際上不會被訪問到
int trans_table[STATES_NUMBER][LETTER_NUMBER] = {
1, 0, 0, 0, 0, 0,
1, 2, 0, 0, 0, 0,
1, 0, 2, 3, 4, 0,
1, 0, 0, 0, 4, 0,
0, 0, 0, 0, 0, 0
};
/*
** state 是否是接受狀態
*/
int is_end(const int state) {
return STATES_NUMBER - 1 == state;
}
int move(const int state, const char letter) {
int column;
if ('a' <= letter && letter <= 'e') {
column = letter - 'a';
} else {
column = LETTER_NUMBER - 1;
}
assert(0 <= state && state < STATES_NUMBER - 1); // 最後一行不應該被執行到
assert(0 <= column && column < LETTER_NUMBER);
return trans_table[state][column];
}
/*
** 若主串中匹配上了模式則返回1,否則返回0
*/
int is_matched(const char *const text) {
int state = 0;
const char *p = NULL; // p 指示輸入字元
for (p = text; '\0' != *p && (!is_end(state)); ++p) {
state = move(state, *p);
}
if (is_end(state)) {
return 1;
}
return 0;
}
int main(int argc, char **argv) {
char text[MAX_LENGTH];
int ans;
int cnt = 1;
FILE * in_stream = freopen("test.txt", "r", stdin);
if (NULL == in_stream) {
printf("Can not open file!\n");
exit(1);
}
while (EOF != scanf("%s", text)) {
scanf("%d", &ans);
assert(ans == is_matched(text)); // 用於測試
printf("Case %d: ", cnt++);
if(is_matched(text)) {
printf("找到 abc*d?e 匹配\n");
} else {
printf("沒有找到 abc*d?e 匹配\n");
}
}
fclose(in_stream);
return 0;
}
/*test.txt
abe 1
abee 1
abde 1
eabcce 1
bb33_aabcabdee 1
-*+68abcdababaabcccccccdeeeesabc 1
a 0
abc 0
b 0
. 0
eab 0
eabcccd 0
abdff 0
&*%&(* 0
*/