Javascript 正则04-正则表达式回溯法原理

正则表达式

正则表达式是匹配模式, 要么匹配字符, 要么匹配位置

参考网址

Chapter four: 正则表达式回溯法原理

研究匹配原理的时候,必须要知道什么是 ‘回溯’

主要内容

  1. 没有回溯的匹配
  2. 有回溯的匹配
  3. 常见的回溯匹配
  4. 小结

3. 常见的回溯

这个尝试的过程貌似是 一位一位字符或者一组一组字符的尝试(在一组字符中也是一位一位的尝试)

回溯法也称作试探法,基本思想是: 从问题的某一种状态触发(初始状态),搜索这种状态出发所能达到的所有“状态”, 当一条路走到 “尽头” 的时候(不能再前进了),再后退一步或若干步,从另一种可能 “状态” 出发,继续搜索,直到所有 “路径” (状态) 都尝试过。这种不断 “前进”、不断 “回溯”寻找解的方法,称作 “回溯法”。

本质上就是 深度优先搜索算法。 其中退到之前某一步过程我们称之为回溯。从定义来看,当路走不通的时候,就会发生 “回溯”。 即, 尝试匹配失败的时候,接下来一步通常就是回溯。

3.1 贪婪量词

之前的例子都是贪婪量词相关的,比如 b{1,3} 因为其是贪婪的,尝试可能的顺序是从多往少方向去尝试。首先会尝试 bbb , 探后再看整个正则是否匹配成功。不能匹配时,吐出一个 ‘b’ , 即在 bb 基础上继续尝试。如果还不行,再吐出一个,再尝试。如果还不行,只能说明匹配失败了。

如果多个贪婪量词紧挨着,并相互冲突,此时会怎样?

答案是: 先下手为强(从左到右依次匹配),因为深度优先搜索。如下:

var string = '12345';
var reg = /(\d{1,3})(\d{1,3})/;
console.log(string.match(reg));
// ["12345", "123", "45", index: 0, input: "12345", groups: undefined]

第一个分组 \d{1,3} 匹配 123, 第二个分组 \d{1,3} 匹配剩下的 45

3.2 惰性量词

惰性量词就是在贪婪量词后面加上问号。表示尽可能少的匹配,比如:

var string = '12345';
var reg = /(\d{1,3}?)(\d{1,3})/;
console.log(string.match(reg));
// ["1234", "1", "234", index: 0, input: "12345", groups: undefined]

第一个分组 \d{1,3}? 匹配 1, 第二个分组 \d{1,3} 匹配剩下的 234

虽然惰性量词不贪婪,但是也会有 回溯现象。比如正则:


var string = '12345';
var reg = /^(\d{1,3}?)(\d{1,3})$/;
console.log(string.match(reg));
// VM80:3 (3) ["12345", "12", "345", index: 0,input: "12345", groups: undefined]

为了整体的匹配成功,惰性量词子模式也得多塞点。因此最后 \d{1,3} 匹配的字符是 ‘12’, 是两个数字,而不是一个数字。

分支结构

分支结构在匹配过程中也是惰性的。比如: pattern:/can|candy/, 去匹配字符串 candy ,的得到结果是 can,如果前面的满足了,后面的就不会再检验了。

分支结构, 可能前面的子模式会形成局部匹配,如果接下来的表达式整体不匹配时候,仍会继续尝试剩下的分支。这种尝试也可以看成一种回溯。

我的理解还是 在分支结构中,会尽可能匹配,直到匹配成功或者匹配失败

比如:

var reg = /^(?:can|candy)$/;

'candy'.match(reg);

小结

回溯法简单总结为: 正因为有多种可能,所以要一个一个尝试。直到,要么到某一步时候匹配成功,整体匹配成功了;要么都试完后,整体匹配不成功(简而言之,匹配所有可能,直到存在一个成功,或者没有符合匹配模式的(失败))

  1. 贪婪量词 ‘试’ 的策略是: 为了整体的成功,尽可能多的匹配
  2. 惰性量词 ‘试’ 的策略是: 为了整体的成功,尽可能少的匹配
  3. 分支结构 ‘试’ 的策略是: 为了整体的成功,更换子匹配模式

既然有回溯的过程,匹配效率肯定会低。相对那些 DFA 引擎。

JS 正则引擎是 NFA, NFA 是 “非确定型有限自动机”简写

大部分语言的正则都是 NFA。(NFA 正则引擎具有匹配慢,编译快的特征)

你可能感兴趣的:(javascript)