用有限自动机实现正则表达式的匹配

问题:在主串中查找是否存在正则表达式为 abc*d?e 的匹配,下面用有限自动机的方法查找,该正则表达式的最简 DFA 如下

状态转换表如下图所示

程序中用二维数组定义如下

#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
};
算法是暴力匹配,其中 is_matched 函数模仿了 C 语言中的库函数 strstr(Linux 下的源码,鲁棒性高)。

程序如下

/**************************************************************************
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 的算法,没匹配上可滑动多个字符。

3、提供正则表达式的库实现的是对任给的一个正则表达式在主串中查找,许多语言都支持,这篇文章 http://blog.csdn.net/xiaozhuaixifu/article/details/9875423 中用的方法比较简洁,直接匹配,可能库的实现原理与之类似。

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
*/


  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值