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";        }    }    ?>
0 0