像讲故事一样:NFA 怎么变成 DFA
在编译原理里,非确定有限自动机(NFA)和确定有限自动机(DFA)是两个好朋友。NFA 像个随性的向导,可以同时走多条路,而 DFA 像个严格的老师,每步只许走一条路。为了让 NFA 的"随性"变成 DFA 的"规矩",我们得学会一个"整理魔法"。别担心,这个过程没那么复杂,我们用一个简单的故事和例子,手把手带你看明白!
一、故事开场:为什么要变?
想象你在森林里跟着一个向导(NFA)找宝藏。他看到一条岔路说:"可以走左边,也可以走右边,甚至还能跳过去试试。"你得派好几个分身去试每条路,看哪个能到宝藏。这很灵活,但太乱了。如果能把这些乱七八糟的路线整理成一张简单地图(DFA),上面写着"看到这个就去那儿",找宝藏就轻松多了。
在计算机里,NFA 是从正则表达式来的,很容易做出来,但它"分身太多"不好用。DFA 每步只有一条路,电脑喜欢,所以我们要把 NFA 变成 DFA。
二、主角登场:一个简单的 NFA
我们用一个简单的例子:识别"以 a
开头,后面跟任意多个 b
"(正则表达式 ab*
)。它的 NFA 长这样:
- 有三个点:起点
q0
,中间点q1
,宝藏点q2
。 - 路线:
- 从
q0
看到a
可以去q1
。 - 从
q1
看到b
可以回到q1
(循环)。 - 从
q1
啥也不看(空跳)也能到q2
。
- 从
- 起点是
q0
,宝藏在q2
。
这个 NFA 很随性:读到 b
时可以循环,可以跳,挺自由的。
三、整理思路:从乱到整齐
要把 NFA 变成 DFA,我们得解决它的"随性"。NFA 的问题是:一个点读一个字母,可能去好几个地方,还能空跳。我们要做的,就是把所有"可能去的地方"打包成一个大点,让每步只有一条路。
比喻:
- NFA 像个向导说:"读到
a
,你可以去这儿那儿。" - DFA 像个老师说:"读到
a
,你就去这个大包裹,里面装了所有可能的地方。"
这个"打包"的过程,就是把 NFA 的多个可能性合起来,变成 DFA 的一个状态。
四、动手整理:一步步来
我们拿 ab*
的 NFA 来试试,假装自己在画新地图。
第一步:从起点开始
- NFA 起点是
q0
。问:"从q0
能空跳到哪儿?" 答案是没有空跳,所以 DFA 的起点就是"装着q0
的包裹",记作{q0}
。
第二步:读字母,看去哪儿
- 从
{q0}
读a
:- NFA 说:
q0 --a--> q1
。 - 但
q1
能空跳到q2
(q1 --ε--> q2
)。 - 所以,把所有能到的地方打包:
{q1, q2}
。 - 新地图写:
{q0} --a--> {q1, q2}
。
- NFA 说:
- 从
{q0}
读b
:- NFA 说:
q0
没路。 - 所以没新地方,暂时不管。
- NFA 说:
第三步:从新包裹继续
- 现在有了
{q1, q2}
,从它读b
:q1 --b--> q1
,q2
没路。q1
还能空跳到q2
。- 打包:还是
{q1, q2}
。 - 新地图写:
{q1, q2} --b--> {q1, q2}
(自己到自己)。
- 从
{q1, q2}
读a
:q1
和q2
都没路。- 所以没新地方。
第四步:定下宝藏
- NFA 的宝藏在
q2
。 - DFA 只要包裹里有
q2
,就是宝藏点。 {q1, q2}
有q2
,所以它是宝藏点。
新地图(DFA):
- 两个点:
{q0}
和{q1, q2}
。 - 路线:
{q0} --a--> {q1, q2}
。{q1, q2} --b--> {q1, q2}
。
- 起点:
{q0}
。 - 宝藏:
{q1, q2}
。
五、试试看:新地图好用吗?
- 输入
a
:从{q0} --a--> {q1, q2}
,到宝藏,成功! - 输入
ab
:{q0} --a--> {q1, q2} --b--> {q1, q2}
,还是宝藏,成功! - 输入
b
:从{q0}
没路,失败。
跟 NFA 一样能认出 ab*
的东西,但每步只有一条路,简单多了!
六、整理的秘密:打包和空跳
这个"魔法"的关键是两点:
- 打包:把 NFA 所有可能的状态装进一个包裹,变成 DFA 的一个点。
- 空跳 :每次算新包裹时,别忘了 NFA 的空跳(像
q1 --ε--> q2
),得把能跳到的地方也装进去。
这个过程就像把向导的口头建议整理成一张表格,告诉你在哪儿读到什么就去哪儿。
七、为什么这么做?
在编译器里,NFA 是从正则表达式来的,但它太"随性",电脑跑起来得试所有分身,太慢。DFA 每步只有一条路,电脑一看就知道下一步,很快。所以我们用这个方法,把 NFA 变成 DFA,让词法分析又快又准。
八、总结:从故事到领悟
NFA 像个随性的向导,DFA 像个严格的老师。把 NFA 变成 DFA,就是把所有"可能"打包成"一定",从乱七八糟的路线变成一张简单地图。用 ab*
的例子,我们一步步整理,看到它怎么从三个点变成两个包裹,每步都清清楚楚。
下次再看到代码被编译器读懂时,想想这个小故事:从随性到规矩的魔法,就藏在里面!