需要一定的正则基础,并且是基于JS写的文章。

正则表达式是从左往右匹配的。在使用正则表达式的时候我们知道/.*/可以匹配一个字字符串中所有的字符,/.*?/却一个字符都匹配不到。/(.*)\d/中的.\*可以匹配除了最后一位数字的所有字符,但是之前说的/.*/不是匹配了所有字符吗为什么后面的\d还可以匹配到一个数字字符?

首先我们要知道对于贪婪模式在进行匹配的时候会首先尝试匹配。意思就是/.*/匹配”abcd”的时候可以选择匹配a和不匹配a都是可以的,但是因为是贪婪模式所以选择了匹配a,b和c和d是同样的道理,到最后匹配完了abcd正则表达式匹配完成并且匹配成功。

对于懒惰模式在进行匹配的时候会首先尝试跳过。就是/.*?/匹配”abcd”字符串的时候首先尝试跳过a的匹配,再跳过b的匹配,直到最后正则表达式匹配完成,射门都没匹配到。

通过上面的描述可以看出贪婪和懒惰只是在每一个字符是否匹配上做的选择不同,相同的是在匹配和跳过的选择中正则表达式都会记住我在这里做了选择,这里还要其他选择。记住这些选择的作用就是当前选择如果走不通了,那么还可以回退到这里选择记录下来的另一条路,这就是回溯。 开始回溯的会选择离当前位置最近的一次选择,就是一个后入先出的栈的模式。

例1:/(.*)\d/匹配”abcd1”

第一步:因为.*是贪婪模式,所以会匹配字符a(并记住也可以不匹配a),往后匹配字符b(也可不匹配)一直往后匹配了字符c,字符d,字符1。

第二步:\d匹配的时候匹配不到字符,整个正则需要回溯,到选择是否匹配字符1的时候,之前选择了匹配现在要选择不匹配,让出了字符1。

第三步:\d匹配字符1,完成整个正则的匹配。

var reg = /(.*)\d/
var str = "abcd1"
var res = str.match(reg) //  ["abcd1", "abcd"] 可以看到分组(.*)匹配到了abcd并不包括1

例2:/(.*)\d/匹配”ab1cd”

第一步:因为.*是贪婪模式,所以会匹配字符a(并记住也可以不匹配a),往后匹配字符b(也可不匹配)一直往后匹配了字符c,字符d,字符1。

第二步:\d匹配的时候匹配不到字符,整个正则需要回溯,到选择是否匹配字符d的时候,之前选择了匹配现在要选择不匹配d,让出了字符d。

第三步:\d匹配字符d,匹配失败。继续回溯.*让出字符c。

第四步:\d继续匹配字符c,匹配失败。还要继续回溯.*让出字符1.

第五步:\d匹配字符1,完成整个正则的匹配。

var reg = /(.*)\d/
var str = "ab1cd"
var res = str.match(reg) //  ["ab1", "ab"] 可以看到分组(.*)匹配到了ab并不包括1

例3:/(.*?)\d/匹配”abcd1”

第一步:因为.*?是懒惰模式,所以不会匹配字符a(并记住可以匹配a)。

第二步:\d匹配字符a,匹配失败。回溯.*?重新选择匹配字符a,并继续放弃了字符b(记住可以匹配字符b)。

第二步:\d匹配字符b,匹配失败。回溯.*?重新选择匹配字符b,并继续放弃了字符c(记住可以匹配字符c)。

第三步:\d匹配字符c,匹配继续失败。继续回溯.*?重新选择匹配了字符c,继续并放弃了匹配字符d(记住可以匹配字符d)。

第四步:\d继续匹配字符d,匹配失败。还要继续回溯.*?重新选择匹配字符d,还是放弃了字符1(记住可以匹配字符1)。

第五步:\d匹配字符1,完成整个正则的匹配。

var reg = /(.*?)\d/
var str = "abcd1"
var res = str.match(reg) //  ["abcd1", "abcd"] 可以看到分组(.*)匹配到了abcd并不包括1

小结

对比例1和例3可以发现懒惰和贪婪模式匹配的结果是相同的,但是这并不意味着这两种匹配模式匹配结果是无差别的。对于两种模式匹配的字符串如果只有一个真确的匹配结果那么确是匹配得到的结果是一样的,但是一步一步检查过来就会知道虽然匹配结果一样但是经过的步骤是不同的。

如果匹配的结果不止一种可能,那么这两种模式匹配得到的结果就不一样了。例如,将字符串“abcd1”换成”abcd11”,那么这两种模式匹配到的结果就一样了。

var reg = /(.*?)\d/
var reg2 = /(.*)\d/
var str = "abcd11"str.match(reg)  // ["abcd1", "abcd"]
str.match(reg2)  // ["abcd11", "abcd1"]

例4:/\w?(\w?)1/匹配“a1”

第一步:\w?匹配字符a(记住可以不匹配字符a)

第二步:(\w?)匹配字符1(记住可不匹配字符1)

第三步:1匹配不到任意字符,返回之前做选择的地方(\w?)从新选择放弃了字符1,什么都没有匹配

第四步:1匹配了字符1,完成整个正则表达式的匹配

var reg = /\w?(\w?)1/
var str = "a1"str.match(reg) // ["a1", ""] 数组的第二个值也就是分组1(\w?)什么都没有匹配到

通过上面的步骤可以看出当需要回溯的时候会选择当前位置最近的一次选择,在重新开始,而不是从整个表达式的第一次选择开始重新选择。这是一个后进先出的模式就像栈一样。

断言/环视中的回溯

首先说结论,断言中的备用状态在断言匹配结束后会被丢弃,整个断言只能当做一个整体存在,回溯的时候不会进入断言中的备用状态。

例5:/(?=(.*))\11/匹配”11111”

这个正则什么都匹配不到。

第一步:(?=(.*))\1匹配了11111

第二步:1并不能匹配到任何字符串,并且准备回溯的时候发现前面并没有可回溯的状态,匹配失败

第三步:以为这就结束了吗?还没有会从第二个1再开始匹配,虽然得到的结果是一样的,直到最后一个1完成匹配,匹配失败,什么都没有匹配到

var reg = /(?=(.*))\11/
var str = "11111"str.match(reg) // null

注:这是固化分组的一种模拟,固化分组指的是放弃分组中的备用状态,语法是(?…)。

分支中的回溯

首先多选分支是从左到右匹配的,并不会因为某个分支的或长或短就优先匹配,这样就会造成分支顺序不正确就导致某些分支永远不会被匹配到。

var reg = /abc|ab|abcd/
var str = "abcd"str.match(reg) // ["abc"]

匹配结果是abc并不是ab,如果将分支调换位置那么得到的结果又将不一样。

var reg = /abcd|abc|ab/
var str = "abcd"str.match(reg) // ["abcd"]
var reg = /ab|abcd|abc/
var str = "abcd"str.match(reg) // ["ab"]

从上面的例子中可以看出多选分支的匹配是从左到右来选择分支来匹配的,并且在分支之间选择的时候会记住备用的选择,以备无法匹配的时候回溯到这里选择另一个分支。

例6:/((aa?)|(aa))a/匹配”aa”

第一步:选择分支(aa?)并且记住可以选择分支(aa),开始匹配字符aa

第二步:a匹配字符a

第三步:a? 匹配第二个a(记住可以不匹配这个a)

第四步:正则中最后一个a匹配的时候发现,无字符可匹配

第五步:回溯到第三步选择不匹配a字符

第六步:正则中最后一个a完成了匹配字符串中的a,整个正则匹配完成

var reg = /((aa?)|(aa))a/
var str = "aa"str.match(reg) // ["aa", "a", "a", undefined]

例7:/((aa)|(aa?))a/匹配”aa”

第一步:选择分支(aa)并且记住可以选择分支(aa?),开始匹配字符aa

第二步:a匹配字符a

第三步:aa 匹配第二个a

第四步:正则中最后一个a匹配的时候发现,无字符可匹配

第五步:回溯到第一步选择分支(aa?)

第六步:这时就会重复例6的步骤,直到整个正则匹配成功

var reg = /((aa)|(aa?))a/
var str = "aa"str.match(reg) // ["aa", "a", undefined, "a"]

注:双引号”表示的是字符串,并不是字符串的一部分

参考

精通正则表达式(第三版)

懒惰和贪婪-正则回溯相关推荐

  1. PHP正则匹配效率,PHP 正则表达式效率 贪婪、非贪婪与回溯分析(推荐)

    先扫盲一下什么是正则表达式的贪婪,什么是非贪婪?或者说什么是匹配优先量词,什么是忽略优先量词? 好吧,我也不知道概念是什么,来举个例子吧. 某同学想过滤之间的内容,那是这么写正则以及程序的.$str ...

  2. php懒惰模式,PHP正则贪婪/懒惰匹配模式

    当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符.例如以下表达式将匹配以a开始,以b结束的最长字符串: a.*b 如果用来搜索"aa ...

  3. java 正则 懒惰_正则表达式的最大最小原则(就是懒惰和贪婪定理),java版本

    今天问了老大,学了一个新名词,其实文章里面有,我没有仔细看,叫做懒惰和贪婪定理.原文如下 当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符.以 ...

  4. 正则表达式学习——(2)正则回溯

    参考文章: https://www.zhihu.com/question/48219401/answer/1476436385 https://www.jianshu.com/p/48dc319f68 ...

  5. php取消贪婪是什么意思,PHP 正则表达式效率 贪婪、非贪婪与回溯分析(推荐)

    先扫盲一下什么是正则表达式的贪婪,什么是非贪婪?或者说什么是匹配优先量词,什么是忽略优先量词? 好吧,我也不知道概念是什么,来举个例子吧. 某同学想过滤之间的内容,那是这么写正则以及程序的. $str ...

  6. php 正则 回溯,php 正则表达式效率 贪婪、非贪婪与回溯分析

    先扫盲一下什么是正则表达式的贪婪,什么是非贪婪?或者说什么是匹配优先量词,什么是忽略优先量词? 好吧,我也不知道概念是什么,来举个例子吧. 某同学想过滤之间的内容,那是这么写正则以及程序的. $str ...

  7. nginx的正则回溯和灾难性回溯

    正则的回溯 我们先了解一下什么是回溯 https://zhuanlan.zhihu.com/p/27417442 这篇文章将的比较详细,我选取其中一个例子给大家简单介绍一下 当用 /".*& ...

  8. php 正则 回溯,PHP正则匹配绕过

    之前没有从机制上去了解过PHP正则匹配绕过具体是怎么一回事,于是主动去网上找了一些资料来加深理解 NFA与正则表达式 常见的正则引擎,被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自 ...

  9. 浅谈动态规划,贪婪,回溯算法联系

    那么看到这三种算法,你应该有所出现: 贪心法是动态规划法的特例,如0-1背包,最小代价生成树(prim算法和cruskal算法),huffman算法,以及地杰斯特拉算法. 动态规划法是一种方法,注意和 ...

最新文章

  1. update_notifier 造成nodejs进程数量增长的问题
  2. SpingMVC ModelAndView, Model,Control以及参数传递
  3. ThreadPool的使用
  4. python学习笔记1-基础语法
  5. 下岗职工_下岗后我如何获得多位软件工程师的面试
  6. 工作311:uni-携带当前参数跳转页面传值
  7. 【Noip模拟 20161005】公约数
  8. 如何实现插入数据时自动更新另外一个表的内容
  9. 站立会议(11月21日)
  10. C语言:指向指针的指针
  11. 从零单排PAT1015,1016,1017,1018
  12. 微信小游戏flappy bird填坑
  13. linux内核之同步
  14. 计算机思维在化学上的应用,信息技术在化学教育中的应用
  15. 教师国培计算机计划,教师国培计划大全
  16. 解除诺顿企业版的 liveupdate 旁边小锁,解除限制 手动 更新诺顿的方法
  17. 微信小程序获取openId,传参问题导致参数无效(errcode: 40013、errcode:40125、errcode“:40029)
  18. Files 使用体验:一款高颜值 Windows 第三方资源管理器
  19. 思维题:三个箱子,一个只装苹果,一个只装橙,另一个装苹果和橙,请问?
  20. 亲历医院蹩脚程序(项目)的糟糕

热门文章

  1. oracle存储过程深入,深入了解oracle存储过程的优缺点
  2. Confluence 6 自动添加用户到用户组
  3. python对象分类
  4. BZOJ 4568 倍增维护线性基
  5. C语言 · 未名湖边的烦恼
  6. Spring_事务(2)
  7. redis key设计技巧
  8. 架构之路(八)从CurrentUser说起
  9. Java中的传值与传引用
  10. 查询SQL中某表里有多少列包含某字段