正则表达式

正则匹配思路

在前面查找子字符串时,KMP算法中采用的是确定有限状态机来模拟的查找字符串,在正则表达式匹配中,则采用的是类似的思路–不确定有限状态机NFA。在正则表达式中,定义两种情况,一种是字符匹配,一种是 ϵ -变换。只要求出了正则表达式的 ϵ -变换序列,然后与文本字符串进行匹配就OK。
正则表达式中的三种运算:
- 连接运算:AB,简单自然的连接运算
- 重复运算: "" ,表示重复0次或者多次, A*,代表A连续出现0次或多次
- 或运算: "|" ,唯一的二元运算符,表示前后两者选其一,A|B表示出现A或者B。
其中 "." 是通配符。
例如:对于正则表达式 (.(AB)|AC)|D.) ϵ -变换序列的求解方法如下:
在简单的正则表达式中,只想要考虑下面几种特殊符号:

  1. “*”:连接字符,表示前面的字符重复0次或多次,在状态机中表现为:

    正则表达式_第1张图片

  2. “|”:或字符,唯一的二元字符,表示前面或者后面的字符串中的一种情况,在状态机中表现为:
    正则表达式_第2张图片

  3. “(”或者“)”:括号,改变正则中的优先级。
    ϵ -变换序列可以采用之前我们求运算表达式的结果时一样的方法,用栈来保存这些正则表达式中的运算符, "" 可以不用入栈, "|" "(" 需要入栈,而右括号会自然的匹配。

    示例

    匹配转换:如果当前状态存在于NFA中,且下一个字符与文本中的字符相匹配,则可以直接扫过该字符转换到下一个字符。
    ϵ -转换:不用扫描文本中的字符串自动转换到下一个状态。
    正则表达式 (.(AB)|AC)|D.) 的不确定有限状态机如下:
    正则表达式_第3张图片
    上图中红色的线代表 ϵ -转换,蓝色的线代表匹配转换。
    可以看到NFA中状态的转换很类似于有向图中的多点的可达性。我们在txt中匹配时就是要找到一个字符串使得刚开始的起始状态最后能达到接受状态。
    例如对于字符串AAAABD,在该字符串中找到与 (.(AB)|AC)|D.)
    匹配过程中状态转换如下图:
    正则表达式_第4张图片

java实现

通过上面的分析可知:正则匹配主要分为以下几步:
1. 根据给定的正则表达式构造NFA
2. 根据NFA利用有向图中的多点可达性算法匹配。
在下面的实现中,主要用到的是有向图中的深度优先算法进行的可达性搜索。

package Grep;

import DataStrcuture.Bag;
import DataStrcuture.Digraph;
import DataStrcuture.DirectedDFS;
import edu.princeton.cs.introcs.StdOut;

import java.util.Stack;

/** * Created by Wang on 2016/1/16. */
public class NFA {
    //非确定有限状态自动机NFA
    private char[] re;  //匹配转换
    Digraph G;  //epsilon转换
    private int M;  //状态数量

    public NFA(String regexp){
        Stack<Integer> ops = new Stack<Integer>();
        re = regexp.toCharArray();
        M = re.length;
        G = new Digraph(M + 1);

        for (int i = 0; i < M; i++) {
            int lp = i;
            if(re[i] == '(' || re[i] == '|'){
                ops.push(i);
            }
            else if(re[i] == ')'){
                int or = ops.pop();
                if (re[or] == '|'){
                    lp = ops.pop();
                    G.addEdge(lp, or+1);
                    G.addEdge(or, i);
                }
                else lp = or;
            }
            if (i < M-1 && re[i+1] == '*'){
                G.addEdge(lp, i+1);
                G.addEdge(i+1, lp);
            }
            if (re[i] == '(' || re[i] == '*' || re[i] == ')'){
                G.addEdge(i, i+1);
            }
        }
    }


    public boolean recoginize(String txt){
    //NFA能否识别文本txt
        Bag<Integer> pc = new Bag<Integer>();
        DirectedDFS dfs = new DirectedDFS(G, 0);

        for (int v = 0;  v < G.V();  v++) {
            if (dfs.marked[v])
                pc.add(v);
        }

        for (int i = 0; i < txt.length(); i++) {
            Bag<Integer> match = new Bag<Integer>();
            for (int v: pc) {
                if (v < M)
                    if (re[v] == txt.charAt(i) || re[v] == '.')
                        match.add(v+1);//为何不是v
            }

            pc = new Bag<Integer>();
            dfs = new DirectedDFS(G, match);
            for (int v = 0;  v < G.V();  v++) {
                if (dfs.marked[v])
                    pc.add(v);
            }
        }
        for (int v : pc) {
            if(v == M)   return true;
        }
        return false;
    }


    public static void main(String[] args){
       // String regexp = "(.*(A*B|AC)D.*)";
        String regexp = "(.*(A*B|AC)D.*)";
        String txt = "ABCCBD";
        NFA nfa = new NFA(regexp);

        if(nfa.recoginize(txt))
            StdOut.println(txt);

    }
}

github链接

源码-github链接

有问题,欢迎交流。

你可能感兴趣的:(java,算法,正则表达式)