有限狀態機詳解(轉載)
以前總覺得有限狀態機和無限狀態機非常的難理解,原來也就是自己一直沒有一個直觀的認識,今天看到一篇部落格,總算對有限狀態機入門了。一看就懂。
我們知道,一般編寫程式時都要畫出流程圖,按照流程圖結構來程式設計,如果編寫一個比較繁瑣,容易思維混亂的程式時,我們可以利用有限狀態機模型畫出一個狀態轉移圖,這樣便可以利用畫出的邏輯圖來編寫程式,簡潔且不易出錯。
那什麼是有限狀態機是什麼意思呢?百度百科上這樣解釋:
有限狀態機,(英語:Finite-state machine, FSM),又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。
常見的計算機就是使用有限狀態機作為計算模型的:對於記憶體的不同狀態,CPU通過讀取記憶體值進行計算,更新記憶體中的狀態。CPU還通過訊息匯流排接受外部輸入裝置(如鍵盤、滑鼠)的指令,計算後更改記憶體中的狀態,計算結果輸出到外部顯示裝置(如顯示器),以及持久化儲存在硬碟。
電腦遊戲設計中也經常使用有限狀態機模型。以水果忍者遊戲為例,遊戲中水果的狀態是有限狀態,其執行軌跡是由模擬物理運動規律的計算公式運算而成的,一個香蕉拋起來後會按照拋物線執行,其每一幀位置變化都是一個狀態的改變,狀態改變通過計算公式來決定。當然作為遊戲不會僅僅這麼簡單,如果這麼簡單就是動畫了,遊戲還有複雜的人機互動事件,比如用手在螢幕上“切”了水果,水果感知到這個事件後,會按照程式邏輯進入爆炸狀態。
簡單知道其定義是沒有用的,在C程式設計中我們改如何應用呢?
例題1:去除一個字串中連續的空格,即 H__el___lo 變成 H_el_lo;
我們拿到這道題時,可能會想到用getchar()依次取字元,當遇到空格時,繼續下一個字元是否為空格,如果是則刪除,如果不是則繼續;那麼問題來了,如果空格後面還有空格呢?如果還有很多空格呢?如果還有其他要求呢?這樣很容易造成思維混亂,所以這時我們可以用到有限狀態機模型,好像這樣說很模糊啊,該怎麼使用呢?利用這種方式,最後的就是利用好flag,即識別符號;這樣吧,我們來畫一下這個模型:
這樣看好像有點抽象,我來解釋一下:
狀態0 (flag = 1)若當前字元是空格,輸出並跳轉到狀態1(flag = 1),如果是非空格,則列印字元;
狀態1 (flag = 1) 若當前字元為非空格,則輸出並跳轉到狀態0,若是空格,則不列印;
下面幾個例題我儘量寫得清楚;
我們按照這種邏輯來寫程式,看看是不是比較方便,程式碼如下:
#include <stdio.h>
int main(int argc, char *argv[])
{
char flag = 0;
int ch;
while((ch = getchar()) != EOF)
{
switch(flag)
{
case 0:
if(ch == ' ')
flag = 1;
putchar(ch);
break;
case 1:
if(ch == ' ')
continue;
flag = 0;
putchar(ch);
break;
default:
break;
}
}
return 0;
}
程式執行結果如下:
[cpp] view plain copy
fs@ubuntu:~/qiang/char1$ gcc -o test test.c
fs@ubuntu:~/qiang/char1$ ./test
H el lo
H el lo
效果很明顯;
上面的例題還算簡單吧,好像不用這個模型也可以是吧,那好,我們再來一題
例題2:除連續的空格但字串中的連續空格不變,比如H_ _el__"wor___ld"___lo;
大家看看,如果直接寫程式是不是很煩,一會就亂掉了,那我們還用這個模型來編寫,還是先畫圖:
#include <stdio.h>
int main(int argc, char *argv[])
{
char flag = 0;
int ch;
while((ch = getchar()) != EOF){
switch(flag){
case 0:
if(ch == ' ')
flag = 1;
if(ch == '"')
flag = 2;
putchar(ch);
break;
case 1:
if(ch != ' '){
flag = 0;
if(ch == '"')
flag = 2;
putchar(ch);
}
break;
case 2:
if(ch == '"')
flag = 0;
if(ch == '\"')
ch = '"';
putchar(ch);
break;
default:
printf("error!\n");
break;
}
}
return 0;
}
執行結果如下:
[cpp] view plain copy
fs@ubuntu:~/qiang/char1$ ./char3
H el "wor ld" lo
H el "wor ld" lo
來個實際點的問題:
例題3::除單行註釋
示例程式:
/*******this is program*****/
#include <stdio.h>
int main()
{
printf("Hello world!\n");//Hello world!
}
將這段程式中的單行註釋去掉,繼續畫圖:
編寫程式如下:
#include <stdio.h>
int main(void)
{
char flag = 0;
char ch;
while((ch = getchar()) != EOF)
{
switch(flag)
{
case 0:
if(ch == '/')
flag = 1;
else
putchar(ch);
break;
case 1:
if(ch == '/'){
flag = 2;
}
else{
putchar('/');
putchar(ch);
}
break;
case 2:
if(ch == '\n')
{
flag = 0;
putchar(ch);
}
break;
default:
break;
}
}
}
執行命令:
fs@ubuntu:~/qiang/char1$ ./quzhushi1 < test.c >result.c
注意命令中用到的重定向,將test.c檔案重定向到quzhushi1 中,輸出結果重定向到 result.c中
可檢視result.c中:
/*******this is program*****/
#include <stdio.h>
int main()
{
printf("Hello world!\n");//Hello world!
}
單行註釋被刪除。
例題4:去除單行註釋和多行註釋
還是這個測試程式:
/*******this is program*****/
#include <stdio.h>
int main()
{
printf("Hello world!\n");//Hello world!
}
好吧,繼續畫圖,圖上比較抽象,大家可以自己思考一下
這次比較複雜,要有5個識別符號
測試程式碼如下:
#include <stdio.h>
int main()
{
char ch;
int flag = 0;
while((ch = getchar()) != EOF)
{
switch(flag)
{
case 0:
if(ch == '/')
flag = 1;
else
putchar(ch);
break;
case 1:
if(ch == '/')
flag = 2;
else if(ch == '*')
flag = 3;
else
{
flag = 0;
putchar('/');
putchar(ch);
}
break;
case 2:
if(ch == '\n')
{
putchar(ch);
flag = 0;
}
break;
case 3:
if(ch == '*')
flag = 4;
break;
case 4:
if(ch == '/')
flag = 0;
else
flag = 3;
break;
}
}
}
執行命令:
fs@ubuntu:~/qiang/char1$ ./quzhushi < test.c >result.c
執行結果:
/*******this is program*****/
#include <stdio.h>
int main()
{
printf("Hello world!\n");//Hello world!
}
大家可以比較畫的圖與所寫程式,這種方法可以使我們編寫程式時有個很好的思路!