题意
给定一个字符串 ( s s s) 和一个字符模式 ( p p p) ,实现一个支持 ′ ? ′ '?' ′?′ 和 ′ ∗ ′ '*' ′∗′ 的通配符匹配。
'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。
如:
" ∗ a ∗ b " "*a*b" "∗a∗b"可以匹配 " a d c e b " "adceb" "adceb"(第一个 ∗ * ∗为空,第二个 ∗ * ∗为 d c e dce dce)
限制字母表为小写字母。
(另有LeetCode 10.正则表达式匹配与其相似,但更像正则表达式)
题解
因为这学期正好在学形式语言与自动机这门课,这道题显然是一个正则表达式的问题,就尝试模拟了一下正则表达式转 ϵ − N F A \epsilon-NFA ϵ−NFA。(虽然最后跑出来特别慢,但好处是直接按部就班走,不需要动脑子)
先构造出空串 ϵ \epsilon ϵ的 N F A : NFA: NFA:
对于每个输入字符,在原有的 N F A NFA NFA上进行修改,产生一个新的自动机。
考虑给定字符 a a a的三种情况:
( 1 ) (1) (1) a a a是小写字母。
构造新自动机的方法很简单:使原有的终结状态 F 0 F_0 F0变为非终结状态,让 F 0 F_0 F0经 a a a转移到新创建的终结状态 F ′ F' F′.
( 2 ) a (2)a (2)a是 ′ ? ′ '?' ′?′。
与 ( 1 ) (1) (1)类似,只不过任意字符都能使其进入新的终结状态。在这里直接用’?‘代替。
( 3 ) a (3)a (3)a是 ′ ∗ ′ '*' ′∗′。
由于’*'可以代替零个及以上的字符,那么它就可以不读取字符经空转移到下一个状态,或用任意字符转移到自身。新自动机如下:
代码
类成员:
class NFA {
struct state {
bool star;
char ch;
state *next;
state() {
star = false;
next = nullptr;
}
};
state *start, *final;
}
其中:
( 1 ) (1) (1) s t a t e state state为 N F A NFA NFA中的状态。
( 2 ) (2) (2)若 s t a r = = t r u e star==true star==true,说明该状态可以在自身进行任意次转移;
( 3 ) (3) (3)由于一个状态只能经由一种字符( ′ ? ′ '?' ′?′也包括在内)转移到下一个状态,所以只用一个 c h a r char char型的 c h ch ch来保存这个使其转移的字符。
( 4 ) n e x t (4)next (4)next是指向转移的下一个状态的指针。
( 5 ) s t a r t (5)start (5)start和 f i n a l final final分别指向起始状态和终止状态。
用一个新字符更新自动机,与上面阐述的思路相同。
void addState(char op) {
state *tmp = new state;
if((op >= 'a' && op <= 'z') || op == '?') {
final->star = false;
final->ch = op;
final->next = tmp;
final = final->next;
}
else {
final->star = true;
final->ch = '#';
final->next = tmp;
final = final->next;
}
}
取 ϵ − \epsilon- ϵ−闭包,用 s e t set set保存过程中的状态集合(所以很慢):
set<state*> ECLOSE(set<state*> S) {
set<state*> ret = S;
for(auto it = S.begin(); it != S.end(); ++it) {
state *p = *it;
while(p->ch == '#') {
p = p->next;
ret.insert(p);
}
}
return ret;
}
匹配过程:
bool isMatch(string str) {
set<state*> S;
S.insert(start);
S = ECLOSE(S);//取闭包
for(int i = 0; i < str.size(); i++) {
set<state*> tmp;
for(auto it = S.begin(); it != S.end(); ++it) {
if((*it)->star) tmp.insert(*it);//若为星,可以转移到自身
if((*it)->ch == str[i] || (*it)->ch == '?') tmp.insert((*it)->next);//当字符匹配,或可以用问号替代任何字符,那么可以转移到下一个状态
}
S = ECLOSE(tmp);//取闭包
}
for(auto it = S.begin(); it != S.end(); it++) {
if((*it) == final) return true;
}
return false;
}
顺便附上 ϵ − N F A \epsilon-NFA ϵ−NFA的匹配过程(来源:Automata Theory,Languages,and Computation第三版):
串 w w w被接受当且仅当 δ ( q 0 , w ) \delta(q_0,w) δ(q0,w)与终结状态集 F F F的交集不为空。
完整代码:
class Solution {
class NFA {
struct state {
bool star;
char ch;
state *next;
state() {
star = false;
next = nullptr;
}
};
state *start, *final;
set<state*> ECLOSE(set<state*> S) {
set<state*> ret = S;
for(auto it = S.begin(); it != S.end(); ++it) {
state *p = *it;
while(p->ch == '#') {
p = p->next;
ret.insert(p);
}
}
return ret;
}
public:
NFA() {
start = final = new state;
}
void addState(char op) {
state *tmp = new state;
if((op >= 'a' && op <= 'z') || op == '?') {
final->star = false;
final->ch = op;
final->next = tmp;
final = final->next;
}
else {
final->star = true;
final->ch = '#';
final->next = tmp;
final = final->next;
}
}
bool isMatch(string str) {
set<state*> S;
S.insert(start);
S = ECLOSE(S);
for(int i = 0; i < str.size(); i++) {
set<state*> tmp;
for(auto it = S.begin(); it != S.end(); ++it) {
if((*it)->star) tmp.insert(*it);
if((*it)->ch == str[i] || (*it)->ch == '?') tmp.insert((*it)->next);
}
S = ECLOSE(tmp);
}
for(auto it = S.begin(); it != S.end(); it++) {
if((*it) == final) return true;
}//若集合中有一个是终结状态,返回true
return false;
}
};
public:
bool isMatch(string s, string p) {
NFA N;
for(int i = 0; i < p.size(); i++)
N.addState(p[i]);//用每个字符来构造自动机
return N.isMatch(s);
}
};