有限状态机

53 阅读5分钟

一 有限状态机 (Finite State Machine, FSM)

1、基本概念

有限状态机是一种数学计算模型,用于描述系统在不同状态之间的转换行为。它由有限个状态以及这些状态之间的转换规则组成。

2、核心组成要素

  1. 状态 (State): 系统在某一时刻的运行情况
  2. 事件 (Event): 触发状态转换的条件
  3. 转换 (Transition): 从一个状态到另一个状态的变化过程
  4. 动作 (Action): 状态转换时执行的操作
  5. 初始状态: 系统的起始状态
  6. 终止状态: 系统的结束状态(可选)

3、应用场景

  • 🎮 游戏开发: 角色AI行为控制(巡逻→追击→攻击)
  • 🔐 协议设计: TCP连接状态管理
  • 🤖 硬件设计: 数字电路、嵌入式系统
  • 💬 编译器: 词法分析器
  • 🚦 控制系统: 交通信号灯、自动售货机
  • 🎯 工作流: 订单状态管理(待支付→已支付→已发货→已完成)

4、简单示例

交通信号灯状态机:

状态: 红灯 → 绿灯 → 黄灯 → 红灯
事件: 定时器超时
动作: 切换灯光颜色

代码示例 (Python):

class TrafficLight:
    def __init__(self):
        self.state = "RED"
    
    def change(self):
        if self.state == "RED":
            self.state = "GREEN"
        elif self.state == "GREEN":
            self.state = "YELLOW"
        elif self.state == "YELLOW":
            self.state = "RED"
        
        print(f"当前状态: {self.state}")

二 怎么可以想到用有限状态机解决问题

1、典型特征信号 🚦

当你的问题具有以下特征时,应该考虑FSM:

1.1 有明确的"阶段"或"模式"

❓ 问题中是否有"先...然后...最后..."的描述?
❓ 是否需要"依次处理"某些步骤?

例如:
- myAtoi: 先跳过空格 → 再处理符号 → 再读数字
- 括号匹配: 遇到'(' → 遇到')' → 判断是否匹配
- URL解析: 协议 → 域名 → 路径 → 参数

1.2 处理逻辑随"当前情况"变化

❓ 同样的输入,在不同情况下处理方式不同?
❓ 是否有"如果当前在...状态,遇到...就..."的逻辑?

例如:
- 在"已读符号"状态遇到数字 → 开始累加
- 在"初始"状态遇到数字 → 直接累加
- 在"数字"状态遇到字母 → 结束

1.3 顺序处理字符/事件流

❓ 是否需要逐个字符/事件处理?
❓ 前面的处理会影响后面的行为?

例如:
- 字符串解析(JSON、XML、表达式)
- 网络协议处理(HTTP、TCP)
- 词法分析器

1.4 有"非法"或"终止"条件

❓ 某些情况下需要立即停止处理?
❓ 有明确的"合法路径""非法路径"?

例如:
- 遇到非数字字符就停止
- 状态转换不合法就报错

2、快速判断清单

遇到问题时,问自己这些问题:

问题如果答案是"是"示例
能否画出流程图?可能适合FSM订单状态流转
是否有if-else嵌套超过3层?FSM可以简化复杂的输入验证
是否在不同阶段做不同的事?典型FSM场景登录流程
处理逻辑能否用"当前状态+输入→新状态"描述?完美匹配FSM游戏角色AI
是否需要"记住"之前发生了什么?状态就是记忆解析引号内的字符串

3、常见FSM题目模式 📝

模式1: 字符串解析类

关键词:解析、验证、转换、提取
题目:
- 字符串转整数 (atoi)
- 验证IP地址
- 简化路径
- 有效数字
- 正则表达式匹配

模式2: 括号/配对类

关键词:匹配、嵌套、平衡
题目:
- 有效的括号
- 最长有效括号
- 删除无效的括号

模式3: 状态转换类

关键词:状态、阶段、流程
题目:
- 股票买卖(持有/未持有状态)
- 打家劫舍(抢/不抢状态)
- 游戏胜负判断

模式4: 模式识别类

关键词:模式、序列、匹配
题目:
- KMP算法
- 字符串匹配
- 正则表达式

4、对比:何时不用FSM?

❌ 不适合FSM的情况:

  1. 需要回溯 - 如深度优先搜索
  2. 状态爆炸 - 状态数量指数增长
  3. 简单的线性逻辑 - 几个if就能解决
  4. 需要复杂计算 - 如数学公式求解

5、总结

核心判断标准:

  • ✅ 能否用"状态+输入→新状态"描述?
  • ✅ 是否有明确的处理阶段?
  • ✅ 同样输入在不同阶段行为不同?

如果3个问题都是"是",那就是FSM的好场景!

三、leedCode实战

问题

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数。

函数 myAtoi(string s) 的算法如下:

  1. 空格: 读入字符串并丢弃无用的前导空格(" "
  2. 符号: 检查下一个字符(假设还未到字符末尾)为 '-' 还是 '+'。如果两者都不存在,则假定结果为正。
  3. 转换: 通过跳过前置零来读取该整数,直到遇到非数字字符或到达字符串的结尾。如果没有读取数字,则结果为0。
  4. 舍入: 如果整数数超过 32 位有符号整数范围 [−231,  231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被舍入为 −231 ,大于 231 − 1 的整数应该被舍入为 231 − 1 。

解答

先画出状态机 image.png 根据状态机实现程序 `class Solution { public int myAtoi(String s) { int state = 0; int flag = 1; int result = 0;

    for(char c : s.toCharArray()) {
        switch(state) {
            case 0:
            if(c == ' ') state = 0;
            else if(c == '+' || c == '-') {
                state = 1;
                flag = c == '+' ? 1 : -1;
            } else if(isDigit(c)) {
                state = 2;
                result = c - '0';
            } else {
                state = 3;
            }
            break;
            case 1:
            if(isDigit(c)) {
                state = 1;
                result = result * 10 + (c - '0');
            } else {
                state = 3;
            }
            break;
            case 2:
            if(isDigit(c)) {
                state = 2;
                result = result * 10 + (c - '0');
            } else {
                state = 3;
            }
            break;
        }
        if(state == 3) {
            return flag * result;
        }
    }
    return flag * result;
}

public boolean isDigit(char c) {
    return c >= '0' && c <= '9';
}