轉載自:http://blog.csdn.net/urecvbnkuhbh_54245df/article/details/5847876
圖的搜索分類:
BFS(廣度優先搜索) 和 DFS(深度優先搜索)
兩個最基本的搜索,一個是按深度進行搜索,另一個是按廣度進行搜索...
記憶化搜索(基於深搜)
就是用一個數組,dp[state] 表示state這個狀態的結果,如果進行深搜時,發現已經得出dp[state]的結果了,就直接 return dp[state];
雙向廣搜
從初始結點和目標結點開始分別作兩次BFS,每次選擇隊列中結點少的一邊進行擴展,並且檢測兩邊是否出現了狀態重合
- //雙向廣搜代碼框架
- struct State { }; //狀態
- queue<State>que[2];
- bool vis[2];
- bool flag;
- void bfs(int d) {
- int size=que[d].size();
- while(size--) {
- //普通單廣轉移新狀態v
- if(vis[d][v]) continue;
- if(vis[d^1][v]) {
- flag=true;
- return;
- }
- //新狀態入隊
- }
- }
- int dbfs() {
- //初始化
- int cnt=0;
- while(true) {
- cnt++;
- if(que[0].size()<que[1].size()) bfs(0);
- else bfs(1);
- if(flag) break;
- }
- return cnt;
- }
二分狀態搜索
多半應用於揹包的搜索,就是給你n<=30個物品,每個物品有一定的價值(Value<=10^9),問你將其分成兩堆,使得兩堆的總價值和差值最小是多少.
如果我們直接dfs搜索的話,複雜度會達到O(2^n).極限複雜度就是 2^30 !!!
我們可以先記錄sum爲所有物品的價值總和,然後先將前15個物品的所有組合狀態用一個hash[state]記錄下來,然後按價值排序.
再對後15個物品枚舉組合狀態,記其組合的價值爲Val,那麼我們在hash[]中二分查找一個狀態state,使得Val+hash[state]最接近sum/2(就是總價值的一半)...然後最小差值就在 O( (2^15) * log2(2^15) ) + O( (2^15)*log2(2^15) ) [前15個狀態排序+後15個狀態二分的複雜度] 的時間內完美解決了...
啓發式搜索
啓發式合併,很好很強大.Nlog(N)的複雜度.
與或樹搜索
如果是一個與節點,那麼其子狀態中如果有一個狀態是false,那麼與節點的值就是false,也就是說子狀態都要是true,它纔是true.
對於一個或節點,只要其子狀態中有一個狀態是true,那麼它就是true,也就是說子狀態都是false,它纔是false.
好比下棋.我想要贏,那麼輪到我下的時候,如果我棋子下在某個位置後,可以保證我能贏,那麼我就能贏.(我就是或節點)
博弈樹搜索(α-β剪枝) (極大極小過程搜索)
例題:給出一個n*n的棋盤(n<=8).
0和1兩個玩家輪流操作,0先
0玩家在棋盤的空位上放置0,1玩家放置1
當棋盤放滿時查詢兩個玩家最大連通塊中棋子的個數
玩家得分爲比對方多的棋子的個數
這就是傳說中的 Alpha-Beta剪枝!!
A*搜索
定義一個估計函數 G = g(x) + h(x) . 其中g(x)爲你實際已經走過的距離,然後其重點就在 h(x) 的選擇,比如迷宮搜索最短路時,可以選擇 h(x)爲到終點的曼哈頓距離.那麼如果用BFS實現,那麼用一個優先隊列,使得每次增廣時選擇G小的先增廣,這樣搜最優解的希望更大一些.如果是DFS的話,假設我們目前已經搜到的最優解爲Ans,那麼我們再搜另一個狀態他的G如果大於Ans,那麼就沒有必要再搜下去了,剪掉...
A*的關鍵就在h(x)的選擇,一般選擇的都是:離目標狀態最少還要花費多少步數.(當然這並不是說這樣是最好的h(x).只是普遍的選擇...).根據不同的需要,你也可以定義一個更強大的h(x)來,讓你的搜索快的飛起來~~~
IDA*搜索
就是從小到大(當然你也可以二分枚舉)不斷限制搜索的深度,然後去做DFS半的A*,當搜到一個解的時候,那就是最優解了(如果是二分的話,還需縮小深度繼續搜).因爲我們是從小到大枚舉的...
h()函數:①曼哈頓距離
②魔方旋轉類,每次如果會改變c個數字,那麼 ( 曼哈頓距離和+(c-1) )/c 作爲估計函數.
反正就是當前狀態到大最終狀態最理想情況下最少需要多少步來當做估計函數h().
哈希方法:
樸素的哈希:將這9個數的排列轉化爲一個int範圍的值,範圍過大
散列表哈希:轉化爲一個int值後對大素數取模,然後線性解決衝突
map哈希: 直接把狀態使用map進行hash,太慢了
字符串哈希:將狀態轉換成字符串,用字符串hash,可能會有衝突
形狀哈希: 特殊點的位置及物體的形狀進行hash. (例如:貪吃蛇,對其蛇頭位置及彎曲形狀進行hash.)
- const int M=1000007;
- unsigned int HASH(char *str)
- {
- int hash=0,i=0;
- while(str[i]) hash=hash*7+(*str++);
- return((hash&0x7FFFFFFF)%M);
- }
如: 1 3 4 5 7 6 8 9 2 其Hash值爲
剪枝技巧:
1.預處理一些狀態.(先搜一半)
2.先對狀態進行排序,然後再搜索,可以以此作爲剪枝的工具.(從大往小搜:容量,邊的度)
3.記憶化搜索
4.從終點擴展到每個點(x,y).記錄距離爲len[x][y],作爲A*的估計函數.
5.縮圖後,再進行搜索.
6.流氓剪枝.(卡節點什麼的)
7.正搜不行,進行反搜.
8.根據最優解應該具有的特性,按順序搜索.
9.狀態壓縮(二進制,三進制,或者哈希不同形態...)
10.是否能將所有數據的目標狀態轉換成一樣.