淺談DFS的迭代優化和IDA*
前言:
DFS的迭代優化在於每次控制搜尋的深度,然後搜尋,就可以避免一些不必要的大量搜尋,但要在自己有把握答案在應該淺層時使用,不然可能會適得其反。
IDA*則是基於DFS的一種優化思想。主體是設計一個估值函式valuate(n)以求當前狀態到目標狀態的理想步數,在加上當前狀態的實際步數,如果已經大於某個數,就直接折返。valuate函式求出的值必須滿足小於等於這個狀態到目標狀態的實際值,這樣才能保證在時間優化基礎上保證正確性。
IDA*通常搭配DFS的迭代優化使用,如果實際步數加上理想步數大於maxdep,則返回。
IDA*演算法的難點就在於如果設計估值函式,在保證正確性的同時估值函式越大則程式更快。如果估值函式為0,那麼就是普通的搜尋。例如八數碼的每個狀態的估值函式則為每個數位置和它目標位置距離的和,實際步數不可能比這個理想步數更小,所以既保證了時間的優化,也保證了正確性。
DFS是成指數增加的,設計一個好的估值函式,則會省去很多層無用功,使效率更高。
例題.
一.騎士精神
程式碼:
#include<bits/stdc++.h> #define ll long long using namespace std; int n,m,t,mp[7][7],stx,sty,success; char ch; const int dx[]={0,1,1,-1,-1,2,2,-2,-2}; const int dy[]={0,2,-2,2,-2,1,-1,1,-1}; const int goal[7][7]={ {0,0,0,0,0,0}, {0,1,1,1,1,1}, {0,0,1,1,1,1}, {0,0,0,2,1,1}, {0,0,0,0,0,1}, {0,0,0,0,0,0} }; int vvv() { int cnt=0; for(int i=1;i<=5;i++) for(int j=1;j<=5;j++) if(mp[i][j]!=goal[i][j]) cnt++; return cnt; } int safe(int x,int y) { if(x<1 || x>5 || y<1 || y>5) return 0; return 1; }void A_star(int dep,int x,int y,int maxdep) { if(dep==maxdep) { if(!vvv()) success=1; return ; } for(int i=1;i<=8;i++) { int xx=x+dx[i]; int yy=y+dy[i]; if(!safe(xx,yy)) continue ; swap(mp[x][y],mp[xx][yy]); int eva=vvv(); if(eva+dep<=maxdep) A_star(dep+1,xx,yy,maxdep); swap(mp[x][y],mp[xx][yy]); } } int main() { int T; cin>>T; while(T--) { success=0; for(int i=1;i<=5;i++) for(int j=1;j<=5;j++) { cin>>ch; if(ch=='*') mp[i][j]=2,stx=i,sty=j; else mp[i][j]=ch-'0'; } if(!vvv()) { cout<<0<<endl; continue ; } for(int maxdep=1;maxdep<=15;maxdep++) { A_star(0,stx,sty,maxdep); if(success) { cout<<maxdep<<endl; goto zager; } } cout<<-1<<endl; zager:; } return 0; }
估值函式:因為這些馬是按日字走的(不和八數碼一樣),所以我們乾脆就將估值函式設為,不在目標狀態的馬的數量,這樣顯然是正確的。
題外話:這個goto zager,然後下面有個zager:,這個語法挺方便,背下來!!
二.鐵盤整理
題目描述
在訓練中,一些臂力訓練器材是少不了的,小龍在練習的時候發現舉重器械上的鐵盤放置的非常混亂,並沒有按照從輕到重的順序擺放,這樣非常不利於循序漸進的鍛鍊。他打算利用一個非常省力氣的辦法來整理這些鐵盤,即每次都拿起最上面的若干個圓盤並利用器械的力量上下翻轉,這樣翻轉若干次以後,鐵盤將會按照從小到大的順序排列好。那麼你能不能幫小龍確定,最少翻轉幾次就可以使鐵盤按從小到大排序呢?
例如:下面的鐵盤經過如圖所示的以下幾個步驟的翻轉後變為從小到大排列。
這個題首先要離散化。
估值函式:我們會發現每次翻轉的整體裡的個體差值不變,變得則是將翻轉的頭和沒翻轉部分的頭連在了一起,他們的差值變了,可能歸位了一對,所以我們統計有差值不對的對數,當做我們的估值函式。
最後不要忘了即使所有差值都對了,還要判斷正反,既n和n+1的差值也要正確。
程式碼:
#include<bits/stdc++.h> using namespace std; const int maxn=20; int n,ans; int r[maxn]; int a[maxn]; int fl; int evaluate() { int cnt=0; for(int i=1;i<=n;i++) { if(abs(a[i]-a[i+1])!=1) cnt++; } return cnt; } void dfs(int sum,int pre,int maxdep) { if(fl||sum+evaluate()>maxdep) return ; if(!evaluate()) { fl=1; return ; } for(int i=1;i<=n;i++) { if(i==pre) continue ; reverse(a+1,a+1+i); dfs(sum+1,i,maxdep); reverse(a+1,a+1+i); } } int main() { cin>>n; for(int i=1;i<=n;i++) cin>>r[i],a[i]=r[i]; sort(r+1,r+1+n); for(int i=1;i<=n;i++) a[i]=upper_bound(r+1,r+1+n,a[i])-r-1; a[n+1]=n+1; for(int i=0;;i++) { fl=0; dfs(0,0,i); if(fl) { cout<<i<<endl; break; } } return 0; }
總結:
推出估值函式,你就完成了IDA*的一半,就像推出遞推式,你就完成了DP的一半。