3、状态机表示方法:状态图、状态转移表、状态转移矩阵

有限状态机(FSM)与层次状态机(HSM)的建模,本质上是对系统行为的一种精确描述。在工程实践中,我们面临的首要问题不是“如何设计状态机”,而是“如何把脑子里的状态逻辑清晰、无歧义地表达出来”。状态图(State Diagram)、状态转移表(State Transition Table)和状态转移矩阵(State Transition Matrix)是三种最核心的表示方法,它们分别服务于人类直觉沟通、形式化验证和算法化处理。本章将深入剖析这三种表示法的内在结构、适用场景及相互转换关系,帮助你在嵌入式、协议栈或业务编排中做出正确的建模选择。

状态机三种表示方法关系与转换 状态机模型 (S, E, T, S0) 状态图 (State Diagram) S1 S2 e1 / a1 S3 e2 / a2 直观、适合人脑理解 状态转移表 当前状态 | 事件 | 动作 | 下一状态 S1 | e1 | a1 | S2 S2 | e2 | a2 | S3 S3 | e3 | - | S1 精确、适合形式化验证 状态转移矩阵 行=当前状态,列=事件,单元格=下一状态/动作 适合算法生成、模型检测、表格驱动 可视化 结构化 矩阵化

上图展示了三种表示方法在状态机建模生态中的定位:状态图是面向人脑的“直觉层”,状态转移表是面向文档与验证的“结构层”,状态转移矩阵则是面向计算机与算法的“数据层”。三者描述的是同一逻辑内核,但表达维度和信息密度不同。下面我们逐一展开。

3.1 状态图:可视化建模的黄金标准

核心原理

状态图(State Diagram)源自UML(统一建模语言)状态机图,它用图形化元素表达状态、事件、动作和转移关系。其基本构成包括:圆角矩形(状态)、实心圆(初始伪状态)、牛眼符号(终止状态)、带箭头的连线(转移)。每个转移上标注“事件[守卫条件]/动作”的语法,例如 e1[count > 3] / sendAlert()。状态图的最大优势在于层次化表达能力——它天然支持嵌套状态(子状态)、历史状态(H*)和并发区域(fork/join),这是层次状态机(HSM)建模的首选工具。

判断方法与操作流程

在工程中,判断何时使用状态图,可遵循以下准则:

  • 当系统状态数 < 10 且交互逻辑复杂时,状态图能显著降低沟通成本。
  • 当需要表达状态嵌套或并发行为时(如通信协议栈、GUI控件),状态图几乎是唯一直观的选择。
  • 当团队进行设计评审时,状态图作为讨论媒介,比表格更容易发现遗漏的转移路径。

绘制状态图的标准流程为:

  1. 列出所有候选状态,剔除重复或可合并的状态。
  2. 确定初始状态和终止状态(若有)。
  3. 对每个状态,枚举所有可能触发转移的事件。
  4. 为每个转移标注事件、守卫条件(可选)和动作(可选)。
  5. 检查是否存在“非确定性转移”(同一事件在同一状态产生多个未加守卫的转移)。

案例拆解:电梯控制器状态图

考虑一个简化电梯控制器,状态包括:Idle(待机)、MovingUp(上行)、MovingDown(下行)、DoorOpen(开门)。事件包括:callUp、callDown、arriveFloor、doorClose。状态图可清晰表达:在 Idle 状态收到 callUp 后转移至 MovingUp 并执行 startMotor();在 MovingUp 状态收到 arriveFloor 后转移至 DoorOpen 并执行 stopMotor()。若在 MovingUp 时收到 callDown,则需守卫条件 [currentFloor > targetFloor] 才能响应,否则忽略。这种带守卫的转移在表格中表达较为繁琐,但在状态图中一目了然。

经验提醒:状态图虽然直观,但若状态数超过15个且无层次划分,图形会迅速变得杂乱无章。此时应优先考虑引入复合状态(Composite State)或正交区域(Orthogonal Region),将平面图转化为层次图。

3.2 状态转移表:精确性与可验证性的基石

核心原理

状态转移表(State Transition Table)以行表示当前状态,以列表示事件(或输入),单元格内填写“下一状态/动作”。它消除了状态图的视觉歧义,每个转移都是显式且唯一的。对于有限状态机(FSM),转移表是形式化验证(如模型检测、等价性检查)的输入格式。在安全关键系统(如航空电子、医疗设备)中,转移表是需求文档的必备附件,因为它可以逐行审查,确保没有遗漏任何事件-状态组合。

操作流程与表格设计

构建状态转移表的标准步骤:

  1. 定义状态集合 S = {s1, s2, ..., sn}。
  2. 定义事件集合 E = {e1, e2, ..., em}。
  3. 对每个 (si, ej) 组合,确定转移目标 T(si, ej) 和输出动作 A(si, ej)。
  4. 若某组合无转移,则标记为“-”或“忽略”,但必须显式写出,避免歧义。

一个典型的转移表结构如下:

当前状态事件 e1事件 e2事件 e3
S1S2 / action1-S3 / action3
S2-S1 / action2S3 / -
S3S1 / action1S2 / --

注意,表中“-”表示该事件在当前状态下被忽略,但必须显式标注,否则在代码实现时容易产生未定义行为。

实战场景:通信协议解析器

在实现一个UART通信协议解析器时,状态包括 WAIT_START、WAIT_LEN、WAIT_DATA、WAIT_CRC。事件为接收到的字节类型。使用状态转移表可以精确描述:在 WAIT_START 收到 0xAA 时转移至 WAIT_LEN;收到其他字节则停留在 WAIT_START 并丢弃。这种表格可以直接转化为C语言的switch-case结构,且每个case都有明确的输入-输出映射,便于单元测试用例的生成。

风险提示:状态转移表不适合表达层次状态机(HSM)。当存在嵌套状态时,表格的行数会呈指数级膨胀,因为每个子状态组合都需要单独一行。此时应使用状态图或状态转移矩阵的扩展形式(如状态配置表)。

3.3 状态转移矩阵:算法化与模型检测的利器

核心原理

状态转移矩阵(State Transition Matrix)是转移表的数学化表达,通常以二维矩阵 M 表示,其中 M[i][j] 表示从状态 i 经事件 j 转移到的目标状态。矩阵元素可以是目标状态编号、动作编码或“无效”(用 -1 或 INF 表示)。这种表示法在模型检测工具(如NuSMV、SPIN)、自动代码生成(如MATLAB Stateflow)和图搜索算法(如BFS/DFS遍历状态空间)中广泛使用。

判断方法与构建要点

选择矩阵表示法的判断依据:

  • 需要执行状态空间搜索(如寻找最短路径、检测死锁)时,矩阵是标准输入。
  • 需要做矩阵运算(如计算传递闭包、检测可达性)时,矩阵是唯一选择。
  • 状态数较多(> 20)且事件数固定时,矩阵比表格更紧凑。

构建矩阵时需注意:

  1. 矩阵的行和列必须明确标注状态名和事件名,避免索引错位。
  2. 对于无转移的单元格,建议填入特殊值(如 -1),而不是留空。
  3. 若同一事件在不同守卫条件下产生不同转移,矩阵无法直接表达,需拆分为多个事件(如 e1_guard_true、e1_guard_false)。

案例拆解:自动售货机状态矩阵

设自动售货机状态:S0(空闲)、S1(已投币5元)、S2(已投币10元)。事件:coin5、coin10、dispense。状态转移矩阵如下:

当前状态coin5coin10dispense
S0S1S2-1
S1S2-1S0
S2-1-1S0

该矩阵可直接用于生成状态转移图,也可用于验证“从S0出发,经过任意事件序列,最终能否回到S0”的可达性分析。在Python中,可用二维列表或NumPy数组表示,配合BFS算法快速检测是否存在不可达状态。

经验总结:三种表示法并非互斥,而是互补。在项目实践中,推荐“状态图用于设计沟通,转移表用于需求规格,转移矩阵用于代码生成与验证”。在代码实现阶段,矩阵驱动的表驱动法(Table-Driven)能显著减少if-else嵌套,提高可维护性。

3.4 常见误区与选型复盘

误区一:状态图与流程图混淆

状态图描述的是“状态因事件而迁移”,流程图描述的是“步骤因条件而顺序执行”。例如,电梯的“开门-关门-上行”是流程,而“DoorOpen状态在收到doorClose事件后转移至MovingUp状态”才是状态图。若将流程逻辑画成状态图,会导致状态爆炸且难以维护。

误区二:转移表遗漏“忽略”事件

许多工程师在转移表中只填写有转移的单元格,忽略无转移的情况。这会导致代码实现时出现未处理的输入,引发运行时错误。正确做法是显式填写“-”或“忽略”,并在代码中生成默认分支。

误区三:矩阵中混淆守卫条件

状态转移矩阵无法直接表达守卫条件(Guard)。若强行将带守卫的转移写入矩阵,会导致矩阵元素不唯一。解决方案是将守卫条件转化为事件的一部分,例如将 e1[count>3] 拆分为 e1_high 和 e1_low 两个事件,分别填入矩阵的不同列。

选型决策清单

场景推荐表示法理由
设计评审、团队沟通状态图直观、层次化、易发现逻辑漏洞
安全关键系统需求文档状态转移表可逐行审查、无歧义、便于形式化验证
自动代码生成、模型检测状态转移矩阵算法友好、支持矩阵运算、紧凑
状态数 > 20 且含嵌套层次状态图 + 转移表平面表示法会指数膨胀

在实际项目中,建议在需求阶段使用状态图进行探索性设计,在设计定型后转换为状态转移表作为正式规格,最后在编码阶段将转移表映射为矩阵或直接生成代码。这种“图-表-矩阵”的演进路径,既能保证沟通效率,又能确保实现精度。