人工智能老師要求實現一個算法,剛開始的時候本身作的是一個深度優先搜索,而後又學了A*,以爲深度優先效率仍是不行,就打算用A*算法實現兔子找蘿蔔的小遊戲。算法
地圖設計採用的是坦克大戰中的地圖設計圖案。編程
這是用C語言寫的,編譯器是VC6.0windows
須要源程序代碼參考的能夠在個人csdn下載資源中找到。數據結構
附上下載地址:http://download.csdn.net/detail/yujin753/7060747
框架
迷宮問題能夠表述爲:一個二維的網格,0表示點可走,1表示點不能夠走,點用(x,y)表示,尋找從某一個給定的起始單元格出發, 經由行相鄰或列相鄰的單元格(能夠經過的),最終能夠到達目標單元格的、所走過的單元格序列。在任一個單元格中,都只能看到與它鄰近的4個單元格(若是位於底邊,則只有3個;位於4個角上,則只有2個是否能經過。迷宮問題用傳統的廣度優先搜索或帶回溯的深度優先搜索等算法都能很好地解決。固然採用A*算法更能體現對人工智能這門的理解,比起盲目的搜索,A*算法更具備針對性。編程語言
所謂「深度優先」,即:狀態樹的生長或展開,首先沿狀態樹的深度方向進行。深度優先搜索算法須要記錄下狀態樹的生長過程。深度優先搜索算法是一種盲目的搜索算法,搜索中可能不少次的搜索到目標點,深度搜索算法經過不斷剪枝,尋找出一條從起始點到目標點最近且最省時的路徑,即深度優先搜索算法也是一種全局的搜索算法,這樣的深度優先搜索算法運用到未知迷宮搜救中是沒有意義的。而本次實驗中,深度優先搜索算法是以找到目標物爲目的,沒有剪枝的步驟,只要搜索到目標物,迷宮的搜索過程就成功退出。編輯器
深度優先搜索的一些特色:函數
a) 通常不能保證找到最優解測試
b) 當深度限制不合理時,可能找不到解,能夠將算法改成可變深度限制人工智能
c) 最壞狀況時,搜索空間等同於窮舉
d) 是一個通用的與問題無關的方法
圖一 深度優先搜索
深度優先僞碼:
1) Push(S,Open) 將初始節點放入Open表中;
2) Judge(S) 判斷S節點是否是目標節點,if(S爲目標節點),那麼找到目標節點;
3) While(判斷Open表是否爲空)
若是爲空,那麼搜索失敗 end;
不然把第一個節點N從Open表中移至Close表中;
4) 將N的後繼節點放入Open表中;
判斷後繼節點中是否有目標節點;
If(存在目標節點)
成功;
Else
返回3);
A*算法是人工智能中的一種搜索算法,是一種啓發式搜索算法,它不需遍歷全部節點,只是利用包含問題啓發式信息的評價函數對節點進行排序(Node Ordering),使搜索方向朝着最有可能找到目標併產生最優解的方向。它的獨特之處是檢查最短路徑中每一個可能的節點時引入了全局信息,對當前節點距終點的距離作出估計,並做爲評價節點處於最短路線上的可能性的度量。
I.啓發函數的肯定
A*算法中引入了評估函數,評估函數以下:
f(n)=g(n)+h(n)
其中:n是搜索中遇到的任意狀態。g(n)是從起始狀態到n的代價。h(n)是對n到目標狀態代價的啓發式估計。即評估函數f ( n) 是從初始節點到達節點n 處已經付出的代價與節點n 到達目標節點的接近程度估價值的總和[10]。
這裏咱們定義n點到目標點的最小實際距離爲h(n)*,A*算法要知足的條件爲:
h(n)<=h(n)*
由前面的迷宮問題的描述咱們能夠知道,迷宮走的時候只能往上下左右走,每走一步,代價爲1,這裏咱們採用的估價函數爲當前節點到目標節點的曼哈頓距離,即:
h(n)=|end.x – n.x|+ |end.y – n.y|[11]
這裏end表示迷宮的目標點,n表示當前點,很明顯這裏h(n)<=h(n)*。
g(n)容易表示,即每走一步的代價是1,因此利用f(n)=g(n)+h(n)這種策略,咱們能夠不斷地逼近目標點,從而找到問題的解。
II A*的主要實現步驟的僞碼錶示
1) OPEN:=(s), f(s):=g(s)+h(s);
2)LOOP: IF OPEN=( ) THEN EXIT(FAIL);
3) n:=FIRST(OPEN);
4)IF GOAL(n) THEN EXIT(SUCCESS);
5)REMOVE(n, OPEN), ADD(n, CLOSED);
6)EXPAND(n) →{mi},
計算f(n, mi):=g(n, mi)+h(mi);
ADD(mj, OPEN), 標記mj到n的指針;
IF f(n, mk)<f(mk) THEN f(mk):=f(n, mk),
標記mk到n的指針;
IF f(n, ml)<f(ml,) THEN f(ml):=f(n, ml),
標記ml到n的指針,
ADD(ml, OPEN);
7, OPEN中的節點按f值從小到大排序;
8, GO LOOP;
這裏OPEN表和CLOSED表分別用來存儲要搜索的狀態結點和存儲已經訪問過的狀態。
該測試程序在VC6.0環境下編譯實現,編程語言用的是c語言。
struct mark //定義迷宮內點的座標類型
{
int x;
int y;
};
struct mark start,end; //start,end入口和出口的座標
struct Element //棧元素,
{ int f;//估價函數
int g;//代價
int h;//啓發函數
int x,y; //x行,y列
};
Element elem,e;
typedef struct LStack //鏈棧
{ Element elem;
struct LStack *next;
}*PLStack;
PLStack Open,Close;//OPEN表和CLOSE表用來儲存鏈節點
InitStack(Open); //用於初始化open 表
InitStack(Close); //用於初始化close表
Push(Open,elem); //表示初始節點elem進入open表
while(!StackEmpty(Open)&&Flag==0) //棧不爲空 有路徑可走
{
Pop(Open,elem); //將open表中的當前元素提出來
Push(Close,elem);//將從open表取出來的元素放入close表
if(判斷elem節點的子節點是否能夠走)判斷上下左右{
if(elem_child不爲目標節點){
Push(Open,elem_child) 子節點進Open表,這裏的push()函數進入棧中,爲有序進入,也就是說進棧是按順序插入的。
}//if
else(該節點爲目標節點){
Push(Close,elem_child)//目標節點進close表
Flag=1//將標誌置爲一表示已經找到了目標節點
End//找到了目標節點結束
}//else
}//if
}//while
If(StackEmpty(Open)||Flag==0)
Printf(」迷宮沒有解\n」)
返回迷宮沒有解//表示走到這裏尚未找到解則迷宮走不出去
int InitStack(PLStack &S) //初始化鏈棧,Open表和Close表初始化
{
S=NULL;
return 1;
}
int StackEmpty(PLStack S)//判斷棧是否爲空
{
if(S==NULL)
return 1;
else
return 0;
}
int Push1(PLStack &S, Element e)//壓入新數據元素
{
PLStack p;
PLStack q,t;
p=(PLStack)malloc(sizeof(LStack));
p->elem.g=deep;//表示深度
//估價函數即便用曼哈頓距離表示
p->elem.h=abs(end.x-e.x)+abs(end.y-e.y);
p->elem.f=p->elem.h+p->elem.g;
p->elem.x=e.x;
p->elem.y=e.y;
p->next=NULL;
if(NULL==S||p->elem.f<=S->elem.f)
{
p->next=S;
S=p;
//return 1;
}
else
{
q=S;
t=q->next;
while(t!=NULL)
{
if(p->elem.f>t->elem.f)
{
q=t;
t=t->next;
}
else
break;
}
p->next=t;
q->next=p;
}
return 1;
}
int Pop(PLStack &S,Element &e) //棧頂元素出棧
{
PLStack p;
if(!StackEmpty(S))
{
e=S->elem;
p=S;
S=S->next;
free(p);
return 1;
}
else
return 0;
}
界面設計很是友好、清晰、表達明確、功能集中、操做簡單。
迷宮大小設定爲18*25的,入口和出口的位置分別設爲(1.0)、(16,24),
迷宮狀態用一個二維表存儲,0表示能夠走,即界面中的可行域表示,1表示不能走,即界面中的黃金牆和鉑金牆。可選的地圖有三個,每次只能選擇一個,選擇完地圖以後就能夠畫迷宮了,以後就能夠搜索了。
我採用的WindowsAPI程序設計,整個的界面是利用windows程序設計的窗口設計實現的,地圖的樣子是經過一個地圖編輯器來實現的,整體給人的感受是比較清楚、清晰,也比較新穎。而後把地圖看成圖片畫出來,即每一小格表明的數字,決定了地圖將會畫出什麼顏色的圖片來,當二維表中的數據爲1時,咱們就能夠畫出牆的圖片,當其中數據爲0是咱們就能夠畫出可行域的圖片。經過這樣的方法,咱們能夠比較簡單的實現地圖樣式的變換,可以達到很是不錯的效果,旁邊的選擇地圖按鈕可以幫助咱們選擇咱們想要的地圖,選擇好了以後,運行完程序,而後經過地圖重置能夠實現地圖的刪除,而後能夠再進行地圖的選擇,很是方便的實現了地圖的更換。
調用函數:
HWND hmap;
HDC hm;
hmap = GetDlgItem(hwnd,IDC_MAP);
hm = GetDC(hmap);
將整個窗口的句柄控制,使用函數:
BitBlt(hm,x,y,p_Lsize,p_Hsize,h,0,0,SRCCOPY);
其中hm爲窗口句柄,x,y爲對應位置的座標,p_Lsize,p_Hsize爲圖片的大小,h爲圖片畫的位置,這樣就能夠畫出界面了。
圖二 迷宮界面
從程序運行的結果咱們能夠知道,深度優先搜索和A*搜索兩種搜索策略都實現了。咱們也已經知道,深度優先搜索是一種盲目的搜索,也就是說搜索過程當中並無帶啓發式信息,而A*搜索帶了啓發式信息,在程序運行過程當中,咱們能夠明顯的感知,這兩種算法的區別。
我經過實驗驗證了深度優先搜索和A*搜索在本規模迷宮環境下的狀況,能夠利用遍歷Open表和Close表中的節點,得出所擴展的節點和所走的步數。這裏咱們能夠計算出三個地圖走出迷宮須要走的步數。
下面的數據爲兩種搜索算法從初始節點走到目標節點所走的步數:
表一 深度優先搜索和A*搜索所走步數比較
地圖 |
所走步數 |
|
深度優先搜索 |
A*搜索 |
|
地圖一 |
178 |
90 |
地圖二 |
207 |
87 |
地圖三 |
218 |
82 |
從上表咱們能夠明顯看出來採用啓發式的A*搜索比盲目的深度優先搜索所走的步數更少,也就是說,在迷宮問題中採用A*搜索算法較傳統的深度優先搜索能更快的找到迷宮的目標節點,而且效率提升的很是快。從表中能夠看出,效率提升了兩倍多,這樣在更大規模的迷宮問題中,可以節約更多的時間找到問題的解。