Розробити DFA, приймаючи двійкові рядки, що діляться на число 'n'


76

Мені потрібно навчитися розробляти DFA таким чином, що, даючи будь-яке число 'n', воно приймає двійкові рядки {0, 1}, десятковий еквівалентний номер ділиться на 'n'.

Будуть різні DFA для різних 'n', але чи може хтось запропонувати базовий підхід, якого я повинен дотримуватися, щоб продовжувати з будь-яким числом 0 <n <10.


адже навіть nце тривіально, так?
akonsu

2
Вибачте @akonsu, я цього не розумів.
Naveen

Не використовуйте для цього регулярні вирази. Просто проаналізуйте рядок mod n.
Raymond Chen

@Naveen Я маю на увазі двійковий рядок, що ділиться на парний, nповинен мати log(n)кінцеві нулі.
akonsu

@akonsu добре, а як щодо простих чисел менше 10.
Naveen

Відповіді:


229

Нижче я написав відповідь nрівним 5, але ви можете застосувати той самий підхід, щоб намалювати DFA для будь-якого значення nта "будь-якої позиційної системи числення", наприклад, двійковий, трійковий ...

Спочатку схильний термін "Повна DFA", DFA, визначена на повному домені в δ: Q × Σ → Q, називається "Повна DFA". Іншими словами, ми можемо сказати; у діаграмі переходу повної DFA відсутній край (наприклад, з кожного стану в Q присутній по одному вихідному краю для кожного символу мови в Σ). Примітка: Часом ми визначаємо частковий DFA як δ ⊆ Q × Σ → Q (Читати: Як читається “δ: Q × Σ → Q” у визначенні DFA ).

Дизайн 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. Стан q 1 досягає, якщо нагадування дорівнює 1, не остаточний стан.
  3. Укажіть q 2, якщо нагадування дорівнює 2, не остаточний стан.
  4. Укажіть q 3, якщо нагадування 3, не остаточний стан.
  5. Укажіть q 4, якщо нагадування дорівнює 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)Кінцевий стан │
├──────┼───────────────────┼─────────┤
│Один │1 │1 │q 1        │
├──────┼───────────────────┼─────────┤
│дві │10 │2 │q 2        │
├──────┼───────────────────┼─────────┤
│Три │11 │3 │q 3        │
├──────┼───────────────────┼─────────┤
│Четверо │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 → (q 4 )

рис-2 Малюнок-2

Крок-3 : П'ять = 101
Наведена вище схема переходів на малюнку-2 все ще неповна і є багато відсутніх країв, наприклад, для δ не визначено переходу: (q 2 , 1) - ? . І правило має бути присутнім для обробки рядків типу '101'.
Оскільки '101'= 5 ділиться на 5, і для прийняття '101'я додаю δ: (q 2 , 1) → q 0 на малюнку-2.
Шлях: → (q 0 ) ─1 → (q 1 ) ─0 → (q 2 ) ─1 → (q 0 )
з цим новим правилом, діаграма переходів стає такою:

рис-3 Малюнок-3

Нижче на кожному кроці я вибираю наступне наступне двійкове число, щоб додати відсутні ребра, поки не отримаю TD як "повний DFA".

Крок-4 : Шість = 110.

Ми можемо обробити '11'в поточному TD на малюнку-3 як: → (q 0 ) ─11 → (q 3 ) ─0 → ( ? ). Оскільки 6% 5 = 1, це означає додати одне правило δ: (q 3 , 0) → q 1 .

рис-4 Малюнок-4

Крок 5 : Сім = 111

┌──────┬───────────────────┬───────────────────── ─┬───────────┐
│ НомерДвійковийЗалишок (% 5)Кінцевий станШляхДодати        │
├──────┼───────────────────┼───────────────────── ─┼───────────┤
│Сім │111 │7% 5 = 2 │q 2        │ q 0 ─11 ​​→ q 3     q 3 ─1 → q 2     │
└──────┴───────────────────┴───────────────────── ─┴───────────┘

рис-5 Малюнок-5

Крок 6 : Вісім = 1000

┌┌ ─────────┐
│ НомерДвійковийЗалишок (% 5)Кінцевий станШляхДодати      │
├──────┼───────────────────┼───────────────────┼ ─────────┤
│ Вісім │1000 │8% 5 = 3 │q 3        │q 0 ─100 → q 4 │ q 4 ─0 → q 3   │
└──────┴───────────────────┴───────────────────┴ ─────────┘

рис-6 Малюнок-6

Крок 7 : Дев'ять = 1001

┌┌ ─────────┐
│ НомерДвійковийЗалишок (% 5)Кінцевий станШляхДодати      │
├──────┼───────────────────┼───────────────────┼ ─────────┤
│Дев’ять │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)Кінцевий станДодати         │
├───────┼───────┼────────────┼─────────┼───────── ────┤
│Три │10 │3 │q3 │ δ: (q1,0) → q3 │
├───────┼───────┼────────────┼─────────┼───────── ────┤
│Четверо │11 │4 │q4 │ δ: (q1,1) → q4 │
├───────┼───────┼────────────┼─────────┼───────── ────┤
│п’ять │12 │0 │q0 │ δ: (q1,2) → q0 │
└───────┴───────┴────────────┴─────────┴───────── ────┘

рис-9
Малюнок-9

Крок-4 Додайте шість, сім, вісім

┌───────┬───────┬────────────┬─────────┬───────── ────┐
│ ДесятковийТрійковийЗалишок (% 5)Кінцевий станДодати         │
├───────┼───────┼────────────┼─────────┼───────── ────┤
│Шість │20 │1 │q1 │ δ: (q2,0) → q1 │
├───────┼───────┼────────────┼─────────┼───────── ────┤
│Сім │21 │2 │q2 │ δ: (q2,1) → q2 │
├───────┼───────┼────────────┼─────────┼───────── ────┤
│ Вісім │22 │3 │q3 │ δ: (q2,2) → q3 │
└───────┴───────┴────────────┴─────────┴───────── ────┘

рис-10
Малюнок-10

Крок-5 Додайте дев’ять, десять, одинадцять

┌───────┬───────┬────────────┬─────────┬───────── ────┐
│ ДесятковийТрійковийЗалишок (% 5)Кінцевий станДодати         │
├───────┼───────┼────────────┼─────────┼───────── ────┤
│Дев’ять │100 │4 │q4 │ δ: (q3,0) → q4 │
├───────┼───────┼────────────┼─────────┼───────── ────┤
│Десять101 │0 │q0 │ δ: (q3,1) → q0 │
├───────┼───────┼────────────┼─────────┼───────── ────┤
│Одинадцять │102 │1 │q1 │ δ: (q3,2) → q1 │
└───────┴───────┴────────────┴─────────┴───────── ────┘

рис-11
Малюнок-11

Крок-6 Додайте дванадцять, тринадцять, чотирнадцять

┌────────┬───────┬────────────┬─────────┬──────── ─────┐
│ ДесятковийТрійковийЗалишок (% 5)Кінцевий станДодати         │
├────────┼───────┼────────────┼─────────┼──────── ─────┤
│Дванадцять │110 │2 │q2 │ δ: (q4,0) → q2 │
├────────┼───────┼────────────┼─────────┼──────── ─────┤
│Тринадцять│111 │3 │q3 │ δ: (q4,1) → q3 │
├────────┼───────┼────────────┼─────────┼──────── ─────┤
│Чотирнадцять│112 │4 │q4 │ δ: (q4,2) → q4 │
└────────┴───────┴────────────┴─────────┴──────── ─────┘

рис-12
Малюнок-12

Загальна кількість ребер на діаграмі переходу на малюнку 12 дорівнює 15 = Q × Σ = 5 * 3 (повний DFA). І цей DFA може приймати всі рядки, що складаються з {0, 1, 2}, ці десяткові еквіваленти діляться на 5.
Якщо ви помічаєте на кожному кроці, у таблиці є три записи, оскільки на кожному кроці я додаю всі можливі вихідні ребра із стану зробити повний DFA (і я додаю фронт, щоб q r стан отримав для залишку r)!

Щоб додати далі, пам’ятайте, об’єднання двох регулярних мов також є регулярним. Якщо вам потрібно розробити DFA, який приймає двійкові рядки, ці десяткові еквіваленти діляться на 3 або 5, тоді намалюйте два окремі DFA для ділення на 3 і 5, а потім об'єднайте обидва DFA для побудови цільового DFA (для 1 <= n <= 10 ви повинні об'єднати 10 DFA).

Якщо вас попросять намалювати DFA, який приймає двійкові рядки так, щоб десятковий еквівалент ділився на 5 і 3, тоді ви шукаєте DFA, що ділиться на 15 (але як щодо 6 і 8?).

Примітка: DFA, намальовані за допомогою цієї техніки, будуть мінімізовані DFA лише тоді, коли між числом і базою немає спільного множника, nнаприклад, у першому прикладі немає між 5 і 2 або у другому прикладі між 5 і 3, отже, обидва побудовані вище DFA мінімізовані DFA. Якщо ви зацікавлені читати далі про можливі міні-стани для числа nта базової bстатті для читання: Подільність та складність стану .

нижче я додав скрипт Python, я написав його для розваги під час вивчення бібліотеки Python pygraphviz. Я додаю, сподіваюся, це може якось комусь допомогти.

Розробити DFA для базових рядків числа "b", що діляться на число "n":

Отже, ми можемо застосувати вищеописаний фокус, щоб намалювати DFA для розпізнавання числових рядків у будь-якій основі, 'b'які діляться на дане число 'n'. У цьому DFA загальна кількість станів буде n(для nзалишків), а кількість ребер має дорівнювати 'b' * 'n' - це повний DFA: 'b' = кількість символів на мові DFA і 'n' = кількість штатів.

Використовуючи вищеописаний трюк, нижче я написав сценарій Python, щоб намалювати DFA для введення baseта number. У сценарії функція divided_by_Nзаповнює правила переходу DFA base * numberкроками. У кожному кроці-num я перетворюю numв числовий рядок num_sза допомогою функції 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 та для циклів? "



1
Щоб отримати альтернативну перспективу, можна було прочитати модульну арифметику.
Будь ласка, допоможіть

4
як довести це рішення?
Тім Хсу,

1
Мені також було цікаво, чи може хто-небудь подати посилання на ресурси, що підтверджують?
Джузер Алі

@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, що, якщо запитання задає деякий залишок K замість залишку 0, коли ділиться на 20? Чи зміниться відповідь?
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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.