zoukankan      html  css  js  c++  java
  • 有限状态机FSM详解及其实现

    有限状态机,也称为FSM(Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态。当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态。任何一个FSM都可以用状态转换图来描述,图中的节点表示FSM中的一个状态,有向加权边表示输入字符时状态的变化。如果图中不存在与当前状态与输入字符对应的有向边,则FSM将进入“消亡状态(Doom State)”,此后FSM将一直保持“消亡状态”。状态转换图中还有两个特殊状态:状态1称为“起始状态”,表示FSM的初始状态。状态6称为“结束状态”,表示成功识别了所输入的字符序列。

    在启动一个FSM时,首先必须将FSM置于“起始状态”,然后输入一系列字符,最终,FSM会到达“结束状态”或者“消亡状态”。

     

    说明:

    在通常的FSM模型中,一般还存在一个“接受状态”,并且FSM可以从“接受状态”转换到另一个状态,只有在识别最后一个字符后,才会根据最终状态来决定是否接受所输入的字符串。此外,也可以将“其实状态”也作为接受状态,因此空的输入序列也是可以接受的。

    FSM的实现

    程序设计思路大致如下:

    • 使用状态转换图描述FSM
    • 状态转换图中的结点对应不同的状态对象
    • 每个状态对象通过一个输入字符转换到另一个状态上,或者保持原状态不变。

    通过输入字符从一个状态切换到另一个状态的过程,我们称之为一个映射。在计算机程序设计中,我们可以有两种表示映射的方法:

    • 通过算法表示,即“可执行代码(Executable Code)”方式
    • 通过一张映射表,即“被动数据(Passive Data)”方式

    如下详细介绍这两种实现方式:

    • 通过Executable Code实现映射的FSM

    这种方式主要是通过条件分支来处理不同的字符,如if或者switch语句块,如

    复制代码
     1 State* State1::Transition(char c)
     2 {
     3     switch(c)
     4     {
     5     case 'A':
     6         return &s2;
     7     case 'B':
     8         return &s3;
     9     case 'C':
    10         return &s4;
    11     case 'D':
    12         return &s5;
    13     case '':
    14         return NULL;
    15     default:
    16         return NULL;
    17     }
    18 }
    复制代码
     View Code
    •  通过Passive Data实现映射的FSM

    在如上的switch分支中,其使用类型大致相同,因此,我们可以考虑将相似的信息保存到一张表中,这样就可以在程序中避免很多函数调用。在每个状态中都使用一张转换表来表示映射关系,转换表的索引使用输入字符来表示。此外,由于通过转换表就可以描述不同状态之间的变化,那么就没有必要将每种状态定义为一个类了,即不需要多余的继承和虚函数了,仅使用一个State即可。

    复制代码
    #include <limits.h>
    
    class State
    {
    public:
        State();
        State* transition[range];
    };
    
    对于任意一个状态state和输入字符c,后续状态都可以通过state.transition[c]来确定。
    
    类Fsm中的成员state包含6个状态,为了对应方便,我们将结束状态放在state[0]中,每个状态都使用一个三元组 { 当前状态,输入字符,下一个状态 } 来表示:
    
    struct TransGraph   // use triple to describe map
    {
        int current_state;
        char input_char;
        int next_state;
    };
    复制代码

    如此,使用了转换表代替了虚函数,简化了程序的设计。

    复制代码
      1 // fsm_with_passive_data.h
      2 #ifndef FSM_WITH_PASSIVE_DATA_H
      3 #define FSM_WITH_PASSIVE_DATA_H
      4 
      5 #include <string.h>
      6 #include <limits.h>     // CHAR_MAX
      7 
      8 const int range = CHAR_MAX + 1;
      9 
     10 class State
     11 {
     12 public:
     13     State();
     14     State* transition[range];
     15 };
     16 
     17 struct TransGraph   // use triple to describe map
     18 {
     19     int current_state;
     20     char input_char;
     21     int next_state;
     22 };
     23 
     24 class Fsm
     25 {
     26 public:
     27     Fsm();
     28     void Reset();            // move to start state
     29     void Advance(char c);    // advance one transition
     30     int EndState();
     31     int DoomState();
     32 
     33 private:
     34     State* p_current;   // &s1, &s2, ..., &s6; NULL ==> doom
     35     State state[6];     // 6 states, state[0] is end state
     36 };
     37 
     38 
     39 #endif // FSM_WITH_PASSIVE_DATA_H
     40 
     41 // fsm_with_passive_data.cc
     42 #include "fsm_with_passive_data.h"
     43 
     44 State::State()
     45 {
     46     for (int i = 0; i < range; ++i)
     47         transition[i] = NULL;
     48 }
     49 
     50 Fsm::Fsm()
     51 {
     52     static TransGraph graph[] =
     53     {
     54         {1, 'A', 2}, {1, 'B', 3}, {1, 'C', 4}, {1, 'D', 5},
     55         {2, 'E', 2}, {2, 'I', 0},
     56         {3, 'F', 3}, {3, 'J', 0}, {3, 'M', 4},
     57         {4, 'G', 4}, {4, 'K', 0},
     58         {5, 'H', 5}, {5, 'L', 0}, {5, 'O', 2}, {5, 'N', 4},
     59         {0, 0, 0}
     60     };
     61 
     62     for (TransGraph* p_tg = graph; p_tg->current_state != 0; ++p_tg)
     63         state[p_tg->current_state].transition[p_tg->input_char] = &state[p_tg->next_state];
     64 
     65     p_current = NULL;
     66 }
     67 
     68 void Fsm::Reset()
     69 {
     70     p_current = &state[1];
     71 }
     72 
     73 void Fsm::Advance(char c)
     74 {
     75     if (p_current != NULL)
     76         p_current = p_current->transition[c];
     77 }
     78 
     79 int Fsm::EndState()
     80 {
     81     return p_current == &state[0];
     82 }
     83 
     84 int Fsm::DoomState()
     85 {
     86     return p_current == NULL;
     87 }
     88 
     89 // test_with_passive_data.cc
     90 #include "fsm_with_passive_data.h"
     91 
     92 #include "stdio.h"  // printf, scanf
     93 #include "stdlib.h" // system
     94 
     95 void test_fsm()
     96 {
     97     char input_string[80];
     98     printf("Enter input expression: ");
     99     scanf("%s", input_string);
    100 
    101     Fsm fsm;
    102     fsm.Reset();
    103     int index = 0;
    104     fsm.Advance(input_string[index++]);
    105 
    106     while (!fsm.EndState() && !fsm.DoomState())
    107         fsm.Advance(input_string[index++]);
    108 
    109     if (fsm.EndState())
    110         printf("
    Valid input expression");
    111     else
    112         printf("
    Invalid input expression");
    113 }
    114 
    115 
    116 int main()
    117 {
    118     test_fsm();
    119 
    120     system("pause");
    121 }
    复制代码

    通用FSM的设计

    如果类Fsm可以表示任意类型的FSM,那么就更符合程序设计的要求了。在构造函数中执行的具体配置应该被泛化为一种机制,我们通过这种机制来建立任意的FSM。在Fsm的构造函数中,应该将转换表作为一个参数传入,而非包含具体的转换表,如此,则不需要将转换表的大小硬编码到Fsm中了。因此,在构造函数中必须动态地创建这个存放转换表的内存空间,在析构函数中记着销毁这块内存。

    复制代码
     1 class Fsm
     2 {
     3 public:
     4     Fsm(TransGraph* p_tg);
     5     virtual ~Fsm();
     6     void Reset();
     7     void Advance(char c);
     8     int EndState();
     9     int DoomState();
    10 
    11 private:
    12     State* p_current;
    13     State* p_state;
    14 };
    15 
    16 Fsm::Fsm(TransGraph* p_tg)
    17 {
    18     int max_state = 0;  // size for dynamically allocated graph
    19     for (TransGraph* p_temp = p_tg; p_temp->current_state != 0; ++p_temp)
    20     {
    21         if (p_temp->current_state > max_state)
    22             max_state = p_temp->current_state;
    23         if (p_temp->next_state > max_state)
    24             max_state = p_temp->next_state;
    25     }
    26 
    27     p_state = new State[max_state + 1];
    28     for (TransGraph* p_temp = p_tg; p_temp->current_state != 0; ++p_temp)
    29         p_state[p_temp->current_state].transition[p_temp->input_char] = &p_state[p_temp->next_state];
    30 
    31     p_current = NULL;
    32 }
    33 
    34 Fsm::~Fsm()
    35 {
    36     delete []p_state;
    37 }
    复制代码
     View Code

    当然也可以将上述程序中的转换表不放在主程序中,而是由一个派生自Fsm的子类SpecificFsm提供,在SpecificFsm中设置具体的转换表,然后通过SpecificFsm的初始化列表传到基类Fsm中,这样在主程序中就可以使用SpecificFsm来进行操作了。

     
     
  • 相关阅读:
    前缀和
    B. Ilya and Queries
    BZOJ1652 [Usaco2006 Feb]Treats for the Cows
    NOIP2014提高组 酱油记
    NOIP初赛 BLESS ALL!
    BZOJ1096 [ZJOI2007]仓库建设
    BZOJ1036 [ZJOI2008]树的统计Count
    BZOJ1030 [JSOI2007]文本生成器
    BZOJ2749 [HAOI2012]外星人
    BZOJ1093 [ZJOI2007]最大半连通子图
  • 原文地址:https://www.cnblogs.com/williamjie/p/9504092.html
Copyright © 2011-2022 走看看