DFA 最小化
Posted
技术标签:
【中文标题】DFA 最小化【英文标题】:DFA minimization 【发布时间】:2012-06-23 08:13:55 【问题描述】:我有一个关于 的问题。因此,我使用了众所周知的技术将正则表达式转换为 NFA,然后使用 goto / 闭包算法从中构造 DFA。现在的问题是如何最小化它?我在这里看过有关它的演讲:http://www.youtube.com/watch?v=T9Z66NF5YRk,但我仍然无法理解这一点。什么是 ?这只是合并相同的状态(在相同字符上进入相同状态的状态)还是不同的东西?
所以,我从以下语法开始:
%digit = '0'..'9'
%letter = 'a'..'z' | 'A'..'Z'
%exponent = ("e" | "E") ("+" | "-")? digit+
T_INT = digit+
T_FLOAT = T_INT exponent
T_IDENTIFIER = (letter | "$" | "_") (letter | "$" | "_" | digit)*
最终得到以下 DFA(表示为 JSON):
"START": [
"type": "range",
"from": 36,
"to": 36,
"shift": "1"
,
"type": "range",
"from": 48,
"to": 57,
"shift": "2"
,
"type": "range",
"from": 65,
"to": 90,
"shift": "1"
,
"type": "range",
"from": 95,
"to": 95,
"shift": "1"
,
"type": "range",
"from": 97,
"to": 122,
"shift": "1"
],
"1": [
"type": "range",
"from": 36,
"to": 36,
"shift": "1"
,
"type": "range",
"from": 48,
"to": 57,
"shift": "1"
,
"type": "range",
"from": 65,
"to": 90,
"shift": "1"
,
"type": "range",
"from": 95,
"to": 95,
"shift": "1"
,
"type": "range",
"from": 97,
"to": 122,
"shift": "1"
,
"shift": ["t_identifier"]
],
"2": [
"type": "range",
"from": 48,
"to": 57,
"shift": "2"
,
"type": "range",
"from": 69,
"to": 69,
"shift": "3"
,
"type": "range",
"from": 101,
"to": 101,
"shift": "3"
,
"shift": ["t_int"]
],
"3": [
"type": "range",
"from": 43,
"to": 43,
"shift": "5"
,
"type": "range",
"from": 45,
"to": 45,
"shift": "5"
,
"type": "range",
"from": 48,
"to": 57,
"shift": "4"
],
"4": [
"type": "range",
"from": 48,
"to": 57,
"shift": "4"
,
"shift": ["t_float"]
],
"5": [
"type": "range",
"from": 48,
"to": 57,
"shift": "4"
]
那么如何最小化呢?
更新:
好的,这是我的算法。给定以下 DFA:
0: [
from: 97,
to: 97,
shift: 1
],
1: [
from: 97,
to: 97,
shift: 3
,
from: 98,
to: 98,
shift: 2
],
2: [
from: 98,
to: 98,
shift: 4
],
3: [
from: 97,
to: 97,
shift: 3
,
from: 98,
to: 98,
shift: 4
],
4: [
from: 98,
to: 98,
shift: 4
]
我这样做是为了尽量减少它:
对于每个状态(在我的示例中编号为 0、1、2、3、4),获取标识此类状态的唯一哈希(例如,对于 state0,这将是:from=97,to=97,shift =1,对于 state1,这将是:from=97,to=97,shift=3&from=98,to=98,shift=2 等等……)
比较获得的哈希值,如果我们找到两个相同的哈希值,则将其合并。在我的例子中,state2 hash 为:from=98&shift=4&to=98,state4 hash 为:from=98&shift=4&to=98,它们是相同的,所以我们可以将它们合并到新的 state5 中,之后 DFA 将看起来像这样:
0: [
from: 97,
to: 97,
shift: 1
],
1: [
from: 97,
to: 97,
shift: 3
,
from: 98,
to: 98,
shift: 5
],
3: [
from: 97,
to: 97,
shift: 3
,
from: 98,
to: 98,
shift: 5
],
5: [
from: 98,
to: 98,
shift: 5
]
继续这个,直到我们没有任何变化,所以下一步(合并状态 1 和 3)会将 DFA 转换为以下形式:
0: [
from: 97,
to: 97,
shift: 6
],
6: [
from: 97,
to: 97,
shift: 6
,
from: 98,
to: 98,
shift: 5
],
5: [
from: 98,
to: 98,
shift: 5
]
没有更多相同的状态,这意味着我们已经完成了。
第二次更新:
好的,给定以下正则表达式:'a' ('ce')* ('d' | 'xa' | 'AFe')+ | 'b' ('ce')* ('d' | 'xa' | 'AFe')+ 'ce'+ 我有以下 DFA (START -> start state, ["accept"] -> so to说转换到接受状态):
"START": [
"type": "range",
"from": 98,
"to": 98,
"shift": "1.2"
,
"type": "range",
"from": 97,
"to": 97,
"shift": "17.18"
],
"1.2": [
"type": "range",
"from": 120,
"to": 120,
"shift": "10"
,
"type": "range",
"from": 100,
"to": 100,
"shift": "6.7"
,
"type": "range",
"from": 65,
"to": 65,
"shift": "8"
,
"type": "range",
"from": 99,
"to": 99,
"shift": "4"
],
"10": [
"type": "range",
"from": 97,
"to": 97,
"shift": "6.7"
],
"6.7": [
"type": "range",
"from": 99,
"to": 99,
"shift": "15"
,
"type": "range",
"from": 120,
"to": 120,
"shift": "13"
,
"type": "range",
"from": 100,
"to": 100,
"shift": "6.7"
,
"type": "range",
"from": 65,
"to": 65,
"shift": "11"
],
"15": [
"type": "range",
"from": 101,
"to": 101,
"shift": "14.accept"
],
"14.accept": [
"type": "range",
"from": 99,
"to": 99,
"shift": "16"
,
"shift": ["accept"]
],
"16": [
"type": "range",
"from": 101,
"to": 101,
"shift": "14.accept"
],
"13": [
"type": "range",
"from": 97,
"to": 97,
"shift": "6.7"
],
"11": [
"type": "range",
"from": 70,
"to": 70,
"shift": "12"
],
"12": [
"type": "range",
"from": 101,
"to": 101,
"shift": "6.7"
],
"8": [
"type": "range",
"from": 70,
"to": 70,
"shift": "9"
],
"9": [
"type": "range",
"from": 101,
"to": 101,
"shift": "6.7"
],
"4": [
"type": "range",
"from": 101,
"to": 101,
"shift": "2.3"
],
"2.3": [
"type": "range",
"from": 120,
"to": 120,
"shift": "10"
,
"type": "range",
"from": 100,
"to": 100,
"shift": "6.7"
,
"type": "range",
"from": 65,
"to": 65,
"shift": "8"
,
"type": "range",
"from": 99,
"to": 99,
"shift": "5"
],
"5": [
"type": "range",
"from": 101,
"to": 101,
"shift": "2.3"
],
"17.18": [
"type": "range",
"from": 120,
"to": 120,
"shift": "25"
,
"type": "range",
"from": 100,
"to": 100,
"shift": "22.accept"
,
"type": "range",
"from": 65,
"to": 65,
"shift": "23"
,
"type": "range",
"from": 99,
"to": 99,
"shift": "20"
],
"25": [
"type": "range",
"from": 97,
"to": 97,
"shift": "22.accept"
],
"22.accept": [
"type": "range",
"from": 120,
"to": 120,
"shift": "28"
,
"type": "range",
"from": 100,
"to": 100,
"shift": "22.accept"
,
"type": "range",
"from": 65,
"to": 65,
"shift": "26"
,
"shift": ["accept"]
],
"28": [
"type": "range",
"from": 97,
"to": 97,
"shift": "22.accept"
],
"26": [
"type": "range",
"from": 70,
"to": 70,
"shift": "27"
],
"27": [
"type": "range",
"from": 101,
"to": 101,
"shift": "22.accept"
],
"23": [
"type": "range",
"from": 70,
"to": 70,
"shift": "24"
],
"24": [
"type": "range",
"from": 101,
"to": 101,
"shift": "22.accept"
],
"20": [
"type": "range",
"from": 101,
"to": 101,
"shift": "18.19"
],
"18.19": [
"type": "range",
"from": 120,
"to": 120,
"shift": "25"
,
"type": "range",
"from": 100,
"to": 100,
"shift": "22.accept"
,
"type": "range",
"from": 65,
"to": 65,
"shift": "23"
,
"type": "range",
"from": 99,
"to": 99,
"shift": "21"
],
"21": [
"type": "range",
"from": 101,
"to": 101,
"shift": "18.19"
]
故事是一样的,我如何最小化它?如果我在所有这些表结构中遵循经典的 Hopcroft 算法,确定不可区分的状态,将它们合并在一起等等,那么我将获得包含 15 个状态的 DFA(使用这个工具:http://regexvisualizer.apphb.com/ 和这个正则表达式 a(ce )(d|xa|AFe)+|b(ce)(d|xa|AFe)+ce+ 检查)。以下是使用 Hopcroft 算法缩小后 DFA 的样子:
我提出的算法,在“重新思考”Hopcroft 的算法之后,构建的 DFA 比您在上面看到的要小(对不起图像质量,我不得不一步一步地重新绘制它以了解为什么它更小):
这就是它的工作原理,关于“状态等价”的决定是基于给定状态(例如“START”)的哈希函数的结果,如果我们开始构建可以从 DFA 构造的短字符串从那个状态。给定上面的 DFA 和 START 状态,我们可以构造以下字符串:98->120, 98->100, 98->65, 98->99, 97->120, 97->100, 97->65 , 97->99 所以让它成为 START 状态的哈希函数的结果。如果我们为 DFA 中的每个状态运行此函数,我们将看到对于某些状态,此函数为我们提供相同的结果(“1.2”、“6.7”、“2.3”和“10”、“13”和“15” , "16" AND "11", "8", "26", "23" AND "12", "9", "4", "5", "20", "21" AND "17.18", " 18.19" AND "25", "28" AND "27", "24") 所以我们需要做的就是将这些状态合并在一起。
我发现我在某个地方错了,但不明白我的算法生成的最小化 DFA 有什么问题?
【问题讨论】:
这里也有讨论:binarysculpting.com/2012/03/21/dfa-state-minimization 所以我希望最后能从某个地方得到答案... 谢谢大家,我试图找到更优雅的解决方案来最小化 DFA 的尝试没有成功,所以我终于实现了经典的 Hopcroft,我很满意 :) 【参考方案1】:您提出的算法没有进行完全最小化,因为它没有检测到行为相同的复杂结构。要理解这一点,请查看此 DFA(由 JFLAP 绘制):
最小化将结合 q1 和 q2,但概述的算法无法做到。
与此相反,Hopcroft 的算法最初会这样划分:
q0, q1, q2, q3
然后拆分第一组,因为q0没有到q3的过渡:
q0, q1, q2, q3
不要进一步拆分,因为 q1 和 q2 的行为相同。
【讨论】:
嗨,Gunther,谢谢你的提示,你说得对,我的原始算法无法处理 q1 和 q2 之间的合并,但如果我稍作修改,它就可以处理:当获得状态的散列,我需要检查字符是否进入我们找到它的相同状态。所以 q1 的散列是:from=99,to=99,shift=itself,q2 的散列是:from=99,to=99,shift=itself。然后它也适用于这个 DFA :) 你同意吗?你能想出一些使用这种修改后的算法不会被缩小的 DFA 吗? 不,抱歉,事情没有那么简单。想象一下任意复杂但重复的子自动机,而不是“c”弧。 我已经试过了,它在复杂结构上给了我最小的 DFA:(('a'|'b')+('a'|'b') 'c'' 3'+ '0'..'9' ('a'..'z' | 'A'..'Z')+ '3'+ 'd' 'f')+ ((' a'|'b') 'c'*'3'+ '0'..'9' ('a'..'z' | 'A'..'Z')+)? 'f' 但我仍然得到最小的 DFA(针对位于此处的 Hopcroft 的最小化可视化工具进行测试:regexvisualizer.apphb.com)我的算法给出的状态数量与该工具生成的最小化 DFA 中的状态数量相同。 ..问题仍然是我如何证明我的算法与 Hopcroft 的算法相同,即。 e.产生最小的 DFA? 我上面展示的 DFA 是针对ac*d|bc*d
的,但是 a(ce)*d|b(ce)*d
呢?在这种情况下,不会有“shift=itself”转换。
是的,你说得对 :) 这意味着它不像比较哈希那么简单。我想我们可以关闭这个。感谢大家!我会继续我的研究。【参考方案2】:
将您的原始 DFA 称为 M1。 简单来说,构造一个最小化的 DFA(称为 M2)意味着将其转换为包含最少状态数的 DFA。因此,M2 中的状态数将少于 M1 中的状态数。这里需要注意的重要一点是 M1 和 M2 必须是等价的,这意味着它们必须定义相同的正则语言。构建一个最小化的 DFA 不仅涉及寻找相同的状态,还涉及以下内容:
删除“无法访问”状态: 这涉及从 DFA 的初始状态中删除任何输入字符串无法到达的状态。
删除“死亡”或“陷阱”状态: 这涉及删除以自身终止的非接受状态。它们也被称为 TRAP 状态。
删除“不可区分”状态: 这涉及删除对于任何输入字符串无法相互区分的状态。
还有一些流行的算法用于 DFA 最小化:
Moore's algorithm
Brzozowski's algorithm
Hopcroft's algorithm
这些算法可能值得一试!
【讨论】:
谢谢,故事是我想出了一些东西,可以将我的 DFA 最小化到某个状态,据我所知,它为我所拥有的输入生成了最小的 DFA,问题是:它会产生最小 DFA?或者它只是一堆代码,做了一些最小化,但结果不是最小可能的 DFA...请参阅我对原始问题的更新,它描述了我的算法。【参考方案3】:鉴于您有将 NFA 确定为 DFA 的代码,将其最小化的最简单解决方案是 Brzozowski 算法。 您将需要实现一个函数来反转 NFA,但这相当简单。 (反转转换,交换开始和接受状态)
一旦你有一个确定和反向函数,Brzozowski 最小化被实现为:
minimize(nfa) = determinize(reverse(determinize(reverse(nfa))))
恕我直言,这是一个非常优雅的解决方案
【讨论】:
以上是关于DFA 最小化的主要内容,如果未能解决你的问题,请参考以下文章