[JS] 正则表达式的回溯方式

JavaScript010

[JS] 正则表达式的回溯方式,第1张

正则表达式中的回溯,发生在包含限定符(quantifiers)和替换构造(alternation constructs)的场景中,

这种情况下,正则表达式引擎会退回到前一个经历过的状态,

然后从这个状态开始继续往下匹配。

不包含限定符(quantifiers)或替换构造(alternation constructs)的正则表达式,

会在线性时间内完成匹配。

正则表达式引擎,首先会从正则表达式中取出一个元素,然后拿着它和输入字符串进行匹配,

然后再在正则表达式中取下一个元素,与输入字符串的下一个字符进行匹配。

例如,

尽管正则表达式中包含 {2} ,但它不是可选限定符,因此该正则表达式在匹配过程中不回溯,

整个匹配过程如下,

大部分正则表达式引擎使用了 非确定有限状态自动机 (NFA),

与 确定有限状态自动机 (DFA)不同的是,

DFA由输入字符串驱动,NFA由正则表达式中的元素来却动。

正则表达式引擎,会逐一取出正则表达式中的元素,与输入字符串进行匹配,

如果正则表达式的当前元素,无法匹配到字符串上时,

就会退回到之前的匹配状态,从那里开始再尝试重新的可能性,称之为回溯。

首先,正则表达式引擎会使用 .* (贪婪匹配)与整个字符串进行匹配,

然后再尝试匹配正则表达式后面的元素 e ,

这时候,输入字符串已经没有剩余的字符了,匹配失败。

接着,正则表达式引擎会进行回溯,返回到上一次成功匹配时的状态,

即,已经匹配了 Essential services are provided by regular expressions ,

但还剩下一个字符 . 没有匹配时的状态,再尝试匹配 e 。

发现这次也失败了,然后正则表达式引擎就会继续回溯,

直到回溯到匹配了输入字符串 Essential services are provided by regular expr ,

剩余 essions. 的状态。

最后尝试匹配 e 和 s 都成功了,匹配结束。

正则表达式引擎,首先会对字符串开头 ^ 进行匹配,

然后对 (a+) 进行贪婪匹配(吃掉5个 a ),

(a+)+ 对 (a+) 这个捕获组也会进行贪婪匹配,捕获组最多只能重复1次了,

输入字符串中的还剩余字符 ! ,不能匹配正则表达式的 $ (字符串结尾),匹配失败。

接着正则表达式引擎就会回溯,先回到对捕获组的贪婪匹配上 (a+)+ ,

因为已经是最少的重复次数了(1次),还要继续回溯,

回到 (a+) 的贪婪匹配上,这次只吃掉4个 a ,

接着再重复上面的捕获组贪婪匹配 (a+)+ ,字符串结尾 $ ,匹配失败。

这样一直进行和回溯下去,注意从 (a+) 匹配了2个 a 开始,

(a+)+ 的行为也要进行回溯了, (a+)+ 会贪婪匹配2个捕获组 (aa)(aa)a! ,

结果发现 $ 不能匹配时,回溯到 (a+)+ ,然后只匹配1个捕获组, (aa)aaa! ,

结果 $ 还是不能匹配,再回溯到 (a+) 的状态。

然后 (a+) 只能匹配1个 a 了, (a+)+ 贪婪匹配5个捕获组, (a)(a)(a)(a)(a)! ,

最后的 $ 不能匹配 ! 时,先回溯捕获组的匹配,它先匹配4个捕获组, (a)(a)(a)(a)a! ,

结果 $ 不能匹配 a! ,然后回溯捕获组的匹配3个捕获组, (a)(a)(a)aa! 。

如此进行下去,直到捕获组匹配只匹配了一个捕获组 (a)aaaa! ,

而 $ 不能匹配 aaaa! ,所有的可能性都尝试过了,匹配失败。

正则表达式的回溯,会占用大量CPU时间,

以下示例可让CPU占用率飙升到100%,

Backtracking in Regular Expressions

题目:全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列

输入:nums = [1,2,3] //目标数组

输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

这里用回溯是避免重复,导致答案错误

八皇后问题

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

工作原理

首先从定义知道,两个皇后都不能处于同一行,所以第0个皇后放在第0行,第一个皇后放在第1行,以此类推。

先在第0行第0个格子(0,0)放一个皇后0,接着把处于同一行、同一列或同一斜线上的格子都标记为皇后0;

然后把皇后1放到第1行标记为-1的格子中,以此类推直到放下皇后7(即最后一个皇后)。

若中途出现放皇后 iQueen时,第 iQueen行所有格子已经被全部标记,即if( arr[ iQueen*n + i ].index == -1 )的判断,则回溯到上一层函数(其实就是没有进入到if分支,所有没有进行递归了,代码执行完自然会跳回上一层函数继续执行)。

注意此时的执行环境(exection context)已经变了,所有setQueen函数内定义的变量全部回溯到上一层函数递归到下一层函数前的状态,即执行setQueen( iQueen + 1 )这行代码前的状态,例如递归前i=2,iQueen=1,无论下一层函数里的i和iQueen怎样变化,回溯后还是i=2,iQueen=1,然后紧接着执行未执行完的代码。