文章目录
- 1.概念
- 2.什么是计算
- 3.什么是有限状态机
- 3.1特性
- 3.2为什么要用状态机
- 4.实战
- 4.1字符串转换整数
- 4.2用有限状态机实现
- 4.3源码
1.概念
有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。(来自维基百科)
2.什么是计算
在讲有限状态机之前,我们先了解一下什么是计算。计算,简单来说就是有一个问题,然后给出答案。比如知道边长,求正方形的面积;又比如知道两个点和已知路径,求最短路径等等,有无穷无尽的问题。
如果用计算机的模型去处理这些问题的话,我们希望是统一的。那么怎么将问题统一化呢?
把所有问题都变成判定问题,即是和否
3.什么是有限状态机
有限状态机,顾名思义,就是在有限个状态之间流转,即FSM的下一个状态和输出是由输入和当前状态决定的。
3.1特性
有限状态机(Finite-state machine)有三个特征:
- 状态总数(state)是有限的。
- 任一时刻,只处在一种状态之中。
- 某种条件下,会从一种状态转变(transition)到另一种状态。
3.2为什么要用状态机
其实我们在编程时实现相关业务逻辑时经常需要处理各种事件和状态切换,写各种switch/case 和if/else ,所以我们其实可能一直都在跟有限状态机打交道,只是可能没有意识到。现在假如有个订单系统,目前订单上的状态由待支付、已支付、已退款3个状态,这个时候我们可以简单地通过switch/case 和if/else进行实现,但是实际的一个成熟的订单系统是极其复杂的,包括但不限于
- 订单状态多
- 一个订单包含很多个子订单
- 订单流转的操作很多都是公共的
- 随着业务发展,也会时不时地多出几个状态等等,
那么这个时候再用if/else实现,就会导致代码耦合严重、分支多、重复代码多等问题,而且随着状态的增加,对原有代码逻辑的修改就会变的非常困难,这个时候就需要有限状态机来处理了。
所以在处理一些业务逻辑比较复杂的需求时,可以先看看是否适合用一个有限状态机来描述,如果可以把业务模型抽象成一个有限状态机,那么代码就会逻辑特别清晰,结构特别规整。
4.实战
4.1字符串转换整数
这是力扣的一道题,题目描述:
请你来实现一个 myAtoi(string s)
函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi
函数)。
函数 myAtoi(string s)
的算法如下:
- 读入字符串并丢弃无用的前导空格
- 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
- 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
- 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为
0
。必要时更改符号(从步骤 2 开始)。 - 如果整数数超过 32 位有符号整数范围
[−231, 231 − 1]
,需要截断这个整数,使其保持在这个范围内。具体来说,小于−231
的整数应该被固定为−231
,大于231 − 1
的整数应该被固定为231 − 1
。
4.2用有限状态机实现
有限状态机就是通过一个个的输入作为触发器,将当前状态流转到下个状态。针对该题,可以用有限状态机表示
我们的程序在每个时刻有一个状态 s,每次从序列中输入一个字符 c,并根据字符 c 转移到下一个状态 s’。这样,我们只需要建立一个覆盖所有情况的从 s 与 c 映射到 s’ 的表格即可解决题目中的问题。
用表格表示是这样的
’ ’ | +/- | number | other | |
---|---|---|---|---|
start | start | signed | in_number | end |
signed | end | end | in_number | end |
in_number | end | end | in_number | end |
end | end | end | end | end |
4.3源码
class Solution {
public int myAtoi(String s) {
Automaton automaton = new Automaton();
for (int i=0; i<s.length(); i++) {
automaton.get(s.charAt(i));
if (automaton.isEnd) {
return automaton.ans * automaton.sign;
}
}
return automaton.ans * automaton.sign;
}
public static class Automaton {
public Boolean isEnd = false;
public int ans = 0;
public String state = "start";
public int sign = 1;
public Map<String, String[]> table = new HashMap<String, String[]>() {{
put("start", new String[]{"start", "signed", "in_number", "end"});
put("signed", new String[]{"end", "end", "in_number", "end"});
put("in_number", new String[]{"end", "end", "in_number", "end"});
put("end", new String[]{"end", "end", "end", "end"});
}};
//该方法即为字符输入的触发
public void get(char c) {
state = table.get(state)[getCol(c)];
if("end".equals(state)) {
isEnd = true;
} else if ("signed".equals(state)) {
sign = c == '+'? 1 : -1;
} else if ("in_number".equals(state)) {
if (sign > 0) {
// ans = ans * 10 + c - '0';
if (ans > Integer.MAX_VALUE/10 || (ans == Integer.MAX_VALUE/10 && c - '0' > Integer.MAX_VALUE%10)) {
ans = Integer.MAX_VALUE;
isEnd = true;
return;
}
} else {
if (-ans < Integer.MIN_VALUE/10 || (-ans == Integer.MIN_VALUE/10 && '0' - c < Integer.MIN_VALUE%10)) {
ans = Integer.MIN_VALUE;
isEnd = true;
return;
}
}
ans = ans * 10 + c - '0';
}
}
public int getCol(char c) {
if (c == ' ') {
return 0;
}
if (c == '+' || c == '-') {
return 1;
}
if (Character.isDigit(c)) {
return 2;
}
return 3;
}
}
}
https://blog.lerzen.com/post/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BAfsm%E7%9A%84%E7%AE%80%E4%BB%8B%E4%B8%8Edemo/
https://zhuanlan.zhihu.com/p/46347732
https://www.bilibili.com/video/BV1Yi4y1t7J3?p=2&spm_id_from=pageDriver&vd_source=e2283db23f05299cbaebf6984607a0e6