本文讨论了计算机科学(CS)的理论和实践问题。它回顾了图灵机(TM),这是自动机的基本类别,并提出了针对TM的广泛变体的模拟器:不确定性的多磁带。通过计算树的广度优先搜索(BFS)模拟不确定性。
该模拟器是用Python 3编写的,并结合了面向对象和函数式编程技术,充分利用了该编程语言的强大功能和表达能力。
组织如下。首先,以非正式的方式介绍了TM,从而突出了其在理论CS中的许多应用。然后,给出了基本模型和多带式变体的正式定义。最后,介绍了模拟器的设计和实现,并提供了其使用和执行的示例。
介绍
TM是Alan Turing在1936年设计的抽象自动机,用于研究计算的极限。 TM能够遵循简单的规则来计算函数。
TM是具有三个组成部分的原始计算模型:
- 存储器:输入输出磁带,分为多个存储符号的离散单元。磁带上的单元格最少,但是右侧没有限制,因此可以存储的字符串长度没有限制。
- 一个控制单元,具有一组有限的状态和一个指向当前单元的磁带头,并且可以在计算过程中向左或向右移动。
- 存储在有限控制中的程序,用于控制机器的计算。
TM的操作包括三个阶段:
- 初始化。长度为N的输入字符串加载到磁带的前N个单元上。无限多个单元格的其余部分包含一个称为空白的特殊符号。机器切换到启动状态。
- 计算。每个计算步骤涉及:
- 读取当前单元格中的符号(磁带头正在扫描的符号)。
- 遵循程序定义的规则,以结合当前状态和读取的符号。这些规则称为过渡或移动,包括:(a)在当前单元格中写入新符号,(b)切换到新状态,以及(c)可选地将头一个单元格向左或向右移动。
- 定案。当没有关于当前状态和符号的规则时,计算将停止。如果机器处于最终状态,则TM接受输入字符串。如果当前状态为非最终状态,则TM拒绝输入字符串。请注意,并非所有TM都达到此阶段,因为TM可能永不停止在给定输入上进入无限循环。
TM在理论计算机科学中有许多应用,并且与形式语言理论密切相关。 TM是接受Chomsky语言层次结构的顶级类的语言识别器:由不受限制的语法生成的语言的类型0。它们也是语言转换器:给定一种语言的输入字符串,TM可以计算相同或不同语言的输出字符串。此功能允许TM计算其输入和输出被编码为形式语言的字符串的函数,例如,二进制数字被视为字母{0,1}上的一组字符串。
Church-Turing指出,TM能够计算出算法可以表达的任何函数。它的含义是,TM实际上是通用计算机,它们是抽象的数学设备,不受物理设备的时间和空间的限制。正如许多计算机科学家所相信的那样,如果这一论点是正确的,那么图灵发现的事实是,有些功能无法通过TM进行计算,这意味着某些功能无法通过计算机进行过去,现在或将来的算法计算。
在研究计算复杂性以及CS和数学的主要开放问题之一:P vs NP问题中,TMs也非常重要。 TM是一种方便的,独立于硬件的模型,用于根据计算过程中执行的步数(时间复杂度)或扫描的单元数(空间复杂度)来分析算法的计算复杂性。
正式定义:基本模型
图灵机(TM)是一个7元组在哪里:
- 是一组有限的非空状态。
- 是称为磁带字母表的一组有限的非空符号。
- 是输入字母。
- 是将状态符号对映射到三元状态,符号,头部方向(左,右或停留)的子集的过渡或下一移动函数。
- 是开始状态。
- 是最终的接受状态。
- 是空白符号。
在计算的每个步骤中,可以通过即时描述(ID)描述TM。 ID是三元组在哪里是实际状态是机器正在扫描的单元格左侧单元格中包含的字符串,并且是当前单元格和磁带头右边其他单元格中包含的字符串,直到开始无限次空白序列的单元格为止。
二进制关系关联两个ID,并针对所有ID定义如下和和 :
- iff
- iff
- iff
- iff
- iff
- iff
让是…的及物和反身封闭 ,即在ID之间应用零个或多个转换。然后, 定义为: 。
如果适用于所有州和磁带符号 , TM最多具有确定性。如果存在多个选择,那么TM是不确定的。
确定性TM的ID序列是线性的。对于不确定的TM,它形成一个计算树。可以将非确定性视为机器创建并行执行的自身副本。我们的模拟器将使用这种有用的类比。
乍一看,我们可以认为非确定性TM比确定性TM更强大,因为它能够“猜测”正确的路径。但是,事实并非如此:DTM只是NDTM的特殊情况,任何NDTM都可以转换为DTM。因此,它们具有相同的计算能力。
实际上,已经提出了TM的几种变体:带有双向无限磁带,具有多个磁道,没有停留选项等。有趣的是,所有这些变体都具有与基本模型相同的计算能力。他们承认同一类语言。
在下一部分中,我们将介绍一个非常有用的变体:多带不确定性TM。
正式定义:Multitape TM
Multitape TM具有多个带有独立磁头的输入输出带。该变体不会增加原始磁带的计算能力,但是正如我们将看到的那样,它可以简化使用辅助磁带构建TM的过程。
K-tape TM是7元组其中所有元素都与基本TM中的元素相同,但过渡函数是映射 。它将状态读取符号对映射到新状态对-写入符号+方向对的子集。
例如,下面的2磁带TM计算第一磁带中以一元符号存储的数字的总和。第一个磁带包含因素:代表自然数的1序列由0分隔。机器将所有1写入磁带2,计算所有因素的总和。
正式地,让在哪里定义如下:
- 跳过所有0:
- 将1复制到磁带2:
- 达到空白时停止:
停顿问题
TM对于某些输入可能不会停止运行。例如,考虑TM 和 。
暂停问题指出,无法确定任意TM是否会在给定的输入字符串上暂停。这个问题具有深远的意义,因为它表明存在TM无法计算的问题,如果Church-Turing命题是正确的,则意味着没有算法可以解决该问题。
对于TM模拟器来说,这是个坏消息,因为这意味着模拟器可能会陷入无限循环。
我们不能完全避免这个问题,但是我们可以解决它的受限形式。考虑非确定性TM的情况,如果计算树的分支进入无限循环并永远增长,直到其他分支达到最终状态。在这种情况下,模拟器应停止接受输入字符串。但是,如果我们以深度优先搜索(DFS)的方式遍历树,则当模拟器进入无限分支之一时,它将陷入困境。为了避免这种情况,模拟器将通过广度优先搜索(BFS)遍历计算树。 BFS是一种图遍历策略,它在探究分支的所有子级之前,先探究其所有子级。
Python的多磁带NDTM模拟器
在本节中,我们将介绍用于不确定性TM的模拟器,该模拟器具有多个用Python编写的磁带。
该模拟器包括两个类:Tape类和NDTM类。
磁带实例包含当前扫描的单元列表和磁带头索引,并提供以下操作:
- readSymbol():返回由头部扫描的符号;如果头部在最后扫描的单元格中,则返回空白
- writeSymbol():将头部扫描的符号替换为另一个符号。如果头部位于最后扫描的单元格中,则将符号附加到符号列表的末尾。
- moveHead():将头部向左(-1),向右(1)或无位置(0)的位置移动一个位置。
- clone():创建磁带的副本或副本。这对于模拟不确定性非常有用
NDTM实例具有以下属性:
- 起始状态和最终状态。
- 当前状态。
- 磁带列表(磁带对象)。
- 过渡字典。
转换函数是用字典实现的,该字典的键是元组(状态,read_symbols),其值是元组列表(new_state,moves)。例如,以以前出现的一元符号添加数字的TM将表示为:
{('q0', ('1', '#')): [('q0', (('1', 'R'), ('1', 'R')))],
('q0', ('0', '#')): [('q0', (('0', 'R'), ('#', 'S')))],
('q0', ('#', '#')): [('q1', (('#', 'S'), ('#', 'S')))]}
请注意,由于Python的通用数据结构(如字典,元组和列表), Python表示形式与转换函数的数学定义非常相似。 dict的一个子类,标准集合模块中的defaultdict,用于减轻初始化的负担。
NDTM对象包含用于读取磁带中当前符号元组,添加,获取和执行过渡以及制作当前TM副本的方法。
NDTM的主要方法是accepts()。它的参数是输入字符串,如果计算树的任何分支到达接受状态,则返回NDTM对象;如果没有分支则返回NDTM对象。它通过广度优先搜索(BFS)遍历计算树,以允许在任何分支到达接受状态时停止计算。 BFS使用队列来跟踪挂起的分支。来自collections模块的Python deque用于在队列操作中获得O(1)性能。算法如下:
Add the TM instance to the queue
While queue is not empty:
Fetch the first TM from the queue
If there is no transition for the current state and read symbols:
If the TM is in a final state: return TM
Else:
If the transition is nondeterministic:
Create replicas of the TM and add them to the queue
Execute the transition in the current TM and add it to the queue
最后,NDTM类具有一些方法,可将TM表示形式打印为瞬时描述的集合,并从文件中解析TM规范。像往常一样,此输入/输出功能是模拟器中最繁琐的部分。
规范文件具有以下语法
% HEADER: mandatory
start_state final_state blank number_of_tapes
% TRANSITIONS
state read_symbols new_state write_symbol, move write_symbol, move ...
以“%”开头的行被视为注释。例如,以一元表示法添加数字的TM具有以下规范文件:
% HEADER
q0 q1 # 2
% TRANSITIONS
q0 1, # q0 1, R 1, R
q0 0, # q0 0, R #, S
q0 #, # q1 #, S #, S
状态和符号可以是任何不包含空格或逗号的字符串。
可以从Python会话运行模拟器,以探索输出配置。例如,如果先前的文件以名称“ 2sum.tm”保存:
from NDTM import NDTM
tm = NDTM.parse('2sum.tm')
print(tm.accepts('11011101'))
输出显示模拟器在磁带#1中产生了1的总和:
Output :
q1: ['1', '1', '0', '1', '1', '1', '0', '1']['#']
q1: ['1', '1', '1', '1', '1', '1']['#']
输出显示两个磁带的内容,磁头的位置(每个磁带的第二个列表)以及TM的最终状态。
模拟器的源代码
除去输入/输出代码和注释,该模拟器少于100行代码。它证明了Python的强大功能和经济性。它是面向对象的,但也使用诸如列表推导之类的功能构造。
####
# NDTM.py: a nondeterministic Turing Machine Simulator
# Author: David Gil del Rosal (dgilros@yahoo.com)
#### from collections import defaultdict, deque
class Tape:
# Constructor. Sets the blank symbol, the
# string to load and the position of the tape head
def __init__(self, blank, string ='', head = 0):
self.blank = blank
self.loadString(string, head)
# Loads a new string and sets the tape head
def loadString(self, string, head):
self.symbols = list(string)
self.head = head
# Returns the symbol on the current cell, or the blank
# if the head is on the start of the infinite blanks
def readSymbol(self):
if self.head < len(self.symbols):
return self.symbols[self.head]
else:
return self.blank
# Writes a symbol in the current cell, extending
# the list if necessary
def writeSymbol(self, symbol):
if self.head < len(self.symbols):
self.symbols[self.head] = symbol
else:
self.symbols.append(symbol)
# Moves the head left (-1), stay (0) or right (1)
def moveHead(self, direction):
if direction == 'L': inc = -1
elif direction == 'R': inc = 1
else: inc = 0
self.head+= inc
# Creates a new tape with the same attributes than this
def clone(self):
return Tape(self.blank, self.symbols, self.head)
# String representation of the tape
def __str__(self):
return str(self.symbols[:self.head]) + \
str(self.symbols[self.head:])
class NDTM:
# Constructor. Sets the start and final states and
# inits the TM tapes
def __init__(self, start, final, blank ='#', ntapes = 1):
self.start = self.state = start
self.final = final
self.tapes = [Tape(blank) for _ in range(ntapes)]
self.trans = defaultdict(list)
# Puts the TM in the start state and loads an input
# string into the first tape
def restart(self, string):
self.state = self.start
self.tapes[0].loadString(string, 0)
for tape in self.tapes[1:]:
tape.loadString('', 0)
# Returns a tuple with the current symbols read
def readSymbols(self):
return tuple(tape.readSymbol() for tape in self.tapes)
# Add an entry to the transaction table
def addTrans(self, state, read_sym, new_state, moves):
self.trans[(state, read_sym)].append((new_state, moves))
# Returns the transaction that corresponds to the
# current state & read symbols, or None if there is not
def getTrans(self):
key = (self.state, self.readSymbols())
return self.trans[key] if key in self.trans else None
# Executes a transaction updating the state and the
# tapes. Returns the TM object to allow chaining
def execTrans(self, trans):
self.state, moves = trans
for tape, move in zip(self.tapes, moves):
symbol, direction = move
tape.writeSymbol(symbol)
tape.moveHead(direction)
return self
# Returns a copy of the current TM
def clone(self):
tm = NDTM(self.start, self.final)
tm.state = self.state
tm.tapes = [tape.clone() for tape in self.tapes]
tm.trans = self.trans # shallow copy
return tm
# Simulates the TM computation. Returns the TM that
# accepted the input string if any, or None.
def accepts(self, string):
self.restart(string)
queue = deque([self])
while len(queue) > 0:
tm = queue.popleft()
transitions = tm.getTrans()
if transitions is None:
# there are not transactions. Exit
# if the TM is in the final state
if tm.state == tm.final: return tm
else:
# If the transaction is not deterministic
# add replicas of the TM to the queue
for trans in transitions[1:]:
queue.append(tm.clone().execTrans(trans))
# execute the current transition
queue.append(tm.execTrans(transitions[0]))
return None
def __str__(self):
out = ''
for tape in self.tapes:
out+= self.state + ': ' + str(tape) + '\n'
return out
# Simple parser that builds a TM from a text file
@staticmethod
def parse(filename):
tm = None
with open(filename) as file:
for line in file:
spec = line.strip()
if len(spec) == 0 or spec[0] == '%': continue
if tm is None:
start, final, blank, ntapes = spec.split()
ntapes = int(ntapes)
tm = NDTM(start, final, blank, ntapes)
else:
fields = line.split()
state = fields[0]
symbols = tuple(fields[1].split(', '))
new_st = fields[2]
moves = tuple(tuple(m.split(', '))
for m in fields[3:])
tm.addTrans(state, symbols, new_st, moves)
return tm
if __name__ == '__main__':
# Example TM that performs unary complement
tm = NDTM('q0', 'q1', '#')
tm.addTrans('q0', ('0', ), 'q0', (('1', 'R'), ))
tm.addTrans('q0', ('1', ), 'q0', (('0', 'R'), ))
tm.addTrans('q0', ('#', ), 'q1', (('#', 'S'), ))
acc_tm = tm.accepts('11011101')
if acc_tm: print(acc_tm)
else: print('NOT ACCEPTED')
一个不平凡的例子
作为最后一个示例,我们介绍了可识别非上下文无关语言的3-tape TM的规范 。
TM不确定地复制磁带#2中字符串的前半部分和磁带#3中后半部分的内容。然后,继续检查两个部分是否匹配。
% 3-tape NDTM that recognizes L={ ww | w in {0, 1}* }
q0 q4 # 3
% TRANSITIONS
% put left endmarkers on tapes #2 and #3
q0 0, #, # q1 0, S $, R $, R
q0 1, #, # q1 1, S $, R $, R
% first half of string: copy symbols on tape #2
q1 0, #, # q1 0, R 0, R #, S
q1 1, #, # q1 1, R 1, R #, S
% guess second half of string: copy symbols on tape #3
q1 0, #, # q2 0, R #, S 0, R
q1 1, #, # q2 1, R #, S 1, R
q2 0, #, # q2 0, R #, S 0, R
q2 1, #, # q2 1, R #, S 1, R
% reached end of input string: switch to compare state
q2 #, #, # q3 #, S #, L #, L
% compare strings on tapes #2 and #3
q3 #, 0, 0 q3 #, S 0, L 0, L
q3 #, 1, 1 q3 #, S 1, L 1, L
% if both strings are equal switch to final state
q3 #, $, $ q4 #, S $, S $, S
用法示例。将上面的文件另存为“ 3ww.tm”,然后运行以下代码:
from NDTM import NDTM
tm = NDTM.parse('3ww.tm')
print(tm.accepts('11001100'))
产生的输出符合预期:TM达到了最终状态,并且输入字符串的两半的内容位于磁带#2和#3中。
Output :
q4: ['1', '1', '0', '0', '1', '1', '0', '0']['#']
q4: []['$', '1', '1', '0', '0', '#']
q4: []['$', '1', '1', '0', '0', '#']
一个有趣的练习是尝试将此TM转换为单磁带非确定性TM甚至是单磁带确定性TM。这是完全有可能的,但是规范会繁琐得多。这是拥有多个磁带的实用程序:没有更多的计算能力,但是更加简单。