计算理论101:这可能是讲FSM的最生动的一篇了

本文参考了:

简介

这是《计算理论101》系列的第一篇,将从最基本的有限状态机讲起。主要参考的课程和书籍有: Introduction to the Theory of Computation 3rd Edition【Stonehill college CS】Introduction to the Theory of Computation

为什么要学习『计算理论』?

如 Stonehill college 的 Professor of Computer Science Shai Simonson所说,这可能是计算机课程中最抽象的一门了,但却是任何 computer scientist(我觉得可以延生至任何真正热爱 CS 的人)至少需要了解的一门课。这门课不会教你如何写代码,也不会教你如何做一个计算机,而是着重于『计算机科学』中的『科学』两个字,去了解计算机科学发展几十年来前人闪耀的思想。

还有一点,我认为也是很重要的,那就是学习新知识那份最单纯的快乐。

好了,废话不多说,开始正题~

什么是有限状态机

以一个程序员的角度,我的理解就是内存有限的一个机器,上面定义了一些函数,可以从一个状态跳转到另一个状态。严格的数学定义,一个有限状态机可以定义为一个五元组,如下图表示(来源于Introduction to the Theory of Computation 3rd Edition P35):

可以用图直观地表示:下图这个状态机,圆圈表示的是可能出现的状态,可能输入的值为0和1,装换函数就是那些箭头,开始状态为 q1,accept 状态为 q2(用双圆圈表示)。

一个有限状态机的定义其实就是这么简单。

有限状态机应用

有限状态机一个最显然的特点就是内存有限,无法记忆所有的历史输入,所以它能够解决的问题是有限的。至于什么问题能够解决,什么不能,后面再说。先来看看有限状态机的应用。

嵌入式领域:自动门

这是《Introduction to the Theory of Computation 3rd Edition》书中提到的一个例子。

下图中,门口和门背各有一个感应器(front 为门口,rear 为门背)。门的控制开关一共有两个状态:OPEN 和 CLOSED,输入一共有四种情况:FRONT(门口有人)、REAR(门背有人)、BOTH(门口门背都有人)、NEITHER(两边都没人)。

有一个图表示状态转换过程为:

稍微解释一下:

  • 处于 CLOSED 状态,只有前面的 pad 检测到有人时才会打开,从 CLOSED 状态变为 OPEN 状态,其余情况下维持状态不变。
  • 处于 OPEN 状态时,只有当两边都没有检测到人时,才转换为 CLOSED 状态。

我对于这个门的设计表示怀疑,处于 CLOSED 状态时,难道 REAR PAD 检测到人不应该变成 OPEN 状态吗?也许是我哪里理解得不对,有童鞋知道的欢迎指出。不过这一点不影响我们对于有限自动机的理解。

这个自动门其实就可以看作是一个最简单的计算机了,它只有一个 bit 的存储空间,可以记录当前门的状态是OPEN 还是 CLOSED。类似的还有电梯, 饮料机 等等。

事实上,最开始我们骨灰级的程序员(计算机科学家)们,面对的就是类似的情况,存储空间极其有限。现在的很多嵌入式设备,内存同样非常有限,所以有限状态机还是有用武之地的。

编程领域:正则表达式

这里推荐一个网站: regexper.com/ ,能够非常直观地将正则表达式还原为一个 finite state machine。另外开源地址如下: gitlab.com/javallone/r…

举几个正则表达式例子:

匹配手机号:/^1(3|4|5|7|8)\d{9}$/

匹配邮箱:^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$

匹配 IP 地址:^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$

这里不深入代码层,不然这篇文章就讲不完了。有兴趣的童鞋自己 Google 一下,后面有机会可能会再次讲到。

其他

这个 stackexchange 的链接里面提到了很多: softwareengineering.stackexchange.com/questions/4…

设计有限状态机

实例一

设计一个FSM,能够识别被3整除的二进制字符串。比如accept 1001,reject 1101。 首先,我们可以把所有余数(reminder)的可能性当作状态,也就是三个状态:0,1,2,其中0时 accept 状态。 然后分析一下状态转换是怎么样的?二进制数,末位添加一个0表示乘以2,末位加1表示乘以2再加1,对于余数,同样是乘2或者乘2加1,只不过余数会『进位』。

  • 从起始状态开始:最开始没有输入,也就是0。
  • 对于状态0:输入为0时维持不变,为1会跳转到1。

  • 对于状态1,输入为0时,余数要乘以2,变成了2;输入为1时,余数乘2加1,结果变为0(3)。

  • 对于状态2:输入0乘以2,变为1(4);输入1乘以2再加一,变为2(5)。

这样就做完了,一个用普通算法很难解决的问题,用FSM是不是很简单?会设计识别能被3整除的 FSM,接下来被4、5、6、7、8整除的是不是也会了?这里,被2^k(k=1,2,3,4)整除还有一个更简单的方法:转换为判断末位至少有k 个0

动手来画一画:

实例二

设计一个 FSM,能够识别 能被4整除的二进制字符串(末尾至少有两个0)。 这里把状态设计为目前为止末尾收到了几个0,一共有三个状态:0个0,1个0,2个0。

有这个还可以推广到模式字符串识别,比如要识别特定子字符串 ,请看下面这个例子。

实例三

设计一个 FSM,能够识别子字符串abcab

为了简单起见,这里把圆圈省略掉了,同时把中间的某些节点的状态转换忽略掉了。

  • 最开始,状态为未匹配任意字符。
  • 在起始状态:输入 a,跳转到 a;输入其他状态保持不变。
  • 在状态 a:输入 b 跳转到 ab;输入 a 维持状态不变;输入其他跳回起始状态。
  • 在状态 abc:输入 a 跳转到 abca;否则跳回起始状态。
  • 在状态 abca:输入 b 跳转到 abcab,也就是accept 状态;输入 a 跳转回 a(注意不是跳转回起始状态,因为现在是 abcaa,也就相等于 a);输入其他跳转回起始状态。
  • 在状态 abcab:无论输入什么,都维持不变,因为当前已经匹配成功了。

总结一下

状态机的定义非常简洁,但是功能很强大,而且非常有意思不是吗?

码字很辛苦,图文并茂更辛苦,点个赞鼓励一下~

广告时间,欢迎大家关注我的微信公众号。同时本文同步于 github: github.com/liaochangji…

猜你喜欢

转载自juejin.im/post/5cbad6d06fb9a068a256c0b8