摘要
文章通过比喻和形式定义介绍了“字母表”(Alphabet)和“字符串”(String)的概念。字母表是一组允许使用的基本符号,如拼字游戏中的字母牌或乐高积木的颜色;字符串则是用这些符号按顺序排列的组合,如拼出的单词或搭建的积木序列。字母表和字符串的关系类似于“砖头池”和“作品”,字母表规定了可用的符号,而字符串是这些符号的有序排列。
在形式语言和自动机中,字母表是自动机和形式语言的基础材料,定义了输入的可能性;字符串则是自动机要检查的对象,形式语言的成员。文章通过具体的自动机例子,如识别“偶数个1”的二进制串和括号匹配的字符串,详细说明了字母表、字符串、自动机的状态和转移过程,并提供了Python代码模拟。最后,文章总结了字母表、字符串、自动机和语言之间的关系,帮助读者更好地理解这些概念在实际中的应用。
一、什么是“字母表”(Alphabet)?
1. 生活比喻
比喻1:拼字游戏的字母牌
- 想象你在玩拼字游戏(比如Scrabble),桌上有一堆字母牌,比如A、B、C、D。
- 这些字母牌的集合,就是“字母表”。
比喻2:乐高积木的颜色
- 你有红、黄、蓝三种颜色的乐高积木。
- 这三种颜色就是你的“字母表”。
- 你只能用这三种颜色来搭积木。
2. 形式定义
- 字母表(通常用希腊字母Σ表示)就是一组允许使用的基本符号。
- 比如:Σ = {a, b},表示只能用a和b。
二、什么是“字符串”(String)?
1. 生活比喻
比喻1:用字母牌拼单词
- 你用字母牌拼出“CAB”这个单词。
- 这个“CAB”就是一个“字符串”。
- 你可以拼出“AB”、“BCA”、“AABBB”等等。
比喻2:用乐高积木搭一条蛇
- 你用红、黄、蓝积木按顺序搭成“红-蓝-黄-红”。
- 这就是一个“字符串”:“红蓝黄红”。
2. 形式定义
- 字符串就是:用字母表里的符号,按顺序排列起来的一串符号。
- 比如:字母表Σ = {a, b},那么“aab”、“bba”、“ababa”都是字符串。
三、字母表与字符串的关系
- 字母表:就像拼字游戏的“字母池”,规定了你能用哪些“砖头”。
- 字符串:就是你用这些砖头拼出来的“作品”,每个作品都是砖头的有序排列。
四、在形式语言和自动机中的作用
- 字母表:自动机和形式语言的“基础材料”,规定了输入的可能性。
- 字符串:自动机要“检查”的对象,形式语言的“成员”。
比如:
- 字母表Σ = {0, 1},字符串可以是“0101”、“111”、“0”等。
- 自动机会检查这些字符串是不是“合法的”(比如是不是偶数个1)。
五、举个具体例子
例子:地铁检票
- 地铁闸机只认“地铁票”和“公交卡”两种票(字母表Σ = {地铁票, 公交卡})。
- 你可以用这些票按顺序排队进站,比如“地铁票-公交卡-地铁票”。
- 这就是一个“字符串”。
- 闸机(自动机)会检查你这串票是不是符合规则(比如必须先刷地铁票,再刷公交卡)。
六、形象口诀
- 字母表:拼字的“砖头池”,规定你能用哪些砖头。
- 字符串:用砖头拼成的“作品”,每个作品都是砖头的有序排列。
我们用一个具体的自动机例子,详细说明字母表、字符串、自动机的状态和转移过程,让你一目了然。
例子:识别“偶数个1”的二进制串
1. 字母表(Alphabet)
- 记作:Σ = {0, 1}
- 这表示:我们的“砖头池”只有0和1两种砖头。
2. 字符串(String)
- 只能用0和1拼成,比如:
- “0”
- “1”
- “1010”
- “1101”
- “000”
- “1111”
- “”(空串)
3. 语言(Language)
- 我们要识别的“语言”是:所有1的个数是偶数的字符串。
- 比如:
- “00” ✔(0个1,偶数)
- “11” ✔(2个1,偶数)
- “1010” ✔(2个1,偶数)
- “1” ✘(1个1,奇数)
- “1101” ✘(3个1,奇数)
4. 自动机的设计
状态
- S0:当前读到的1的个数是偶数(初始状态,也是接受状态)
- S1:当前读到的1的个数是奇数
状态转移规则
- 在S0:
- 读到0,留在S0
- 读到1,转到S1
- 在S1:
- 读到0,留在S1
- 读到1,转到S0
状态转移图
1
+-----+ +-----+
| S0 |----->| S1 |
+-----+ +-----+
^ |
| |
+-----------+
1
S0 --0--> S0
S1 --0--> S1
状态转移表
当前状态 | 输入 | 下一个状态 |
---|---|---|
S0 | 0 | S0 |
S0 | 1 | S1 |
S1 | 0 | S1 |
S1 | 1 | S0 |
初始状态
- S0
接受状态
- S0(因为我们要偶数个1)
5. 检查字符串的过程举例
检查字符串“1010”:
- 初始在S0
- 读1:S0 → S1
- 读0:S1 → S1
- 读1:S1 → S0
- 读0:S0 → S0
- 结束,停在S0(接受)
检查字符串“1101”:
- 初始在S0
- 读1:S0 → S1
- 读1:S1 → S0
- 读0:S0 → S0
- 读1:S0 → S1
- 结束,停在S1(不接受)
6. Python代码模拟
def even_ones(s):
state = 0 # 0: 偶数个1, 1: 奇数个1
for c in s:
if c == '1':
state = 1 - state
return state == 0
print(even_ones("1010")) # True
print(even_ones("1101")) # False
总结
- 字母表:Σ = {0, 1}
- 字符串:如“1010”、“1101”等
- 自动机:有两个状态,按规则跳转
- 语言:所有1的个数为偶数的字符串
好的!我们来看一个带栈的自动机(堆栈自动机,PDA),用来识别括号匹配的字符串。这个例子比有限自动机复杂,因为它需要“记住”括号的嵌套情况。
1. 问题描述
字母表:Σ = { (, ) }
语言:所有括号完全匹配的字符串
比如:
- “”(空串)✔
- “()” ✔
- “(())” ✔
- “()()” ✔
- “(()())” ✔
- “())(” ✘
- “(()” ✘
- “())” ✘
2. 为什么有限自动机做不到?
有限自动机没有“记忆”,无法记住有多少个左括号还没配对。
但堆栈自动机可以用“栈”来记住每一个未配对的左括号。
3. PDA的工作原理
- 每读到一个“(”,就往栈里压一个符号(比如“X”)。
- 每读到一个“)”:
- 如果栈顶有“X”,就弹出一个“X”。
- 如果栈空,说明多了右括号,不匹配。
- 读完后,栈必须空,才算完全匹配。
4. 状态转移图(简化版)
我们只需要两个状态:
- q0:正常处理
- q_accept:接受状态(读完且栈空)
初始状态q0,栈初始为空
q0 -- 读"(",栈顶任意 --> q0,栈压入"X"
q0 -- 读")",栈顶"X" --> q0,栈弹出"X"
q0 -- 读完,栈顶为空 --> q_accept
图示:
(,任意
+------------------+
| v
+------+ +------+
| q0 | ------> | q0 |
+------+ +------+
^ |
| |
+------------------+
),X
- 读“(”就压X
- 读“)”且栈顶是X就弹X
- 读完且栈空就接受
5. 过程举例
检查字符串 “(()())”
- 初始:栈空
- 读“(”:压X → 栈[X]
- 读“(”:压X → 栈[XX]
- 读“)”:弹X → 栈[X]
- 读“(”:压X → 栈[XX]
- 读“)”:弹X → 栈[X]
- 读“)”:弹X → 栈[]
- 读完,栈空,接受!
检查字符串 “(()”
- 初始:栈空
- 读“(”:压X → 栈[X]
- 读“(”:压X → 栈[XX]
- 读“)”:弹X → 栈[X]
- 读完,栈非空,不接受!
6. Python代码模拟
def match_parentheses(s):
stack = []
for c in s:
if c == '(':
stack.append('X')
elif c == ')':
if not stack:
return False
stack.pop()
return not stack
print(match_parentheses("(()())")) # True
print(match_parentheses("(()")) # False
print(match_parentheses("())(")) # False
7. 总结
- 字母表:{ (, ) }
- 字符串:如"(()())"
- 堆栈自动机:用栈记住未配对的左括号
- 语言:所有括号完全匹配的字符串