PHP实现的ε-NFA
来源:互联网 发布:mobi阅读软件 编辑:程序博客网 时间:2024/06/08 19:38
http://blog.sina.com.cn/s/blog_7762edc60101arvf.html
(以下程序部分有改写)
非确定型有穷状态自动机(NFA)与有穷状态自动机(FA,或者叫“确定型”有穷状态自动机 DFA)只在形式上不同,在计算能力上没有区别。
首先,DFA的每一个状态对于输入字母表Σ的每一个字符都有一条且只有一条通向某个状态的路径。在某一状态遇到某一输入字符时,发生的动作是唯一的(确定的)。而NFA的一个状态对于输入字母表Σ中的每一个字符,可以有0或多条通向某个状态的路径。当NFA处于某个状态时,遇到一个输入字符,如果没有对应这个字符的离开路径,那么机器死掉(拒绝输入字符串);如果有一条离开路径,那么和DFA一样,进到那个状态;如果2条(含)以上的离开路径,此时NFA分裂成两个NFA,分别对接下来的输入串做动作(类似一个进程fork了一下)。最终只要有NFA的一份拷贝接受了字符串,整个NFA就接受这个字符串。
其次,NFA的状态可以读入空字符ε,进到0至多个状态。在输入字符串的两个相邻字符之间(以及第一个字符之前和最后一个字符之后),都可以视作可以读入0至任意多个ε。所以NFA可以在不读入输入字符的情况下,分裂自身。
NFA与DFA是等价的。对于任何一个DFA,都可以构造一个和它接受相同语言的NFA(因为DFA本身就是一个NFA,这个结论是平凡的);对于任何一个NFA,也可以构造一个和它接受相同语言的DFA。只要把NFA状态集合的幂集作为DFA的状态,然后跟踪NFA(的若干分身)对于每个输入字符可能走进的所有状态集合,DFA就走进对应这个NFA状态子集的状态。
那么既然NFA和DFA等价,为什么还要NFA呢?因为想要为某些正则语言构造自动机,NFA要比DFA容易构造。比如,想要为正则语言A和正则语言B的并构造自动机,如果是DFA的话比较困难。而构造NFA就简单多了。只要加一个新状态作为开始状态,成为Qstart,然后从Qstart伸出两条标记为ε的路径分别进入A和B的DFA的开始状态,就可以了。
利用NFA可以容易地证明:正则语言对于”并“,”链接“和”自连接“三种正则运算,是封闭的。
下面使用PHP实现的一个NFA。NFA由文件描述,比如 NFA.txt :
#这是一行注释1#:2;:6#0#02#a:3;b:5#0#03#a:5;b:4#0#04#a:5;b:5;:1#0#15#a:5;b:5#0#06#a:9;b:7#0#07#a:8;b:9#0#08#a:9;b:9;:1#0#19#a:9;b:9#0#010#:1#1#0
每一行描述一个状态。
比如第一行 1#:2;:6#0#0 描述了:
状态1 # 遇到空字符 ε 可进入状态 2 ; 遇到空字符 ε 可进入状态 6 #
这一行 8#a:9;b:9;:1#0#1 的意思是:
状态8 # 遇到字符 a 可进入状态 9 ; 遇到字符 b 可进入状态 9 ;遇到空字符 ε 可进入状态 1 #
这个NFA识别的语言是 (ab|ba)+。一个或多个ab或ba链接而成的字符串。
运行:
php nfa.php nfa.txt abababab
abababab accepted
<?php //NFA描述方式,状态行格式:状态id,输入字符与目标状态,是否起始状态,是否终止状态 //NFA识别的语言 (ab|ba)+$fileformat = <<<DFATABLE#这是一行注释1#:2;:6#0#02#a:3;b:5#0#03#a:5;b:4#0#04#a:5;b:5;:1#0#15#a:5;b:5#0#06#a:9;b:7#0#07#a:8;b:9#0#08#a:9;b:9;:1#0#19#a:9;b:9#0#010#:1#1#0DFATABLE ; class Base //实现 getXxx() setXxx() 方法当作属性用 { public function __get($prop) { return eval('return $this->get'. ucfirst($prop).'();' ); } public function __set($prop, $value) { return eval('return $this->set'. ucfirst($prop)."($value);" ); } } class State extends Base { private $_id; //int 状态id private $_isStart; // bool 该状态是否初始状态 private $_isEnd; // bool 该状态是否终止状态 private $_tf = array(); //array 转移函数: key = 输入字符(可以为空'') value 数组(元素为目标状态id) //构造“状态”时节点的id,是否起止节点就确定了,同时提供属性来访问这些信息 public function __construct($id, $isStart, $isEnd) { $this->_id = $id; $this->_isStart = $isStart; $this->_isEnd = $isEnd; } public function getId() { return $this->_id; } public function getIsStart() { return $this->_isStart; } public function getIsEnd() { return $this->_isEnd; } public function addTransfer($char, $destState) { if(isset($this->_tf[$char]) && in_array($destState, $this->_tf[$char], true)){ return; } $this->_tf[$char][] = $destState; //(如果不存在)为当前状态添加一条(指定输入字符的)“离开”路径 } public function go($char) { //存在当前状态的指定输入字符的“离开”路径,就返回它们,否则返回空数组 if(isset($this->_tf[$char]) && count($this->_tf[$char]) > 0) { return $this->_tf[$char]; } else { return array(); } } public function __toString() { $str = 'State '. $this->id; if($this->isStart) { $str .= ' start'; } if($this->isEnd) { $str .= ' end'; } if(count($this->_tf)) { $str .= "\n"; foreach($this->_tf as $char => $destArray) { $str .= " {$char}: "; foreach($destArray as $dest) { $str .= $dest . ', '; } $str = substr($str, 0, strlen($str) - 2) . "\n"; } } return $str; } } class NFA extends Base { private $_states = array(); //array 状态数组:状态节点id => 状态对象,合法的NFA本数组不能为空 private $_start = -1; //int 起始节点id,合法的NFA本值不能为-1 public function __construct($file) { try { $this->build($file); } catch(Exception $e) { echo 'build failed: '. $e->getMessage() . "\n"; $this->ruin(); } } private function isValid() { return (count($this->_states) > 0 && $this->_start > 0); //不少于一个节点,且存在起始节点 } private function build($file) { if(!file_exists($file)) { throw new Exception("{$file} does not exist"); } $fp = fopen($file, 'r'); if(!$fp) { throw new Exception("open {$file} failed"); } do { $line = trim(fgets($fp)); if(empty($line) || strpos($line, '#') === 0) { continue; //空行或者注释行 } $items = explode('#', $line); if(count($items) !== 4) { throw new Exception("{$file} format error: state error"); } list($id, $tran, $isStart, $isEnd) = $items; $id = trim($id); $tran = trim($tran); $isStart = trim($isStart); $isEnd = trim($isEnd); $tranArr = explode(';', $tran); if(count($tranArr) === 0) { throw new Exception("{$file} format error: transfer function error, no transfer for State {$id}"); } $state = new State($id, $isStart, $isEnd); foreach($tranArr as $tranPath) { $tranPath = trim($tranPath); $tmpArr = explode(':', $tranPath); if(count($tmpArr) !== 2) { throw new Exception("{$file} format error: transfer function error, wrong transfer path for State {$id}"); } list($char, $destState) = $tmpArr; $char = trim($char); $destState = trim($destState); $state->addTransfer($char, $destState); } $this->_states[$id] = $state; if($state->isStart) { //发现新的起始节点 if($this->_start > 0) { //已经有起始节点! throw new Exception("{$file} format error: more than 1 start state"); } else { $this->_start = $state->id; } } } while(!feof($fp)); } private function ruin() { //NFA置为空 $this->_states = array(); $this->_start = -1; } public function __toString() { if(!$this->isValid()) { return 'This is not a valid NFA'; } $str = ''; $str .= 'Start State: ' . $this->_start . "\n"; $str .= "States: \n"; foreach($this->_states as $state) { $str .= $state; } return $str; } public function accept($sentence) { //判断某个语句是否是NFA的合法语言 if(!$this->isValid()) { throw new Exception('this is not a valid NFA'); } $set = $this->travel($this->_start); //$set = 起点的空字符闭包,相当于起点的等价点 $len = strlen($sentence); for($i = 0; $i < $len; $i++) { $char = $sentence[$i]; //指定一个字符$char $nset = array(); //获取并记录等价点的所有$char字符的目标状态(离开路径) foreach($set as $id) { $state = $this->_states[$id]; $destSet = $state->go($char); $nset = array_unique(array_merge($nset, $destSet)); } $travelSet = array(); //获取并记录所有$char字符目标状态的等价点,并合并到目标状态集合 foreach($nset as $id) { $tSet = $this->travel($id); $travelSet = array_unique(array_merge($travelSet, $tSet)); } $nset = array_unique(array_merge($nset, $travelSet)); $set = $nset; //将目标状态集合作为新的起点,以便读取下一个字符继续遍历 } //语句中所有字符读取完毕,检查最终集合中有无终止点 foreach($set as $id) { $state = $this->_states[$id]; if($state->isEnd) { return true; } } return false; } private function travel($id) { //相当于获取状态$id的空字符闭包(等价状态) $stack = array(); $retArr = array(); //返回等价状态的id数组 array_push($stack, $id); while($id = array_pop($stack)) { if(!in_array($id, $retArr)) { $retArr[] = $id; } $state = $this->_states[$id]; $destArr = $state->go(''); foreach($destArr as $destStateId) { $destState = $this->_states[$destStateId]; if(!in_array($destState->id, $stack)) { array_push($stack, $destState->id); } } } return $retArr; } } $nfa = new NFA($argv[1]); echo $nfa. "\n"; if(isset($argv[2])) { try { if($nfa->accept($argv[2])) { echo "{$argv[2]} accepted\n"; } else { echo "{$argv[2]} denied\n"; } } catch (Exception $e) { echo "{$argv[2]} denied: " .$e->getMessage() . "\n"; } } ?>
- PHP实现的ε-NFA
- 简单的NFA转DFA的代码实现
- 有限自动机NFA-ε到NFA再到DFA的转换
- 给定正规式转化为等价的NFA代码实现
- 用java实现编译器之代码实现Thompson构造:在简单NFA的基础上构造更复杂的NFA
- 关于正则引擎ε-NFA -> NFA (仅通过边建立限制结束状态的两种尝试
- DFA 和 NFA 的区别
- NFA到DFA的转换
- NFA到DFA的转化
- 构造Half(L)的NFA
- NFA到DFA的转换
- DFA和NFA的区别
- 非确定有限状态自动机的构建(一)——NFA的定义和实现
- 放下代码,识别尾部以01结尾的0和1的串 自动机NFA实现
- 编译原理(二) NFA的确定化及DFA的最小化的算法及C++实现
- 编译原理(二) NFA的确定化及DFA的最小化的算法及C++实现
- 非确定有限状态自动机的构建-NFA的定义和实现
- 代码实现Thompson构造:由简单到复杂的构建NFA状态机
- ios 如何获取不变的UDID
- ubuntu 上安装 SDL
- andriod中如果引入jar包错误出现Conversion to Dalvik format failed with error 1
- linux环境下规定时间内进行数据库备份
- 深入浅出Java垃圾回收机制(2)——如何监控Java垃圾回收机制
- PHP实现的ε-NFA
- 第9周项目1C-利用循环求和C
- 秋天.Net 压缩支持库 Compression
- 构造函数、访问权限private
- Hduoj1266【水题】
- 关于互联网的10个知识
- C语言文件操作之fgets()
- pdf文字提取方法介绍
- 优化二部子成立以来兢兢业业的帮助大家去盘丝洞找通道