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

1. 背景

Backtracking occurs when a regular expression pattern contains optional quantifiers or alternation constructs, and the regular expression engine returns to a previous saved state to continue its search for a match.

正则表达式中的回溯,发生在包含限定符(quantifiers)和替换构造(alternation constructs)的场景中,
这种情况下,正则表达式引擎会退回到前一个经历过的状态,
然后从这个状态开始继续往下匹配。

2. 无回溯时的线性时间比较

If a regular expression pattern has no optional quantifiers or alternation constructs, the regular expression engine executes in linear time. That is, after the regular expression engine matches the first language element in the pattern with text in the input string, it tries to match the next language element in the pattern with the next character or group of characters in the input string. This continues until the match either succeeds or fails. In either case, the regular expression engine advances by one character at a time in the input string.

不包含限定符(quantifiers)或替换构造(alternation constructs)的正则表达式,
会在线性时间内完成匹配。

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

例如,

/e{2}\w\b/.exec('needing a reed');

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

步骤 模式位置 字符串位置 结果
1 e "needing a reed" 不匹配
2 e "eeding a reed" 可能匹配
3 {2} "eding a reed" 可能匹配
4 \w "ding a reed" 可能匹配
5 \b "ing a reed" 可能的匹配失败
6 e "eding a reed" 可能匹配
7 {2} "ding a reed" 可能的匹配失败
8 e "ding a reed" 不匹配
9 e "ing a reed" 不匹配
10 e "ng a reed" 不匹配
11 e "g a reed" 不匹配
12 e " a reed" 不匹配
13 e "a reed" 不匹配
14 e " reed" 不匹配
15 e "reed" 不匹配
16 e "eed" 可能匹配
17 {2} "ed" 可能匹配
18 \w "d" 可能匹配
19 \b "" 可能匹配

3. 限定符或替换构造引起的回溯

大部分正则表达式引擎使用了非确定有限状态自动机(NFA),
确定有限状态自动机(DFA)不同的是,
DFA由输入字符串驱动,NFA由正则表达式中的元素来却动。

Therefore, the regular expression engine tries to fully match optional or alternative subexpressions. When it advances to the next language element in the subexpression and the match is unsuccessful, the regular expression engine can abandon a portion of its successful match and return to an earlier saved state in the interest of matching the regular expression as a whole with the input string. This process of returning to a previous saved state to find a match is known as backtracking.

正则表达式引擎,会逐一取出正则表达式中的元素,与输入字符串进行匹配,
如果正则表达式的当前元素,无法匹配到字符串上时,
就会退回到之前的匹配状态,从那里开始再尝试重新的可能性,称之为回溯。

/.*(es)/.exec('Essential services are provided by regular expressions.');
步骤 模式位置 字符串位置 结果
1 . 'Essential services are provided by regular expressions.' 可能匹配
2 * '' 可能匹配
3 e '' 可能的匹配失败
4 * '.' 可能匹配
5 e '.' 可能的匹配失败
6 * 's.' 可能匹配
7 e 's.' 可能的匹配失败
8 * 'ns.' 可能匹配
9 e 'ns.' 可能的匹配失败
10 * 'ons.' 可能匹配
11 e 'ons.' 可能的匹配失败
12 * 'ions.' 可能匹配
13 e 'ions.' 可能的匹配失败
14 * 'sions.' 可能匹配
15 e 'sions.' 可能的匹配失败
16 * 'ssions.' 可能匹配
17 e 'ssions.' 可能的匹配失败
18 * 'essions.' 可能匹配
19 e 'essions.' 可能匹配
20 s 'ssions.' 可能匹配

首先,正则表达式引擎会使用.*(贪婪匹配)与整个字符串进行匹配,
然后再尝试匹配正则表达式后面的元素e
这时候,输入字符串已经没有剩余的字符了,匹配失败。

接着,正则表达式引擎会进行回溯,返回到上一次成功匹配时的状态,
即,已经匹配了Essential services are provided by regular expressions
但还剩下一个字符.没有匹配时的状态,再尝试匹配e

发现这次也失败了,然后正则表达式引擎就会继续回溯,
直到回溯到匹配了输入字符串Essential services are provided by regular expr
剩余essions.的状态。

最后尝试匹配es都成功了,匹配结束。

4. 嵌套可选限定符的指数时间比较

/^(a+)+$/.exec('aaaaa!');
步骤 模式位置 字符串位置 结果
1 ^ 'aaaaa!' 可能匹配
2 ^a 'aaaaa!' 可能匹配
3 ^(a+) '!' 可能匹配
4 ^(a+)+ '!' 可能匹配
5 ^(a+)+$ '!' 可能的匹配失败
6 ^(a+) 'a!' 可能匹配
7 ^(a+)+ 'a!' 可能匹配
8 ^(a+)+$ 'a!' 可能的匹配失败
9 ^(a+) 'aa!' 可能匹配
10 ^(a+)+ 'aa!' 可能匹配
11 ^(a+)+$ 'aa!' 可能的匹配失败
12 ^(a+) 'aaa!' 可能匹配
13 ^(a+)+ 'a!' 可能匹配
14 ^(a+)+$ 'a!' 可能的匹配失败
15 ^(a+)+ 'aaa!' 可能匹配
16 ^(a+)+$ 'aaa!' 可能的匹配失败
17 ^(a+) 'aaaa!' 可能匹配
18 ^(a+)+ '!' 可能匹配
19 ^(a+)+$ '!' 可能的匹配失败
20 ^(a+)+ 'a!' 可能匹配
21 ^(a+)+$ 'a!' 可能的匹配失败
22 ^(a+)+ 'aa!' 可能匹配
23 ^(a+)+$ 'aa!' 可能的匹配失败
24 ^(a+)+ 'aaa!' 可能匹配
25 ^(a+)+$ 'aaa!' 可能的匹配失败
26 ^(a+)+ 'aaaa!' 可能匹配
27 ^(a+)+$ 'aaaa!' 可能的匹配失败

正则表达式引擎,首先会对字符串开头^进行匹配,
然后对(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!,所有的可能性都尝试过了,匹配失败。

5. CPU 100%的例子

正则表达式的回溯,会占用大量CPU时间,
以下示例可让CPU占用率飙升到100%,

console.time('backtracking');
/^((((((.*).)*.)*.)*.)*.)*x$/.exec('123456789012345!');
console.timeEnd('backtracking');
backtracking: 18457.3369140625ms

参考

Backtracking in Regular Expressions

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容