我们之前已经见过了正则表达式的一些很特殊的用法。这里我们再来看一个:用正则表达式判断数的整除性。例如,下面这个表达式可以匹配01串S当且仅当S是一个可以被3整除的二进制数。
^1((10*1)|(01*0))*10*$
如果你不信的话,不妨把下面这段代码粘贴进浏览器的地址栏,然后回车运行一下:
javascript:alert(/^1((10*1)|(01*0))*10*$/.test("1000000100"))
被test的是516的二进制表达。516可以被3整除,因此程序返回true。你可以自己把1000000100换成其它的二进制数试试。
但是呢,从这个正则表达式里我们竟看不出任何端倪。奇怪了,为什么这个正则表达式可以用于判断整除性?能被3整除的二进制数究竟有何规律?
其实,能被3整除的二进制数并没有什么明显的规律。这个正则表达式的求法可以说是相当暴力的。这一切的谜底很简单——判断一个数的整除性能轻易地用有限状态自动机实现,而有限状态自动机又可以翻译成正则表达式。
注意到,一个二进制数后面加一个“0”相当于该数乘以2,一个二进制数后面加一个“1”相当于该数乘2加1。设定三个状态,分别叫做0、1和2,它们表示当前的数除以3所得的余数。如果对于某个i和j,有i*2≡j (mod 3),就加一条路径i→j,路径上标一个字符“0”;如果i*2+1≡j (mod 3),则在路径i→j上标记“1”。状态0既是我们的初始状态,也是我们的最终状态。我们的自动机就做好了。现在,假如二进制数10010走进来了。从状态0出发,机器首先读到一个“1”,于是当前位置挪到状态1,表明目前该数模3余1;然后,系统读了一个“0”,我们紧跟着走到状态2,表明二进制数“10”被3除余2;下一步,我们回到状态1,表明“100”除以3余1;再往后,我们得知“1001”能被3整除。最后呢,我们读到一个0,“1001”的两倍当然还是能被3整除,我们依旧停留在原位。我们得到结论:二进制数10010能被3整除。
有限状态自动机是可以转化为正则表达式的。上面的这个自动机转化起来非常容易。我们可以先试着用自然语言叙述一下。首先,每个二进制数第一位必然为“1”。到达状态1后,我们可以随意地、任意多次地在状态1周围绕圈圈,最终回到状态1。临近末尾,我们再读到一个“1”返回状态0,这之后随便读多少个“0”都可以了。现在问题分解为:我们又如何用正则表达式表述“从状态1出发随意地走最终回到状态1”呢?在本例中,这是很好描述的:它可以是字符串“1000..001”和“0111..110”的任意组合。把这些东西用正则表达式写出来,就是我们刚才那个神秘的式子:1((10*1)|(01*0))*10* 。
从原理上说,我们可以这种方法求出任意进制下用于任意数的整除性判断的正则表达式。只不过,它们所对应的自动机都更加复杂,从而得出一长串令人眼花缭乱的表达式。本文开头我把1((10*1)|(01*0))*10*的来源作为一个有趣的问题提出来,因为这个式子恰好不长,让人很难想到这背后藏有一个普适的暴力算法。
强大的“反引用”:http://www.sxnsx.com/niubility-regular-and-divisibility-deciding/
沙发,好好学习。
正则表达式在perl中用的也很广泛,就是不容易看懂。
最近在搞双色球,有没有好的算法啊?
matrix牛,请问能不能把本月的UyHiP的题目翻译一下啊,有些地方没看懂 – -!
我还是trackback你的文章来写的,Matrix同学的文章是不是不允许trackback?
这个方法确实暴力,但是作为一个正则文法能表达到这样的程度确实有趣。
我看了一个小时的英文终于明白了题目,结果:
居然有翻译网站了=.=
New: The Using your Head is Permitted riddles are now translated monthly to Mandarin. Visit http://sites.google.com/site/uyhipchinese/ for the translation site. (Thanks, Ito Kang!)
之前看过类似的所以不觉得很奇怪。
to楼上 似乎需要人工验证trackback的。
“能被3整除的二进制数并没有什么明显的规律.”
不对吧 …
比如整除11的十进制数就很有规律因为10=11-1
同理2=3-1
这是看到的又一种被3整除的判断算法
学习了
哈哈,不错哟
朋友,方便友情链接么?
http://www.hemin.cn/blog
0 is also divisible by 3
javascript:/^0*(1(01*0)*1)*$/.test(“10”);
javascript:for(var i=0;i>=1);document.write(“”+i+”t”);document.write(/^0*(1(01*0)*10*)*$/.test(t));}
0也可以被3整除的. 如果终结状态画在起始状态的话空串ε也是可以的:
0*(1(01*0 | 10*1)*10* | ε)
如果不要空串还要加一个起始状态S等到有输入0,1后再过度到状态0
(0|1)0*(1(01*0 | 10*1)*10* | ε)
纠正: 如果规定空串不能被3整除输入1应该到状态1不是状态0:
(0*1)((01*0 | 10*1)*10* ) | 00*
最后的00*是0也能被3整除的情况
最正确的答案0*(1(01*0)1|0*)*
这种问题,写自动机很简单,但是从自动机转换成表达式的算法是什么?以前就做过类似的题目,想过可以先写自动机,但是不知道如何从自动机变成表达式……
/^(0*|(1(01*0)*1)*)*$/
^(1(01*0)*1|0)*$