淺談從搜尋到動歸

lidasu發表於2019-06-09

淺談從搜尋到動歸

搜尋

搜尋的思路其實就是暴力破解(列舉每種可能的情況)
一般用dfs的相對較多,dfs是遞迴的程式碼結構,簡潔明瞭,思路清晰
搜尋的時候會形成一顆搜尋樹,整個搜尋過程相當於在遍歷這個搜尋樹

搜尋樹可通過剪枝來優化,可以理解為把這棵樹上多餘的枝葉剪去,更快的找到所需的答案

這裡依然以01揹包為例

題目概述:在n件物品取出若干件放在空間為c的揹包裡,每件物品的體積為w1 w2...wn,與之相對應的價值為v1 v2...vn,最終使揹包所裝物品的總價值最高

用普通的搜尋來寫,就是在選取每個物品的時候,有兩種選擇 拿or不拿
時間複雜度為O(2^n)

int w[105],v[105];
int n,c,res=0;
void dfs(int idx,int c,int val){
    if(c<0) return;
    if(idx==n){
        res=max(res,val);
        return;
    }
    dfs(idx+1,c-w[idx],val+v[idx]); //拿這個物品
    dfs(idx+1,c,val); //不拿這個物品
}
int main(){
    cin>>c>>n;
    for(int i=0;i<n;++i) cin>>w[i]>>v[i];
    dfs(0,c,0);
    cout<<res;
    return 0;
}

記憶化搜尋

搜尋過程中,往往包含著很多重複的計算
dfs遞迴引數中相同的idx和c,返回值一樣,這樣可以通過一個二維陣列來記錄
遇到相同的idx和c時,直接返回記憶化陣列中之前算好的值即可

int w[105],v[105];
int mem[105][1005]; //記憶化陣列
int n,c,res=0;
int dfs(int idx,int c){
    if(mem[idx][c]!=-1) return mem[idx][c]; //之前已經算好了,直接返回
    if(idx==n) return mem[idx][c]=0;
    int t1=0,t2=0;
    if(c>=w[idx]) t1=dfs(idx+1,c-w[idx])+v[idx]; //拿這個物品
    t2=dfs(idx+1,c); //不拿這個物品
    return mem[idx][c]=max(t1,t2);
}
int main(){
    cin>>c>>n;
    for(int i=0;i<n;++i) cin>>w[i]>>v[i];
    memset(mem,-1,sizeof(mem));
    cout<<dfs(0,c);
    return 0;
}

動態規劃

動態規劃關鍵是在於:狀態轉移
從一個已求解的狀態轉移到一個新的狀態

記憶化搜尋是建立一個記憶化陣列儲存dfs遞迴函式不同引數的返回值
而動態規劃也是建立一個狀態轉移陣列儲存已求解的狀態
int dfs(int idx, int c) 中的引數idx和c 對應著 dp[i][j] 中的陣列下標i和j

int n,c;
int w[105],v[105];
int dp[105][1005]; //dp[i][j] 表示取到第i個物品時,揹包容量為j
int main(){
    cin>>c>>n;
    for(int i=1;i<=n;++i) cin>>w[i]>>v[i];
    for(int i=1;i<=n;++i)
        for(int j=1;j<=c;++j){
            if(w[i]>j) dp[i][j]=dp[i-1][j];
            else dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
        }
    cout<<dp[n][c];
    return 0;
}

相關文章