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

Posted

技术标签:

【中文标题】设计 DFA 接受可被数字“n”整除的二进制字符串【英文标题】:Design DFA accepting binary strings divisible by a number 'n' 【发布时间】:2014-03-20 18:58:53 【问题描述】:

我需要学习如何设计一个 DFA,使得给定任何数字“n”,它接受二进制字符串 0, 1,其十进制等效数字可以被“n”整除。

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

【问题讨论】:

即使n 也是微不足道的,对吧? 请原谅@akonsu,我不明白。 不要为此使用正则表达式。只需解析字符串 mod n. @Naveen 我的意思是可以被偶数 n 整除的二进制字符串必须有 log(n) 尾随零。 @akonsu 好吧,那么小于 10 的素数呢。 【参考方案1】:

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

首先精益术语“完全 DFA”,δ:Q × Σ→Q 中的 DFA defined on complete domain 称为“完全 DFA”。换句话说,我们可以说;在完整 DFA 的转移图中,没有缺失边(例如,对于 Q 中的每个状态,对于 Σ 中的每个语言符号都有一个出边)。注意:有时我们将partial DFA 定义为 δ ⊆ Q × Σ→Q(阅读:How does “δ:Q × Σ→Q” read in the definition of a DFA)。

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

第 1 步:当您将数字 ω 除以 n 时,提醒可以是 0、1、...、(n - 2) 或 (n - 1)。如果余数是0,则意味着 ω 可以被n 整除,否则不能。因此,在我的 DFA 中会有一个状态 qr 对应于余数 r,其中 0 <= r <= (n - 1),并且 DFA 中的状态总数为 n。 在 Σ 上处理一个数字串 ω 后,结束状态是 qr 意味着 ω % n => r(%提醒算子)。

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

    如果提醒为 0,则达到状态 q0。状态 q0 是最终状态(接受状态)。它也是一个初始状态。 状态 q1 如果提醒为 1,则达到非最终状态。 State q2 如果提醒为 2,则为非最终状态。 State q3 如果提醒为 3,则为非最终状态。 State q4 如果提醒为 4,则为非最终状态。

利用以上信息,我们可以开始绘制五种状态的转移图TD如下:

图-1

因此,5 个状态对应 5 个剩余值。处理完一个字符串 ω 后,如果结束状态变为 q0,这意味着输入字符串的十进制等效值可以被 5 整除。在上图中,q0 被标记为两个同心的最终状态圈子。 此外,我定义了一个转换规则 δ:(q0, 0)→q0 作为符号 '0' 在状态 q0,这是因为任何仅包含 '0' 的字符串的十进制等效项是 0,而 0 可以被 n 整除。

Step-2:上面的TD不完整;并且只能处理'0's 的字符串。现在添加更多边,以便它可以处理后续数字的字符串。检查下表,显示了可以在下一步添加的新转换规则:

┌──────┬──────┬──────────────┬──────────┐ │数字二进制余数(%5)结束状态│ ├──────┼──────┼──────────────┼──────────┤ │一个 │1 │1 │q1 │ ├──────┼──────┼──────────────┼──────────┤ │二 │10 │2 │q2 │ ├──────┼──────┼──────────────┼──────────┤ │三 │11 │3 │q3 │ ├──────┼──────┼──────────────┼──────────┤ │四 │100 │4 │q4 │ └──────┴──────┴──────────────┴──────────┘
    处理二进制字符串'1'应该有一个转换规则δ:(q0, 1)→q1 二:-二进制表示是'10',结束状态应该是q2,而要处理'10',我们只需要再增加一个转移规则δ:(q1, 0)→q2路径:→(q0)─1→(q1)─0→(q2) 三:-二进制是'11',结束状态是q3,我们需要添加一个转移规则δ:(q1, 1 )→q3路径:→(q0)─1→(q1)─ 1→(q3) 四:- 在二进制 '100' 中,结束状态是 q4。 TD 已经处理了前缀字符串'10',我们只需要添加一个新的转换规则 δ:(q2, 0)→q4Path :→(q0)─1→(q1)─0→(q2)─0→(q 4)

图-2

第 3 步:五 = 101 上面图 2 中的转换图仍然不完整,还有很多缺失的边,例如 δ:(q2, 1)-? 没有定义转换。并且应该存在规则来处理像'101'这样的字符串。 因为'101' = 5 可以被5整除,为了接受'101',我将在上图2中添加δ:(q2, 1)→q0 .路径: →(q0)─1→(q1)─0→(q2 sub>)─1→(q0) 有了这个新规则,转换图变成如下:

图-3

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

第 4 步:六 = 110。

我们可以将图3中当前TD中的'11'处理为:→(q0)─11→(q3)─0→()。因为 6 % 5 = 1 这意味着添加一个规则 δ:(q3, 0)→q1

图4

第 5 步:七 = 111

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

图-5

第 6 步:八 = 1000

┌──────┬──────┬──────────────┬─────────┬──────────┬ ──────────┐ │数字二进制余数(%5)结束状态路径添加 │ ├──────┼──────┼──────────────┼──────────┼──────────┼ ──────────┤ │八 │1000 │8 % 5 = 3 │q3 │q0─100→q4 │ q4 sub>─0→q3 │ └──────┴──────┴──────────────┴──────────┴──────────┴ ──────────┘

图-6

第 7 步:九 = 1001

┌──────┬──────┬──────────────┬─────────┬──────────┬ ──────────┐ │数字二进制余数(%5)结束状态路径添加 │ ├──────┼──────┼──────────────┼──────────┼──────────┼ ──────────┤ │九 │1001 │9 % 5 = 4 │q4 │q0─100→q4 │ q4 sub>─1→q4 │ └──────┴──────┴──────────────┴──────────┴──────────┴ ──────────┘

图7

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

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

Step-1 与二进制完全相同,使用图 1。

第二步加零、一、二

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

图 8

第三步添加三、四、五

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

图-9

第四步添加六、七、八

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

图-10

第五步加九、十、十一

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

图-11

第六步添加十二、十三、十四

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

图-12

转移图图 12 中的总边数为 15 = Q × Σ = 5 * 3(一个完整的 DFA)。这个 DFA 可以接受所有包含超过 0, 1, 2 的字符串,这些十进制等价物可以被 5 整除。 如果您在每个步骤中注意到,表中有三个条目,因为在每个步骤中,我从一个状态添加所有可能的传出边以形成完整的 DFA(并且我添加了一条边,以便 qr 状态剩下的是r)!

要进一步补充,请记住两种正则语言的并集也是正则。如果您需要设计一个接受二进制字符串的 DFA,那些十进制等价物可以被 3 或 5 整除,然后绘制两个单独的 DFA 以可被 3 和 5 整除,然后合并两个 DFA 以构造目标 DFA(对于 1

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

注意:只有当数字 n 和基数之间存在 no 公因子时,使用这种技术绘制的 DFA 才会最小化 DFA,例如第一个例子中 5 和 2 之间没有 no,第二个例子中 5 和 3 之间没有 no,因此上面构造的两个 DFA 都是最小化的 DFA。如果您有兴趣进一步了解编号 n 和基数 b 的可能迷你状态,请阅读论文:Divisibility and State Complexity。

下面我添加了一个 Python 脚本,我是在学习 Python 库 pygraphviz 的时候写的。我正在添加它,希望它对某人有所帮助。

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

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

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

对于DFA的转换图,我写了一个函数draw_transition_graph使用Pygraphviz library(非常好用)。要使用此脚本,您需要安装 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

输出:

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

同样,输入 base = 4 和 number = 7 生成 - dfa accepting number string in base '4' those are divisible by '7' 顺便说一句,尝试将filename 更改为.png.jpeg

参考我用来编写这个脚本的那些: ➊ 功能 baseN 来自 "convert integer to a string in a given numeric base in python" ➋ 安装“pygraphviz”:"Python does not see pygraphviz" ➌ 学习 Pygraphviz 的使用:"Python-FSM" ➍ 为每种语言符号生成随机十六进制颜色代码:"How would I make a random hexdigit code generator using .join and for loops?"

【讨论】:

Checking if a number is divisible by 9 中引用了here。 要获得另一种视角,可以阅读模算术。 如何证明这个解? 我也想知道,谁能提供提供证据的资源参考? @JuzerAli 这是我自己的伎俩,你不会在任何书中找到解释。【参考方案2】:

我知道我来晚了,但我只是想在@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 的次数相加。

例如,如果我们需要找到接受所有可被 20 整除的二进制数的 DFA 的最小状态数,我们会这样做:

20/2 = 10 
10/2 = 5

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

【讨论】:

先生,在最后一个关于可被 20 整除的示例中,如果问题要求在除以 20 时得到余数 K 而不是余数 0,该怎么办?答案会改变吗?【参考方案3】:

您可以使用简单的模运算来构建 DFA。 我们可以使用以下规则解释w,它是一串k-ary数字

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 写入重复的数字。

【讨论】:

以上是关于设计 DFA 接受可被数字“n”整除的二进制字符串的主要内容,如果未能解决你的问题,请参考以下文章

数字总和可被 K 整除的子数组的数量

可被 5 整除的二进制前缀

1018.可被 5 整除的二进制前缀

如何在python中计算多种情况(C)在给定范围(A到B)中可被N整除的数字

Leetcode 1029. 可被 5 整除的二进制前缀

LeetCode.1018-可被5整除的二进制数(Binary Prefix Divisible By 5)