八數碼問題:在3×3的方格棋盤上,擺放著1到8這八個數碼,有1個方格是空的,其初始狀態如圖1所示,要求對空格執行空格左移、空格右移、空格上移和空格下移這四個操作使得棋盤從初始狀態到目標狀態。
(a)初始狀態 (b
)目標狀態
a* 演算法實際是一種啟發式搜尋,所謂啟發式搜尋,就是利用乙個估價函式評估每次決策的價值,決定先嘗試哪一種方案,這樣可以極大的優化普通的廣度優先搜尋。
a*演算法是乙個可採納的最好優先演算法。a*演算法的估價函式可表示為:
f'(n) = g'(n) + h'(n)
這裡,f'(n)是估價函式,g'(n)是起點到終點的最短路徑值,h'(n)是n到目標的最斷路經的啟發值。由於這個f'(n)其實是無法預先知道的,所以我們用前面的估價函式f(n)做近似。g(n)代替g'(n),但g(n)>=g'(n)才可(大多數情況下都是滿足的,可以不用考慮),h(n)代替h'(n),但h(n)<=h'(n)才可。可以證明應用這樣的估價函式是可以找到最短路徑的,也就是可採納的。
貌似其經常用在遊戲中?
node狀態結點:
1.儲存每個搜尋得到的狀態,所以需要有儲存狀態的方式。
int chest[g_n][g_n];
2.明顯需要指向前乙個狀態節點的指標,以及指向自己後續狀態的指標。
node *prior; //父節點指標
vectorchilds;
3.由於採用啟發式搜尋,所以還需要儲存自身的估價值。
int h_value,g_value,f_value;
open表:
用於存放待擴充套件的結點。其中節點的排列順序是依照估價函式排列,每次取出估價最好的結點擴充套件(類似優先佇列)。如果可以由初始狀態到達目標狀態,那麼一定可以在open表內結點取完前搜尋到,否則當前初始態無法達到目標狀態。
closed表:
用於儲存已經搜尋過的結點。可以直接使用線性表來模擬。最優結果搜尋路徑應該隱含在這裡。
void fun()
while(tmp可以生成不同的child) // end_of_while(tmp可以生成不同的child)
//tmp擴充套件完成,壓入closed結點列表
closed.push(tmp);
} //end_of_while(當openlist不為空)
}// end_of_fun()
此例中設定了2種啟發函式。
#include #include #include using namespace std;
const short g_n = 3; //方形數碼問題每行元素個數
class node
void inherit(node* ff)
} prior = ff;
} void hn2(node *target)
} h_value = num;
} void hn(node *target)
} distance;
//所有點距離目標位置和作為h_value
int num = 0;
for (int i = 0; i < g_n; i++)
} h_value = num;
} //n到目標的最短路徑的啟發值
void gn()
//fn估價函式
void fn()
void show()
cout << endl;
} }};bool is_same_node(node *a, node *b)
} }return true;
}class openlist else
} void delete_one(node *one) else
one->next_open = one->prior_open = null;
} node* front()
tmp = tmp->next_open;
} return min;
} bool has_child(node *child,node *&who)
tmp = tmp->next_open;
} who = null;
return false;
} bool empty()
};node target;
//priority_queue,cmp> open; //open表,儲存等待擴充套件的結點
vectorclosed; //closed表,儲存訪問過的節點
openlist open_list; //用於訪問open結點
bool is_on_closed(node *child,node * &who,int &who_pos)
} who_pos = -1;
who = null;
return false;
}bool is_target(node*target,node* one)
bool is_same_as_parent(node *one)
tmp = tmp->prior;
} return false;
}void calculate_g_to_update_open(node *source,node* child,node *who)
source->childs.push_back(who);
delete child;
}void calculate_g_to_update_closed(node *source,node* child,node *who,int who_pos_on_closed)
}void check_child(node *source,node *child)
}void creat_child(node *source)
}swap;
//向左交換
if(source->y>0)
//向右交換
if(source->yinherit(source);
swap(child->chest[child->x][child->y],
child->chest[child->x][child->y + 1]);
child->x = source->x;
child->y = source->y + 1;
check_child(source, child);
} //向上交換
if(source->x>0)
//向下交換
if(source->xinherit(source);
swap(child->chest[child->x][child->y],
child->chest[child->x + 1][child->y]);
child->x = source->x + 1;
child->y = source->y;
check_child(source, child); }}
void show_path(node *one)
}void search_astar(node *target, node *init_node)
} }a->hn(&target);
a->gn();
a->fn();
}int main()
; int b = ;
init_node_status(&target, b);
init_node_status(&init_node, a);
target.show();
search_astar(&target, &init_node);
cout << "waitint for input" << endl;
cin.get();
return 0;
}
明顯由上可知,a*演算法類似於優先佇列搜尋,但與之不同的是估值函式的設定,這也就是a*與優先佇列搜尋最大不同的地方,所以a*演算法可以認為是優先佇列搜尋。
演算法難點在於設定合適的啟發式函式與評價估計函式。
以及由上可知我們並未利用節點的孩子節點。因為我再次偷懶。實際的高效做法是在檢測孩子節點時,應該在第2步檢測中遞迴修改其孩子節點的估價值。(此時明顯會從closed追蹤修改到open表中)由此open表需要再排序(注意使用stl的侷限性)。
其次,這樣第1步檢測中若child_in_open的父指標被更改,則應先將其從其原父節點的孩子列表中刪除以不影響第二步。
ps:但是這裡注意不是八數碼問題所有狀態可達某一狀態,需要通過分析其初始狀態的逆序數問題來判斷是否可達目標態。
ps的ps:
在除錯階段,間斷想了幾天的bug,就是想不到問題在哪。直到前不久突然發現自己的鍊錶在出節點後(此處應逐步跟蹤,不過我在深層搜尋時一般懶得逐步,選擇人肉debug),彈出的節點的前後指標未賦空,在這個演算法過程中會導致死迴圈的出現。所以不僅需要注意主要邏輯,還要把簡單的部分寫完整,不然debug起來真的很累人。
A 演算法解決八數碼問題(C 版本)
八數碼問題也稱為九宮問題。在3 3的棋盤,擺有八個棋子,每個棋子上標有1至8的某一數字,不同棋子上標的數字不相同。棋盤上還有乙個空格,與空格相鄰的棋子可以移到空格中。要求解決的問題是 給出乙個初始狀態和乙個目標狀態,找出一種從初始轉變成目標狀態的移動棋子步數最少的移動步驟。關鍵之處 要維護兩個結構 ...
A A 演算法解決八數碼問題(C 實現)
1.狀態圖搜尋 1.1搜尋樹 搜尋過程中經過的節點和邊按原圖的連線關係構成乙個樹型的有向圖,稱為搜尋樹。1.2搜尋方式 樹式搜尋 記錄搜尋過程中所經過的所有節點和邊 1.3路徑的獲得 樹式搜尋 反向求解 2.搜尋演算法 2.1 closed表和open表 closed表對樹式搜尋來說儲存的是正在成長...
八數碼問題的A 演算法求解
a 演算法是啟發式搜素演算法中較為出名和高效的演算法之一,其關鍵是對於啟發式函式的實際,啟發式函式h x 需要盡可能的接近實際的h x h x 下面是人工智慧八數碼問題使用a 演算法求解的原始碼放在部落格上記錄一下。程式使用放錯位置的棋子的個數作為啟發式函式。include include incl...