设计DFA接受可被数字'n'整除的二进制字符串


76

我需要学习如何设计DFA,以便在给定任何数字'n'的情况下,它都接受二进制字符串{0,1},其十进制等效数可以被'n'整除。

对于不同的'n',会有不同的DFA,但是有人可以给出一个基本方法,我应该遵循此基本方法来处理任何0 <n <10的数字。


因为即使n是微不足道的,对吗?
akonsu 2014年

2
请原谅我@akonsu,我不明白。
Naveen 2014年

请勿为此使用正则表达式。只需解析字符串mod n。
雷蒙德·陈

@Naveen我的意思是被偶数整除的二进制字符串n必须具有log(n)尾随零。
akonsu 2014年

@akonsu好的,素数小于10的情况如何?
Naveen

Answers:


229

下面,我写了一个n等于5的答案,但是您可以应用相同的方法来绘制DFA的任意值n和“任何位置数字系统”,例如二进制,三进制...

首先将术语“完整DFA”简化为在δ:Q×Σ→Q的完整域上定义的DFA, 称为“完整DFA”。换句话说,我们可以说;在完整DFA的转换图中,没有丢失的边(例如,对于Q中的每个状态,Σ中的每种语言符号都存在一个输出边)。注意:有时我们将部分DFA定义为δ⊆Q×Σ→Q(请参阅:如何在DFA的定义中读取“δ:Q×Σ→Q”)。

设计DFA接受可被数字'n'整除的二进制数字:

步骤1:当您将ω除以ω时,n提醒可以是0、1,...,(n-2)或(n-1)。如果是余数,则0意味着ωn不能被整除。因此,在我的DFA中,将存在一个状态q r,它对应于余数r,其中0 <= r <= (n - 1)DFA中的状态总数为n
在∑上处理了一个数字串ω之后,最终状态为q r表示ω%n => r(%提醒运算符)。

在任何自动机中,状态的目的就像存储元素。原子状态中的状态存储了一些信息,例如风扇的开关,可以判断风扇处于“关闭”状态还是“开启”状态。对于n = 5,DFA中的五个状态对应于五个提醒信息,如下所示:

  1. 状态q 0,如果提醒是0状态q达到0 是(接受状态)的最终状态。这也是一个初始状态。
  2. 如果提醒为1,则状态q 1达到非最终状态。
  3. 状态q 2(如果提醒为2)为非最终状态。
  4. 如果提醒为3,则为q 3,即为非最终状态。
  5. 如果提醒为4,则进入q 4,这是非最终状态。

使用以上信息,我们可以开始绘制五个状态的过渡图TD,如下所示:

图。1
图1

因此,有5个状态对应5个余数。在处理了一个字符串ω之后,如果最终状态变为q 0,则意味着输入字符串的十进制等效项可以被5整除。在上图中,q 0被标记为两个同心圆的最终状态。
另外,我已经定义了转换规则δ:(q 0,0)→q 0作为'0'状态q 0处的符号的自环,这是因为任何字符串的十进制等效项仅'0'由0组成,而0可被整除n

步骤2:上述TD不完整;并且只能处理'0's的字符串。现在添加更多的边,以便它可以处理后续数字的字符串。下表列出了可以在下一步中添加的新过渡规则:

──────┬──────┬─────────┐
│数字二元余数(%5)结束状态│
├──────┼──────┼─────────┼
│One│1│1│q 1        │
├──────┼──────┼─────────┼
│Two│10│2│q 2        │
├──────┼──────┼─────────┼
│Three│11│3│q 3        │
├──────┼──────┼─────────┼
│Four│100│4│q 4        │
──────┴──────┴─────────┘
  1. 要处理二进制字符串'1',应该有一个转换规则δ:(q 0,1)→q 1
  2. 二: -二进制表示'10',最终状态应该为Q 2,和过程'10',我们只需要添加更多的转换规则δ:(Q 1,0)→q 2
    :→(Q 0)─1→( q 1)─0→(q 2
  3. 三: -二进制是'11',最终状态为q 3,我们需要添加一个转换规则δ:(Q 1,1)→q 3
    路径:→(Q 0)─1→(Q 1)─1 →(q 3
  4. 四:-在二进制中'100',最终状态为q 4。TD已经处理了前缀字符串'10',我们只需要添加一个新的过渡规则δ:(Q 2,0)→q 4
    :→(Q 0)─1→(Q 1)─0→(Q 2)─0→ (问题4

图2 图2

步骤3:5 = 101
图2上的过渡图仍然不完整,并且缺少许多边,例如,对于δ:(q 2,1)- 没有定义过渡。并且应该存在规则来处理类似的字符串'101'
因为'101'= 5可被5整除,并且接受,'101'我将在上面的图2中添加δ:(q 2,1)→q 0
路径: →(q 0)─1→(q 1)─0→(q 2)─1→(q 0),
用这个新规则,转换图如下:

图3 图3

在每个步骤的下面,我选择下一个后续的二进制数字以添加缺失的边,直到获得TD作为“完整DFA”。

步骤4:六个= 110。

我们可以'11'将图3中的当前TD处理为:→(q 0)─11→(q 3)─0→()。因为6%5 = 1,表示添加一个规则δ:(Q 3,0)→q 1

图4 图4

步骤5:7 = 111

┌──────┬──────┬─────────┬──────┬ ──┬──────────┐
│数字二进制余数(%5)结束状态路径添加       │
├──────┼──────┼─────────┼──────┼ ──┼──────────┤
│Seven│111│7%5 = 2│q 2        │q 0 ─11→q 3     q 3 ─1→q 2     │
└──────┴──────┴─────────┴──────┴ ──┴──────────┘

图5 图5

步骤6:八= 1000

──────┬──────┬────────┬ ────────┐
│数字二进制余数(%5)结束状态路径添加     │
├──────┼──────┼─────────┼ ────────┤
│Eight│1000│8%5 = 3│q 3        │q 0 ─100→q 4 │q 4 ─0→q 3   │
──────┴──────┴────────┴ ────────┘

图6 图6

步骤7:九= 1001

──────┬──────┬────────┬ ────────┐
│数字二进制余数(%5)结束状态路径添加     │
├──────┼──────┼─────────┼ ────────┤
│Nine│1001│9%5 = 4│q 4        │q 0 ─100→q 4 │q 4 ─1→q 4   │
──────┴──────┴────────┴ ────────┘

图7 图7

在TD-7中,边的总数为10 == Q×Σ= 5×2。这是一个完整的DFA,可以接受所有可能的二进制字符串,这些十进制等效项可以被5整除。

设计DFA接受可被数字n整除的三进制数字:

步骤1与二进制完全相同,使用图1。

步骤2添加零,一,二

────────┬──────┬───────┬ ──────┐
│十进制三进制余数(%5)结束状态   添加        │
├────────┼────────┼──────── ──────┤
│零│0│0│q0│δ:(q0,0)→q0│
├────────┼────────┼──────── ──────┤
│一个│1│1│q1│δ:(q0,1)→q1│
├────────┼────────┼──────── ──────┤
│两个│2│2│q2│δ:(q0,2)→q3│
────────┴──────┴───────┴ ──────┘

图8
图8

步骤3加三,四,五

────────┬──────┬───────┬ ────┐
│十进制三进制余数(%5)结束状态添加        │
├────────┼────────┼──────── ────┤
│3│10│3│q3│δ:(q1,0)→q3│
├────────┼────────┼──────── ────┤
│四│11│4│q4│δ:(q1,1)→q4│
├────────┼────────┼──────── ────┤
│5│12│0│q0│δ:(q1,2)→q0│
────────┴──────┴───────┴ ────┘

图9
图9

步骤4加上六,七,八

────────┬──────┬───────┬ ────┐
│十进制三进制余数(%5)结束状态添加        │
├────────┼────────┼──────── ────┤
│六│20│1│q1│δ:(q2,0)→q1│
├────────┼────────┼──────── ────┤
│7│21│2│q2│δ:(q2,1)→q2│
├────────┼────────┼──────── ────┤
│8│22│3│q3│δ:(q2,2)→q3│
────────┴──────┴───────┴ ────┘

图10
图10

步骤5加九,十,十一

────────┬──────┬───────┬ ────┐
│十进制三进制余数(%5)结束状态添加        │
├────────┼────────┼──────── ────┤
│九│100│4│q4│δ:(q3,0)→q4│
├────────┼────────┼──────── ────┤
│十│101│0│q0│δ:(q3,1)→q0│
├────────┼────────┼──────── ────┤
│11│102│1│q1│δ:(q3,2)→q1│
────────┴──────┴───────┴ ────┘

图11
图11

步骤6加上十二,十三,十四

────────┬──────┬───────┬ ──────┐
│十进制三进制余数(%5)结束状态添加        │
├────────┼────────┼────────┼ ──────┤
│十二│110│2​​│q2│δ:(q4,0)→q2│
├────────┼────────┼────────┼ ──────┤
│十三│111│3│q3│δ:(q4,1)→q3│
├────────┼────────┼────────┼ ──────┤
│14││112│4│q4│δ:(q4,2)→q4│
└────────┴──────┴───────┴ ──────┘

图12
图12

转换图12中的边总数为15 = Q×Σ= 5 * 3(完整的DFA)。这个DFA可以接受所有{0,1,2}范围内的字符串,这些十进制等效项可以被5整除。
如果您在每一步中注意到,表中有3个条目,因为在每一步中,我都将从状态中添加所有可能的输出边制作完整的DFA(我添加了一条边,以便剩余的q r状态为r)!

此外,请记住,两种常规语言的结合也是常规的。如果您需要设计一个接受二进制字符串的DFA,则这些十进制等效项可以被3或5整除,然后绘制两个单独的DFA以被3和5整除,然后将两个DFA合并以构造目标DFA(对于1 <= n <= 10您必须合并10个DFA)。

如果要求您绘制接受二进制字符串的DFA,使得十进制等效项可被5和3整除,那么您正在寻找DFA可被15整除的东西(但是6和8呢?)。

注意:仅当数字和底数之间没有公因子时(例如,在第一个示例中存在5和2之间,在第二个示例中不存在5和3之间),使用此技术绘制的DFA才会最小化DFA ,因此,上面构造的两个DFA都被最小化DFA。如果您有兴趣进一步阅读有关数字和基础文章的可能的迷你状态:可除性和状态复杂性nnb

下面,我添加了一个Python脚本,我在学习Python库pygraphviz时很有趣。我正在添加它,希望它能以某种方式对某人有所帮助。

为基数“ b”的数字字符串设计DFA,该数字字符串可被数字“ n”整除:

因此,我们可以运用上述技巧来绘制DFA,以识别'b'可被给定数字整除的任何基数的数字字符串'n'。在这种情况下,DFA的状态总数为nn余数),边数应等于'b'*'n'-即完整的DFA:'b'= DFA语言中的符号数,而'n'=状态数。

使用上面的技巧,我在下面编写了Python脚本来绘制DFA作为输入basenumber。在脚本中,函数会逐步divided_by_N填充DFA的转换规则base * number。在每个步骤num编号中,我都num_s使用function转换为数字字符串baseN()。为了避免处理每个数字字符串,我使用了一个临时数据结构lookup_table。在每个步骤中,num_s都会评估并存储数字字符串的最终状态,lookup_table以供下一步使用。

对于DFA的过渡图,我已经draw_transition_graph使用Pygraphviz库编写了一个函数(非常易于使用)。要使用此脚本,您需要安装graphviz。为了在过渡图中添加彩色边缘,我为每个符号get_color_dict函数随机生成了颜色代码。

#!/usr/bin/env python
import pygraphviz as pgv
from pprint import pprint
from random import choice as rchoice

def baseN(n, b, syms="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
    """ converts a number `n` into base `b` string """
    return ((n == 0) and syms[0]) or (
        baseN(n//b, b, syms).lstrip(syms[0]) + syms[n % b])

def divided_by_N(number, base):
    """
    constructs DFA that accepts given `base` number strings
    those are divisible by a given `number`
    """
    ACCEPTING_STATE = START_STATE = '0'
    SYMBOL_0 = '0'
    dfa = {
        str(from_state): {
            str(symbol): 'to_state' for symbol in range(base)
        }
        for from_state in range(number)
    }
    dfa[START_STATE][SYMBOL_0] = ACCEPTING_STATE
    # `lookup_table` keeps track: 'number string' -->[dfa]--> 'end_state'
    lookup_table = { SYMBOL_0: ACCEPTING_STATE }.setdefault
    for num in range(number * base):
        end_state = str(num % number)
        num_s = baseN(num, base)
        before_end_state = lookup_table(num_s[:-1], START_STATE)
        dfa[before_end_state][num_s[-1]] = end_state
        lookup_table(num_s, end_state)
    return dfa

def symcolrhexcodes(symbols):
    """
    returns dict of color codes mapped with alphabets symbol in symbols
    """
    return {
        symbol: '#'+''.join([
            rchoice("8A6C2B590D1F4E37") for _ in "FFFFFF"
        ])
        for symbol in symbols
    }

def draw_transition_graph(dfa, filename="filename"):
    ACCEPTING_STATE = START_STATE = '0'
    colors = symcolrhexcodes(dfa[START_STATE].keys())
    # draw transition graph
    tg = pgv.AGraph(strict=False, directed=True, decorate=True)
    for from_state in dfa:
        for symbol, to_state in dfa[from_state].iteritems():
            tg.add_edge("Q%s"%from_state, "Q%s"%to_state,
                        label=symbol, color=colors[symbol],
                        fontcolor=colors[symbol])

    # add intial edge from an invisible node!
    tg.add_node('null', shape='plaintext', label='start')
    tg.add_edge('null', "Q%s"%START_STATE,)

    # make end acception state as 'doublecircle'
    tg.get_node("Q%s"%ACCEPTING_STATE).attr['shape'] = 'doublecircle'
    tg.draw(filename, prog='circo')
    tg.close()

def print_transition_table(dfa):
    print("DFA accepting number string in base '%(base)s' "
            "those are divisible by '%(number)s':" % {
                'base': len(dfa['0']),
                'number': len(dfa),})
    pprint(dfa)

if __name__ == "__main__":
    number = input ("Enter NUMBER: ")
    base = input ("Enter BASE of number system: ")
    dfa = divided_by_N(number, base)

    print_transition_table(dfa)
    draw_transition_graph(dfa)

执行它:

~/study/divide-5/script$ python script.py 
Enter NUMBER: 5
Enter BASE of number system: 4
DFA accepting number string in base '4' those are divisible by '5':
{'0': {'0': '0', '1': '1', '2': '2', '3': '3'},
 '1': {'0': '4', '1': '0', '2': '1', '3': '2'},
 '2': {'0': '3', '1': '4', '2': '0', '3': '1'},
 '3': {'0': '2', '1': '3', '2': '4', '3': '0'},
 '4': {'0': '1', '1': '2', '2': '3', '3': '4'}}
~/study/divide-5/script$ ls
script.py filename.png
~/study/divide-5/script$ display filename

输出:

base_4_divided_5_best
DFA接受以4为底的数字字符串,这些数字可以被5整除

同样,输入base = 4和number = 7生成-dfa接受以“ 4”为基数的数字字符串,这些数字可被“ 7”
Btw整除,尝试更改filename.png.jpeg

引用那些我用来编写此脚本的内容:
➊函数baseN“将整数转换为python中给定数字基的字符串”
➋要安装“ pygraphviz”:“ Python看不到pygraphviz”
➌要学习使用Pygraphviz:“ Python- FSM”
➍为每种语言符号生成随机的十六进制颜色代码:“如何使用.join和for循环制作随机的十六进制代码生成器?”



1
为了获得另一种观点,可以阅读模块化算术。
2016年

4
如何证明这个解决方案?

1
我还想知道,有人可以提供对提供证明的资源的引用吗?
Juzer Ali

@JuzerAli这是我自己的把戏,您不会在任何书中找到解释。
Grijesh Chauhan

8

我知道我来晚了,但我只想在@Grijesh提供的正确答案中添加一些内容。我只想指出@Grijesh提供的答案不会产生最小的DFA。虽然答案肯定是获得DFA的正确方法,但是如果您需要最小的DFA,则必须研究除数。

像例如二进制数一样,如果除数是2的幂(即2 ^ n),则所需的最小状态数将是n + 1。您将如何设计这样的自动机?只需查看二进制数字的属性即可。对于一个数字,例如8(即2 ^ 3),其所有倍数的最后3位将为0。例如,二进制数40为101000。因此,要使语言接受任何可以被8整除的数字,我们只需要一个自动机,它查看最后3位是否为0,我们可以仅在4个状态而不是8个状态中进行操作。这是机器复杂度的一半。

实际上,这可以扩展到任何基础。对于三进制基数系统,例如,如果我们需要设计一个自动机以除以9,我们只需要查看输入的最后2个数字是否为0。可以再次在3种状态下完成此操作。

尽管除数不是很特殊,但是我们只需要通过@Grijesh的答案即可。例如,在二进制系统中,如果我们采用3或7或21的除数,那么我们将仅需要具有这么多的状态。因此,对于二进制系统中的任何奇数n,我们需要n个状态来定义接受n的所有倍数的语言。另一方面,如果数字是偶数但不是2的幂(仅在二进制数的情况下),则需要将数字除以2,直到得到奇数,然后才能找到最小状态数。将产生的奇数与我们除以2的次数相加。

例如,如果我们需要找到DFA接受所有可以被20整除的二进制数的最小状态数,则可以执行以下操作:

20/2 = 10 
10/2 = 5

因此,我们的答案是5 + 1 + 1 = 7。(1 + 1是因为我们将数字20除以了两次)。


主席先生,在最后一个关于除数为20的例子中,如果问题要求除以20的余数K而不是除数0的余数,该怎么办?答案会改变吗?
Vinay Yadav

0

您可以使用简单的模块化算法构建DFA。我们可以w使用以下规则解释哪个是k元数的字符串

V[0] = 0
V[i] = (S[i-1] * k) + to_number(str[i])

V[|w|]w是代表的数字。如果修改此规则以查找w mod N,则规则变为此。

V[0] = 0
V[i] = ((S[i-1] * k) + to_number(str[i])) mod N

并且每个V[i]都是0到N-1之间的数字之一,它对应于DFA中的每个状态。我们可以将其用作状态转换。

看一个例子。

k = 2,N = 5

| V | (V*2 + 0) mod 5     | (V*2 + 1) mod 5     |
+---+---------------------+---------------------+
| 0 | (0*2 + 0) mod 5 = 0 | (0*2 + 1) mod 5 = 1 |
| 1 | (1*2 + 0) mod 5 = 2 | (1*2 + 1) mod 5 = 3 |
| 2 | (2*2 + 0) mod 5 = 4 | (2*2 + 1) mod 5 = 0 |
| 3 | (3*2 + 0) mod 5 = 1 | (3*2 + 1) mod 5 = 2 |
| 4 | (4*2 + 0) mod 5 = 3 | (4*2 + 1) mod 5 = 4 |

k = 3,N = 5

| V | 0 | 1 | 2 |
+---+---+---+---+
| 0 | 0 | 1 | 2 |
| 1 | 3 | 4 | 0 |
| 2 | 1 | 2 | 3 |
| 3 | 4 | 0 | 1 |
| 4 | 2 | 3 | 4 |

现在您可以看到一个非常简单的模式。您实际上可以构建DFA转换,只需从左到右,从上到下,从0到N-1编写重复数字。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.