Skip to content

正则表达式

一、定义与目标

  • 正则表达式(Regular Expression, RE):描述字符串集合(语言)的模式,用于在文本中查找复杂模式。
  • 目标
  • 在长度为 ( N ) 的文本中匹配长度为 ( M ) 的模式。
  • 最坏情况时间复杂度 ( O(MN) ),一般情况更快。
  • 与子字符串查找区别:依赖完整模式,正则表达式支持部分或多种匹配。

二、正则表达式的语法与语义

1. 基本操作
  • 连接:( AB ) 表示 ({AB})。
  • 或(|):( A|B ) 表示 ({A, B}),优先级低于连接。
  • 闭包(*):( A* ) 表示 ({\epsilon, A, AA, \dots}),优先级高于连接。
  • 括号:改变优先级,如 ( (AB)* ) 表示 ({\epsilon, AB, ABAB, \dots})。
2. 形式定义
  • 语法
  • 空字符串 (\epsilon)、单个字符、括号内 RE、连接、或、闭包。
  • 语义
  • (\epsilon):空集。
  • 字符:单元素集合。
  • 括号:内部 RE 集合。
  • 连接:叉乘。
  • 或:并集。
  • 闭包:重复 0 次或多次。
3. 缩略写法
  • 字符集
  • .:任意字符。
  • [A-Z]:范围。
  • [^A-Z]:补集。
  • 闭包扩展
  • +:至少 1 次。
  • ?:0 或 1 次。
  • {n}:( n ) 次。
  • {m-n}:( m ) 至 ( n ) 次。
  • 转义\* 表示 *\n 表示换行等。
4. 示例
  • ( (A|B)(C|D) ):({AC, AD, BC, BD})。
  • ( A(B|C)*D ):如 ( AD, ABD, ABCCBD )。

三、实际应用

  • 子字符串查找:( .pat. ) 检查文本是否含 ( pat )。
  • 合法性检查(表 5.4.4):
  • 电话:( ([0-9]{3})\ [0-9]{3}-[0-9]{4} )。
  • 邮箱:( [a-z]+@([a-z]+.)+(edu|com) )。
  • 工具:grep 打印匹配行。
  • 基因组:( gcg(cgg|agg)*ctg ) 检测基因模式。
  • 搜索:支持 ( | ) 和 ( * ) 的引擎。
  • 理论:如 ( (0|1(010)1)* ) 表示 3 的倍数二进制。
局限
  • 无法描述括号匹配或等长 ( A ) 和 ( B ) 的语言。

四、非确定有限状态自动机(NFA)

1. 定义
  • 特点
  • ( M ) 个状态(对应 RE 字符)+ 接受状态 ( M )。
  • 字符状态:一条黑色匹配边。
  • 元字符状态:红色 (\epsilon)-转换边。
  • 转换
  • 匹配转换:匹配字符,移至下一状态。
  • (\epsilon)-转换:不消耗字符,跳跃状态。
  • 识别:从 0 读完文本到达 ( M )。
2. 示例
  • RE:( ((A*B|AC)D) )。
  • NFA:状态 0-11,输入 ( AAABD ) 可达 ( 11 )。
3. 与 DFA 区别
  • DFA:确定性,每状态单转换。
  • NFA:非确定性,多转换需“猜测”。

五、NFA 模拟

1. 表示
  • re[]:字符数组存 RE。
  • G:有向图存 (\epsilon)-转换。
2. 模拟过程
  • 初始化:从 0 通过 (\epsilon)-转换可达状态集合。
  • 每字符
  • 检查集合中状态匹配当前字符,得到新状态。
  • 扩展新状态的 (\epsilon)-转换。
  • 结果:集合含 ( M ) 则匹配。
  • 代码java public boolean recognizes(String 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 && (re[v] == txt.charAt(i) || re[v] == '.')) match.add(v + 1); 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; }
3. 性能(命题 Q)
  • 时间:( O(MN) ),每次字符处理 ( O(M) )(DFS)。
  • 空间:( O(M) )。

六、NFA 构造

1. 规则
  • 连接:字符到下一状态。
  • 括号:栈记录 ( ( ) 和 ( | )。
  • 闭包:( * ) 前后加双向 (\epsilon)-转换。
  • :( ( ) 到 ( |+1 ),( | ) 到 ( ) )。
2. 实现
  • 代码java 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); } }
  • 性能(命题 R):时间和空间 ( O(M) )。

七、总结

  • 正则表达式:简洁描述复杂模式。
  • NFA:通过非确定性模拟匹配。
  • 算法:构造 ( O(M) ),模拟 ( O(MN) )。
  • 应用:广泛用于搜索、验证和科学计算。