2022-05-17 基于NFA的简单正则表达式

关于正则表达式

正则表达式可以简单说是模式识别,通过一个可变模式,匹配文本字符串中符合模式的字符串。

对于编程和文本工作人员,正则表达式是生产力工具,对于互联网这种连接信息的载体,正则表达式无处不在,同时不好的正则表达式,可能成为程序瓶颈。

NFA

读过2022-05-04 KMP算法,基于DFA的子字符串查找的同仁,应该对 DFA (确定有限自动机)不陌生,通过对匹配文本的字符进行状态转移,可以线性的解决子字符串查找问题。

但对于正则表达式,DFA就稍有问题,因为表达式模式不确定,无法用一个简单DFA表示,需要引入NFA,非确定有限自动机。

对比DFA,NFA的状态转移是不确定的,但是有限的,我们用有向图的数据结构替代数组,构建NFA。

epsilon 转换

正则表达式与普通字符串的区别是含有epsilon转换,也就是功能表达而非字符值。

来个例子:

“ABAC” :普通字符串,只含有要匹配的字母。

“(A*B|AC)" :正则表达式,含有功能字符,“( )”, " * ", " | ",这些字符不是用来直接匹配的,括号改变优先级,确定其他功能符号的作用范围,星号代表前面的字符或子表达式重复 0 - N 次,N 属于自然数。或符号表达或的语义,相较纯字符串,要复杂的多。

所谓 epsilon 转换,就是将这些功能符号的功能翻译成状态转移的方案,承载这个方案的,就是有向图。

转换过程

基本规则:

遇到左括号,向后移动一位;

'(' - > next

遇到星号,如果前一位是字符,则向前一位,向后一位。

'*' -> next
'*' -> pre

遇到星号,如果前一位是 " ) ",则向前找到 " ( ",同时 " ( " 到星号,向后一位。

'*' -> '('
'(' -> '*'
'*' -> next

遇到右括号,寻找最近的 " | " 或者 " ( ",如果最近的是 “ | ”,则继续寻找左括号,“或”指向右括号,左括号指向“或”+1位置

'|' -> ')'
'(' -> '|' + 1

为了完成转换,我们需要将左括号 " ( " 和或 " | " 进行压栈操作,并在碰到右括号 " ) " 时弹出匹配。

匹配过程

我们将 epsilon 转换构造为有向图,从顶点 0 出发,找到所有可达顶点 v ,如果匹配模式字符串 regExpStr[ v] 等于文本 txt 的第 0 个字符 txt[0],或者为 “.” 则匹配成功,将 v 后一个顶点 v+1 放入匹配表 matchTable。

再根据 matchTable 中的顶点获取可达顶点,如果有匹配第 1 个字符,则将下一个顶点放入匹配表 matchTable中,如此继续,直至达到最后一个 txt 的字符。此时,如果可达顶点 v 中有值等于 txt 的 size,则匹配成功。

以下是代码,注释的比较详细了,可以一边测试,一边熟悉匹配过程。

#ifndef REGEXP
#define REGEXP

#include <algorithm>
#include <iostream>
#include <list>
#include <string>
#include <vector>

namespace GP
{
//有向图类
struct Digraph
{
    //有向图类默认构造函数
    Digraph() = default;
    //创建一幅含有v个顶点但无边的有向图
    explicit Digraph(int V) : numV(V)
    {
        adjTable = std::vector<std::vector<int>>(V);
    }
    //顶点总数
    auto V() const -> int
    {
        return numV;
    }
    //边的总数
    auto E() const -> int
    {
        return numE;
    }
    //有向图中添加一条边v->w
    void addEdge(int v, int w)
    {
        adjTable[v].push_back(w);
        ++numE;
    }
    //由v指出的边所连接的所有顶点
    auto adj(int v) const -> std::vector<int>
    {
        return adjTable[v];
    }
    //该图的反向图
    auto reverse() const -> Digraph
    {
        Digraph R(numV);
        for (int v = 0; v != numV; ++v)
        {
            for (auto &&w : adj(v))
            {
                R.addEdge(w, v);
            }
        }
        return R;
    }
    //对象的字符串表示
    auto toString() const -> std::string
    {
        std::string s = std::to_string(numV) + " vertices, " +
                        std::to_string(numE) + " edges\n";
        for (int v = 0; v != numV; ++v)
        {
            s += std::to_string(v) + ": ";
            for (auto &&w : adj(v))
            {
                s += std::to_string(w) + " ";
            }
            s += "\n";
        }
        return s;
    }

  private:
    //顶点数量
    int numV = 0;
    //边数量
    int numE = 0;
    //邻接表
    std::vector<std::vector<int>> adjTable;
};

//有向图可达性类
struct DirectedDFS
{
    DirectedDFS() = default;
    //在G中找到从s可达的所有顶点
    DirectedDFS(const Digraph &G, int s)
    {
        markedTable = std::vector<bool>(G.V());
        dfs(G, s);
    }
    //在G中找到从sources中的所有顶点可达的所有顶点
    DirectedDFS(const Digraph &G, const std::vector<int> &sources)
    {
        markedTable = std::vector<bool>(G.V());
        for (auto &&s : sources)
        {
            if (!markedTable[s])
            {
                dfs(G, s);
            }
        }
    }
    // v是可达的么?
    auto marked(int v) -> bool
    {
        return markedTable[v];
    }

  private:
    //深度优先搜索
    void dfs(const Digraph &G, int v)
    {
        markedTable[v] = true;
        for (auto &&w : G.adj(v))
        {
            if (!markedTable[w])
            {
                dfs(G, w);
            }
        }
    }
    //标记表
    std::vector<bool> markedTable;
};
} // namespace GP

namespace REG
{
//非确定有限状态自动机
struct NFA
{
    //构造非确定有限状态自动机
    explicit NFA(std::string regExp)
        : regExpStr(std::move(regExp)),
          regStrSize(static_cast<int>(regExpStr.size())),
          matchDiG(GP::Digraph(regStrSize + 1))
    {
        // 设置 "(", "|" 的栈,用于对应 ")"
        std::vector<int> orLBracketStack;
        orLBracketStack.reserve(regStrSize);
        for (int regCharOrder = 0; regCharOrder != regStrSize; ++regCharOrder)
        {
            int leftBracket = regCharOrder;
            //遇到 "(" "|" 压栈
            if (regExpStr[regCharOrder] == '(' ||
                regExpStr[regCharOrder] == '|')
            {
                orLBracketStack.push_back(regCharOrder);
            }
            //遇到 ")" 出栈
            else if (regExpStr[regCharOrder] == ')')
            {
                int orSymbol = orLBracketStack.back();
                orLBracketStack.pop_back();
                //如果出栈的是 "|"
                if (regExpStr[orSymbol] == '|')
                {
                    //再出栈
                    leftBracket = orLBracketStack.back();
                    orLBracketStack.pop_back();
                    //有向图添加两条边,以 leftBracket 指向 orSymbol + 1
                    matchDiG.addEdge(leftBracket, orSymbol + 1);
                    // orSymbol 指向 rightBracket ")"
                    matchDiG.addEdge(orSymbol, regCharOrder);
                }
                else
                {
                    leftBracket = orSymbol;
                }
            }
            //如果下一个字符为 "*"
            if (regCharOrder < regStrSize - 1 &&
                regExpStr[regCharOrder + 1] == '*')
            {
                //有向图加入 2 条边,以 leftBracket 或 regCharOrder 指向 "*"
                matchDiG.addEdge(leftBracket, regCharOrder + 1);
                //以 "*" 指向 leftBracket 或 regCharOrder
                matchDiG.addEdge(regCharOrder + 1, leftBracket);
            }
            //如果 regCharOrder 是 "(" 或 ")"  或 "*"
            if (regExpStr[regCharOrder] == '(' ||
                regExpStr[regCharOrder] == '*' ||
                regExpStr[regCharOrder] == ')')
            {
                //有向图加入一条边,以 regCharOrder 指向 regCharOrder + 1
                matchDiG.addEdge(regCharOrder, regCharOrder + 1);
            }
        }
    }
    //识别文本是否复合正则表达式
    auto recognizes(const std::string &txt) -> bool
    {
        //可达位置表
        std::vector<int> pc;
        //匹配表
        std::vector<int> match;
        //构建正则表达式从 0 位可达的位置
        GP::DirectedDFS dfs(matchDiG, 0);
        //将可达位置输入可达表内
        for (int v = 0; v != matchDiG.V(); ++v)
        {
            if (dfs.marked(v))
            {
                pc.push_back(v);
            }
        }
        //扫描文本进行匹配
        for (char i : txt)
        {
            match.clear();
            //所有可达位置
            for (int v : pc)
            {
                if (v < regStrSize)
                {
                    //如果有可达位置匹配文本字符,或可达位置是 "."
                    if (regExpStr[v] == i || regExpStr[v] == '.')
                    {
                        //匹配字符向后移动一位
                        match.push_back(v + 1);
                    }
                }
            }
            //清空可达位置表
            pc.clear();
            //根据匹配的位置出发,重新构建可达位置
            dfs = GP::DirectedDFS(matchDiG, match);
            //遍历 epsilon 转换有向图
            for (int v = 0; v != matchDiG.V(); ++v)
            {
                //如果可达,输入可达位置表
                if (dfs.marked(v))
                {
                    pc.push_back(v);
                }
            }
        }
        //如果任意可达位置表中有等于 regStrSize 值,返回有匹配,否则返回匹配失败
        return std::any_of(pc.rbegin(), pc.rend(),
                           [this](int v) -> bool { return v == regStrSize; });
    }

  private:
    // 正则匹配表达式
    std::string regExpStr;
    // 正则匹配表达式字符数量
    int regStrSize = 0;
    // epsilon 转换有向图
    GP::Digraph matchDiG;
};

//非确定有限状态自动机改良版
struct NFA_s
{
    //构造非确定有限状态自动机
    explicit NFA_s(std::string regExp)
        : regExpStr(std::move(regExp)),
          regStrSize(static_cast<int>(regExpStr.size())),
          matchDiG(GP::Digraph(regStrSize + 1))
    {
        std::list<int> orLBracketStack;
        for (int i = 0; i != regStrSize; ++i)
        {
            int leftBracket = i;
            if (regExpStr[i] == '(' || regExpStr[i] == '|')
            {
                orLBracketStack.push_back(i);
            }
            else if (regExpStr[i] == ')')
            {
                int orSymbol = orLBracketStack.back();
                orLBracketStack.pop_back();
                if (regExpStr[orSymbol] == '|')
                {
                    leftBracket = orLBracketStack.back();
                    orLBracketStack.pop_back();
                    matchDiG.addEdge(leftBracket, orSymbol + 1);
                    matchDiG.addEdge(orSymbol, i);
                }
                else
                {
                    leftBracket = orSymbol;
                }
            }
            if (i < regStrSize - 1 && regExpStr[i + 1] == '*')
            {
                matchDiG.addEdge(leftBracket, i + 1);
                matchDiG.addEdge(i + 1, leftBracket);
            }
            if (regExpStr[i] == '(' || regExpStr[i] == '*' ||
                regExpStr[i] == ')')
            {
                matchDiG.addEdge(i, i + 1);
            }
        }
    }
    auto recognizes(const std::string &txt) -> bool
    {
        std::vector<int> pc;
        unsigned txtsize = txt.size();
        GP::DirectedDFS dfs(matchDiG, 0);
        for (int v = 0; v != matchDiG.V(); ++v)
        {
            if (dfs.marked(v))
            {
                pc.push_back(v);
            }
        }
        for (int i = 0; i != txtsize; ++i)
        {
            std::vector<int> match;
            for (int v : pc)
            {
                if (v < regStrSize)
                {
                    if (regExpStr[v] == txt[i] || regExpStr[v] == '.')
                    {
                        match.push_back(v + 1);
                    }
                }
            }
            pc.clear();
            dfs = GP::DirectedDFS(matchDiG, match);
            if (dfs.marked(regStrSize) && i == txtsize - 1)
            {
                return true;
            }
            for (int v = 0; v != matchDiG.V(); ++v)
            {
                if (dfs.marked(v))
                {
                    pc.push_back(v);
                }
            }
        }
        return false;
    }

  private:
    // 匹配转换
    std::string regExpStr;
    // 状态数量
    int regStrSize = 0;
    // epsilon 转换
    GP::Digraph matchDiG;
};
} // namespace REG

#endif
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不停感叹的老林_<C 语言编程核心突破>

不打赏的人, 看完也学不会.

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值