语言是这样的:
a 和 b 的所有字符串,至少以一个 b 开头,并且在任何 a 之后至少有一个 b。
每当您尝试理解一种语言时,我建议您考虑该语言中最短的字符串以及如何构建更长的字符串。上述语言中最短的字符串是 b。如果我们在语言中有一个字符串,我们怎样才能得到更长的字符串?好吧,我们总是可以添加更多的 b 来获得语言中的字符串。如果我们添加一个 a,我们必须至少添加一个 b。这表明了以下形式的正则表达式:
bb* means one b, then any number of additional b's
^ this Kleene star lets us add any number of a's, or none at all
| ^
| |
bb* (abb*)*
|-| |-----|
| |
| V
V after any a, we must have at least one b
the string must start with at least one b
要获得语法,我们可以使用对最短字符串的观察和推导更长字符串的步骤来编写递归定义:
- b 在 L 中(最短的字符串是基本情况)
- 如果 x 在 L 中,xb 在 L 中(我们总是可以添加 b)
- 如果 x 在 L 中,xab 在 L 中(如果我们在后面加上 b,我们可以添加一个 a)
- L 中没有其他内容(递归定义的样板)
要从递归定义转为语法,请添加一个产生式以从开始符号派生基本情况:
S -> b
S,根据定义,派生出我们语言中的字符串。特别是,从上面的其他两个产生式规则中,我们知道我们可以在 S 派生的任何内容的末尾添加一个 b 或同时添加一个 a 和 a b,因此我们添加了产生式
S -> Sb
S -> Sab
综合起来,我们得到:
S -> b
S -> Sb
S -> Sab
获取语法的另一种可能更好的方法是找到一个自动机,它可以为您提供您的语言,然后使用构造来获取语法。我们可能会识别出这种语言是常规语言并为它写下 DFA:
_ _
/ \ / \
b | | a,b | |
\ V \ V
----->q0----->q1----->q2
^ a | a
| |
\______/
b
这里,q0 是初始且唯一的接受状态,q1 是您看到 a 但不是必需的 b 的状态,q2 是看到两个后达到的死状态as 连续。要写下语法,请按如下方式写下转换:
(q0) -> b(q0)
(q0) -> a(q1)
(q1) -> b(q0)
(q1) -> a(q2)
(q2) -> b(q2)
(q2) -> a(q2)
最后,为每个表示接受状态的非终结符添加空/epsilon/lambda 产生式:
(q0) -> e
把它放在一起,你就有了一个语法:
(q0) -> b(q0)
(q0) -> a(q1)
(q1) -> b(q0)
(q1) -> a(q2)
(q2) -> b(q2)
(q2) -> a(q2)
(q0) -> e
您可以从此和空状态中删除空/epsilon/lambda 转换,以将其放入 Chomsky 范式(或任何接近它的东西,我忘记了终端和非终端需要的顺序):
(q0) -> a(q1)
(q0) -> b(q0) | b
(q1) -> b(q0) | b
这与我们之前的语法相同,只是它添加了一个额外的非终结符,其唯一职责是将所需的 b 放在 a 之后,而另一种语法则在一个产生式中完成。