1 什么是有限状态自动机
1.1什么是计算
维基百科定义:计算(英语:Calculation)是一种将“单一或多个的输入值”转换为“单一或多个的结果”的一种思考过程。可以简单理解为给出一个问题得到一个答案的过程。如下图所示日常生活比较常见计算问题:
1.2 判定问题
维基百科定义:在可计算性理论与计算复杂性理论中,决定性问题,亦称判定问题,(英语:Decision problem)是一个在某些形式系统回答“是”或“否”的问题。
在可计算理论中,会把相关计算问题最终转化为判定问题,如下图所示,比如求正方形面积如何转成判定问题,边长为4的正方向面积,边长为4面积是1、2、3…输入到判定器去判断(可计算理论中不考虑性能)最终16时候判定器会接受。
1.3 有限状态自动机
维基百科给出的定义:有限状态机(finite state machine, FSM)又称为有限状态自动机(finite state automaton,FSA)简称状态机,是表示有限个状态以及在这些状态之间的转移和动作作为行为的数学计算模型。
从定义可以FSM是一个计算模型;它的状态是有限的(可枚举的),自动机是这个状态之间的转移行为,最后的结果判断这一系列行为是否符可接受要求。
有限状态自动机的数学模型,五元组M=(S,I,f,A,S0),其中
● S是一个有限的状态集合
● I是一个有限输入符号结合
● f表示状态的转换是从SXI到S的函数
● A表示接受状态非空集合A包含于S
● S0表示初始状态,S0属于S
起初,这个自动机处于「初始状态」。随后,它顺序地读取字符串中的每一个字符,并根据当前状态和读入的字符,按照某个事先约定好的「转移规则」,从当前状态转移到下一个状态;当状态转移完成后,它就读取下一个字符。当字符串全部读取完毕后,如果自动机处于某个「接受状态」,则判定该字符串「被接受」;否则,判定该字符串「被拒绝」。
例子:给定一个字符串str=“111011010010”,通过构建一个有限状态自动机,来判定是否满足偶数个1。
● 有限状态有两个:奇数个1和偶数个1,即:S={EVEN_ONE,ODD_ONE};
● I={“111011010010”}
● 状态转移函数,和当前状态S和输入有关;转移函数如下
class Solution {
private static Map<State, Map<Character, State>> transferMap = new HashMap<>();
static {
Map<Character, State> evenOneMap = new HashMap<>();
evenOneMap.put('1', State.ODD_ONE);
evenOneMap.put('0', State.EVEN_ONE);
transferMap.put(State.EVEN_ONE, evenOneMap);
Map<Character, State> oddOneMap = new HashMap<>();
oddOneMap.put('1', State.EVEN_ONE);
oddOneMap.put('0', State.ODD_ONE);
transferMap.put(State.ODD_ONE, oddOneMap);
}
//状态
enum State {
EVEN_ONE,//偶数个1,可接受状态
ODD_ONE//奇数个1
}
public boolean recognize(String input) {
if (input == null) {
return true;
}
//初始状态 0个1也是偶数个1
State currentState = State.EVEN_ONE;
for (int i = 0; i < input.length(); i++) {
currentState = transfer(input.charAt(i), currentState);
//如果存在转移不了状态直接终止
if (currentState == null) {
break;
}
}
return currentState == State.EVEN_ONE;
}
private State transfer(char currentChar, State currentState) {
return transferMap.get(currentState).get(currentChar);
}
}
2 代码实战
https://leetcode.cn/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/description/
class Solution {
private static Map<State, Map<CharType, State>> transferMap = new HashMap<>();
private static Set<State> acceptStateSet=new HashSet<>();
static {
//
Map<CharType, State> initialMap = new HashMap<>();
initialMap.put(CharType.SPACE, State.INITIAL);
initialMap.put(CharType.SIGN, State.SIGN);
initialMap.put(CharType.NUMBER, State.INTEGER);
initialMap.put(CharType.POINT, State.POINT_WITHOUT_INT);
transferMap.put(State.INITIAL, initialMap);
Map<CharType, State> signMap = new HashMap<>();
signMap.put(CharType.NUMBER, State.INTEGER);
signMap.put(CharType.POINT, State.POINT_WITHOUT_INT);
transferMap.put(State.SIGN, signMap);
Map<CharType, State> integerMap = new HashMap<>();
integerMap.put(CharType.NUMBER, State.INTEGER);
integerMap.put(CharType.POINT, State.POINT_HAS_INT);
integerMap.put(CharType.EXP, State.EXP);
integerMap.put(CharType.SPACE, State.END);
transferMap.put(State.INTEGER, integerMap);
Map<CharType, State> pointHasIntMap = new HashMap<>();
pointHasIntMap.put(CharType.NUMBER, State.FRACTION);
pointHasIntMap.put(CharType.EXP, State.EXP);
pointHasIntMap.put(CharType.SPACE, State.END);
transferMap.put(State.POINT_HAS_INT, pointHasIntMap);
Map<CharType, State> pointWithoutIntMap = new HashMap<>();
pointWithoutIntMap.put(CharType.NUMBER, State.FRACTION);
transferMap.put(State.POINT_WITHOUT_INT, pointWithoutIntMap);
Map<CharType, State> fractionMap = new HashMap<>();
fractionMap.put(CharType.NUMBER, State.FRACTION);
fractionMap.put(CharType.EXP, State.EXP);
fractionMap.put(CharType.SPACE, State.END);
transferMap.put(State.FRACTION, fractionMap);
Map<CharType, State> expMap = new HashMap<>();
expMap.put(CharType.NUMBER, State.EXP_NUMBER);
expMap.put(CharType.SIGN, State.EXP_SIGN);
transferMap.put(State.EXP, expMap);
Map<CharType, State> expSignMap = new HashMap<>();
expSignMap.put(CharType.NUMBER, State.EXP_NUMBER);
transferMap.put(State.EXP_SIGN, expSignMap);
Map<CharType, State> expNumberMap = new HashMap<>();
expNumberMap.put(CharType.NUMBER, State.EXP_NUMBER);
expNumberMap.put(CharType.SPACE, State.END);
transferMap.put(State.EXP_NUMBER, expNumberMap);
Map<CharType, State> endMap = new HashMap<>();
endMap.put(CharType.SPACE, State.END);
transferMap.put(State.END, endMap);
acceptStateSet.add(State.INTEGER);
acceptStateSet.add(State.END);
acceptStateSet.add(State.EXP_NUMBER);
acceptStateSet.add(State.FRACTION);
acceptStateSet.add(State.POINT_HAS_INT);
}
enum State {
INITIAL, //初始化,
SIGN,//符号位
INTEGER,//整数部分--可接受状态
POINT_HAS_INT,//小数点左侧有整数如:2. --可接受状态
POINT_WITHOUT_INT,//小数点左侧有无整数如:.3
FRACTION,//小数部分 ---可接受状态
EXP,//指数E,e
EXP_SIGN,//指数符号位
EXP_NUMBER,//指数后面整数----可接受状态
END//末尾空格----可接受状态
}
enum CharType {
NUMBER, //数字
EXP,//指数E或者e
POINT,//小数点
SIGN,//符号
SPACE,//空格
ILLEGAL//非法
}
public boolean isNumber(String input) {
if (input == null) {
return true;
}
State currentState = State.INITIAL;
for (int i = 0; i < input.length(); i++) {
currentState = transfer(input.charAt(i), currentState);
//如果存在转移不了状态直接终止
if (currentState == null) {
break;
}
}
return acceptStateSet.contains(currentState);
}
//转移函数
private State transfer(char currentChar, State currentState) {
CharType charType = toCharType(currentChar);
return transferMap.get(currentState).get(charType);
}
private CharType toCharType(char currentChar) {
if (currentChar >= '0' && currentChar <= '9') {
return CharType.NUMBER;
} else if (currentChar == 'E' || currentChar == 'e') {
return CharType.EXP;
} else if (currentChar == '.') {
return CharType.POINT;
} else if (currentChar == ' ') {
return CharType.SPACE;
} else if (currentChar == '+' || currentChar == '-') {
return CharType.SIGN;
}
return CharType.ILLEGAL;
}
}
3 对实际工作的启发
状态机是由事件、状态、动作三大部分组成。三者的关系是:事件触发状态的转移,状态的转移触发后续动作的执行。其中动作不是必须的,也可以只进行状态转移,不进行任何操作。如下图所示,可以受状态机启发对营销活动状态流转进行改造。
参考文献
[1] 万门大学,有限状态自动机课程