java 字符正则匹配算法_正则表达式匹配算法

正则表达式匹配算法

在leetcode玩耍遇到了这么一个题给定一个字符串 (s) 和一个字符模式 (p)。实现支持 '.'和'*'的正则表达式匹配。

'.' 匹配任意单个字符。

'*' 匹配零个或多个前面的元素。

匹配应该覆盖整个字符串 (s) ,而不是部分字符串。

说明:

s可能为空,且只包含从a-z的小写字母。

p可能为空,且只包含从a-z的小写字母,以及字符.和*。

示例 1:输入: s = "aa" p = "a" 输出: false

解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:输入: s = "aa" p = "a*" 输出: true

解释: '*' 代表可匹配零个或多个前面的元素, 即可以匹配 'a' 。因此, 重复 'a' 一次, 字符串可变为 "aa"。

示例 3:输入: s = "ab" p = ".*" 输出: true

解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。

示例 4:输入: s = "aab" p = "c*a*b" 输出: true

解释: 'c' 可以不被重复, 'a' 可以被重复一次。因此可以匹配字符串 "aab"。 示例 5:输入: s = "mississippi" p = "mis*is*p*." 输出: false

本人当时想了很久毫无头绪,后来在百度上参照了几篇介绍正则表达式算法的文章才解决了问题,把我的见解分享给大家

解决这道题要从状态机说起

自动机也称状态机。下面的自动机对应正则表达式a(bb)+a。

自动机总是处于一个状态之中,也就是图中的圈圈。在读入字符串的时候,它从一个状态进入另一个状态。自动机有两个特殊的状态:开始开始状态和匹配状态。开始状态始于一个但箭头,匹配状态止于一个双环。

自动机一次读入一个字符串,按照箭头转换状态。假设输入字符串abbbba。当字符串读入第一个字符a,此时的状态为s1。它耕者箭头a到达状态s1。重复过程,直到到达状态s4。

自动机结束于s4,这是一个匹配成功的状态,也就说匹配成功了,如果自动机结束于非匹配成功状态,那么匹配失败。如果在运行过程中,没有办法到达其他状态,那么自动机提前结束。

我们刚才考虑的是确定状态自动机,因为在每一个状态,可达的下一个状态至多只有一个。我也可以创建一个有多种选择的机器。以之前的自动机为例:

机器的状态是不确定的,因为当它读入ab到达s2,它对于下一个状态有多个选择:它可以回到s1或者到达s3。由于机器无法预料接下来的输入,它不知道该选择哪个状态。可以同时保存多种状态的也就是非确定状态自动机(NFA)。

正则表达式转换成NFA

在原题中只有".", "*"两个正则表达式匹配,我们做一个简单的例子支持这两个字符

java代码如下:

/*** NFA节点类** @author Julius*/

static class Node {

/*** 当前节点的值*/

char value;

/*** 当前节点的下一个节点*/

Node node;

/*** 当前节点的下一个状态节点集合*/

List linkNodes;

/*** 标记为头*/

boolean isHead;

/*** 标记为尾*/

boolean isTail;

public Node(char value) {

super();

this.value = value;

this.linkNodes = new ArrayList<>();

}

}

/*** 在这里将正则表达式转换成NFA** @param s* 待转换的正则表达式* @return NFA模型*/

public static List parse2Nodes(String s) {

CopyOnWriteArrayList nodes = new CopyOnWriteArrayList<>();

for (int i = 0; i < s.length(); i++) {

char c = s.charAt(i);

nodes.add(new Node(c));

}

for (int i = 0; i < nodes.size(); i++) {

Node n = nodes.get(i);

/** 节点的值是普通字符时,上个节点指向本结点*/

if (n.value >= 'a' && n.value <= 'z') {

Node pn = getPreNode(nodes, i);

if (pn != null)

pn.node = n;

}

/** 节点的值是匹配任意单个字符(.)时,上个节点指向本结点*/

if (String.valueOf(n.value).equals(".")) {

Node pn = getPreNode(nodes, i);

if (pn != null)

pn.node = n;

}

/** 节点的值是匹配零个或多个前面的元素时,上一个节点增加一个指向自己的状态* ,增加一个指向下一个节点的状态并删除本节点*/

if (String.valueOf(n.value).equals("*")) {

Node pn = getPreNode(nodes, i);

Node nn = getNextNode(nodes, i);

if (pn != null) {

pn.linkNodes.add(pn);

}

if (nn != null) {

pn.linkNodes.add(nn);

}

nodes.remove(n);

i--;

}

}

nodes.get(0).isHead = true;

nodes.get(nodes.size() - 1).isTail = true;

return nodes;

}

/*** 获取前一个节点** @param nodes* @param index* @return*/

public static Node getPreNode(List nodes, int index) {

if (index - 1 >= 0) {

return nodes.get(index - 1);

}

return null;

}

/*** 获取下一个节点** @param nodes* @param index* @return*/

public static Node getNextNode(List nodes, int index) {

if (index + 1 <= nodes.size() - 1) {

return nodes.get(index + 1);

}

return null;

}

比如c*a*b*这个正则表达式,经过NFA算法可到如下的模型

图中的圆圈表示一个状态,对应Node节点类,黑色箭头与蓝色箭头表示状态的转换方向

NFA匹配算法

现在我们有了匹配正则表达式的方法:把正则表达式转换为NFA,然后把字符串作为输入来运行NFA。现在来看一下模拟NFA运行的算法

java代码如下:

/*** 检测字符串匹配正则表达式** @param s* 匹配字符串* @param p* 正则表达式* @return*/

public static boolean isMatch(String s, String p) {

List nodes = parse2Nodes(p);

return isMatch(s, 0, nodes.get(0));

}

/*** 深度优先匹配字符串** @param s* 带匹配字符串* @param index* 字符下标* @param node* NFA模型* @return*/

public static boolean isMatch(String s, final int index, Node node) {

if (index > s.length() - 1) {

return false;

}

char c = s.charAt(index);

/** 找到匹配的字符,匹配成功*/

if (c == node.value || node.value == '.') {

/** 字符指针移动到最后一位,状态机到达最终状态返回匹配成功*/

if (s.length() - 1 == index && node.isTail) {

return true;

}

/** 转换到下一个状态尝试匹配*/

if (node.node != null) {

if (isMatch(s, index + 1, node.node)) {

return true;

}

}

for (Node ln : node.linkNodes) {

if (isMatch(s, index + 1, ln)) {

return true;

}

}

} else {

/** 转换到下一个状态尝试匹配*/

for (Node ln : node.linkNodes) {

if (ln != node) {

if (isMatch(s, index, ln)) {

return true;

}

}

}

}

/** 不满足匹配成功条件,回溯*/

return false;

}

主函数中调用方法isMatch(String s, String p)即可检测字符串是否匹配表达式,例如isMatch("ab", ".*")返回true,表示".*"可以匹配字符串

以下是匹配测试输入: aaa, p=a

结果: 不匹配

输入: s=aa, p=a*

结果: 匹配

输入: s=aab, p=c*a*b*

结果: 匹配

输入: s=aab, p=ca*b*

结果: 不匹配

输入: s=ab, p=.*

结果: 匹配

输入: mississippi, p=mis*is*p*.

结果: 不匹配

输入: abcdef, p=.*de.

结果: 匹配

输入: abcdef, p=.*p*de.

结果: 匹配

输入: abcdef, p=.*p*d.e.

结果: 不匹配

至此我们的正则表达式算法已完成

结尾

本篇介绍了正则表达式匹配的常见算法非确定型有限状态自动机(NFA),并贴上代码实现了算法,希望给想了解正则表达式和想实现正则表达式的人起到帮助作用

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值