dfs和回溯

很多问题动态规划比较难想,dfs就可以用来兜底,再加上合适的剪枝就成了回溯,有时候可能比dp更快,dfs感觉比递归更好理解,一般都可以使用二叉树或者图的方式来理解。一般用回溯能解的一定能用dfs解因为dfs是遍历了所有的解。回溯遍历的解是dfs的子集

目录

01背包问题

dfs求解

回溯求解

1291. 顺次数

22. 括号生成


01背包问题

dfs求解

题目来源:https://www.acwing.com/problem/content/2/



#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
//dfs 显然超时
int w[MAXN];    // 重量 
int v[MAXN];    // 价值 
int f[MAXN];  // f[i][j], j重量下前i个物品的最大价值 

int maxprofit = 0;
void dfs(int w[],int v[],int i,int tmpw,int tmpv,int n,int m){
    if(i==n){ //终止条件
        if(tmpv > maxprofit && tmpw <= m){
            maxprofit = tmpv;
        }
        return ;//无论如何这里都要出去
    }
    if(tmpw > m){ //对不合理的解进行剪枝
        return ;
    }
    dfs(w,v,i+1,tmpw,tmpv,n,m);//不取第i件物品
    dfs(w,v,i+1,tmpw+w[i],tmpv+v[i],n,m);//取第i件物品
}
int main() 
{
    int n, m;   
    cin >> n >> m;
    for(int i = 0; i < n; ++i) 
        cin >> w[i] >> v[i];
    dfs(w,v,0,0,0,n,m);
    cout<<maxprofit<<endl;
    
    return 0;
}

不用修改的变量都可以放到dfs的外面


#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;


//超时


int w[MAXN];    // 重量 
int v[MAXN];    // 价值 

int n,m;
int maxprofit = 0;
void dfs(int i,int tmpw,int tmpv){
    if(i==n){
        if(tmpv > maxprofit && tmpw <= m){
            maxprofit = tmpv;
        }
        return ;
    }
    if(tmpw > m){
        return ;
    }
    
    dfs(i+1,tmpw,tmpv);
    if(tmpw + w[i] <=m)
    dfs(i+1,tmpw+w[i],tmpv+v[i]);
}
int main() 
{
       
    cin >> n >> m;
    for(int i = 0; i < n; ++i) 
        cin >> w[i] >> v[i];
    dfs(0,0,0);
    cout<<maxprofit<<endl;
    
    return 0;
}

回溯求解

回溯有多种写法,由于回溯其实就是dfs实现的,我更倾向于直接根据上面的dfs写回溯,这样容易理解,而且可以作为模板套用在其他问题上,但是很遗憾这个题回溯有个别测试案列还是超时的,关键是掌握这种思想

回溯模板

//这个状态把只需要修改的变量写进来,其他的都可以作为全局变量
void dfs(当前状态){
    if 当前状态到达边界 
        更新解
        return ; // 一定不能忘掉
    if 当前状态不满足约束条件   //剪枝
        return ; //一定不能忘掉
 
    for 遍历所有下一个可能的状态
        产生下一个新的状态
        dfs(下一个新的状态)
        回到上一个状态
}

回溯法求解01背包

int w[MAXN];    // 重量 
int v[MAXN];    // 价值 

int n,m;
int maxprofit = 0;
//i 第i+1个物品
//tmpw 当前放入背包的总重量
//tmpv 当前放入背包的总价值
void dfs(int i,int tmpw,int tmpv){ // 状态 (i,tmpw,tmpv)
    if(i==n){ //当前状态到达边界条件
        if(tmpv > maxprofit && tmpw <= m){
            maxprofit = tmpv;
        }
        return ;
    }
    if(tmpw > m){ //当前状态不满足约束条件
        return ;
    }
   
    for(int k=0;k<2;k++){ //产生下一个新的状态 ,选或不选
        tmpw +=k*w[i]; //产生新的状态
        tmpv +=k*v[i];
        dfs(i+1,tmpw,tmpv);
        tmpw -=k*w[i];//回到上一个状态回溯
        tmpv -=k*v[i];
    }
    
}
int main() 
{
       
    cin >> n >> m;
    for(int i = 0; i < n; ++i) 
        cin >> w[i] >> v[i];
    dfs(0,0,0);
    cout<<maxprofit<<endl;
    
    return 0;
}

回溯+限定条件剪枝

int w[MAXN];    // 重量 
int v[MAXN];    // 价值 

int n,m,tmpw,tmpv;
int maxprofit = 0;

int Bound(int i){  //限定条件 
    int s = 0;
    for(int j=i;j<n;j++){
        s +=v[j];
    }

    return s+tmpv;
}
void dfs(int i){
    if(i==n){
        if(tmpv > maxprofit && tmpw <= m){
            maxprofit = tmpv;
        }
        return ;
    }
  
    if(Bound(i+1) > maxprofit){ //满足限制条件搜索右子树
        dfs(i+1);
    }
    if(tmpw + w[i] <= m){ //满足约束条件搜索左子树
       
        tmpw +=w[i]; //产生新的状态
        tmpv +=v[i];
        dfs(i+1);
        tmpw -=w[i];
        tmpv -=v[i];
    }
        

    
    
}
int main() 
{
       
    cin >> n >> m;
    for(int i = 0; i < n; ++i) 
        cin >> w[i] >> v[i];
    dfs(0);
    cout<<maxprofit<<endl;
    
    return 0;
}

回溯算法的基础题目是组合排列子集问题,比较难的就是搜索

1291. 顺次数

时间打败100% 内存 5%

题目

我们定义「顺次数」为:每一位上的数字都比前一位上的数字大 1 的整数。

请你返回由 [low, high] 范围内所有顺次数组成的 有序 列表(从小到大排序)。

 
示例 1:

输出:low = 100, high = 300
输出:[123,234]
示例 2:

输出:low = 1000, high = 13000
输出:[1234,2345,3456,4567,5678,6789,12345]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sequential-digits
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

很显然这些数只能由1-9组成,所以相当于求1-9的排序数,而且每个数比前一个数大一。一般这些求所有可能的组合,排列和子集问题都是用回溯解决的。

class Solution {
public:
    vector<int>solu;
    vector<int>tmp;
    vector<int> sequentialDigits(int low, int high) {
        dfs(1,low,high,0);
        sort(solu.begin(),solu.end());
        return solu;
    }
    int num(vector<int>tmp){ //计算由这些数组成的整数
        int s = 0;
        for(int i=0;i<tmp.size();i++){
            s+=tmp[i]*pow(10,tmp.size()-1-i);
        }
        return s;
    }
    void dfs(int i, int low,int high,int pre){ //当前状态变量 i,pre记录前一个状态变量
        if(num(tmp)>=low && num(tmp)<=high){
            solu.push_back(num(tmp));
        }
        for(int j=i;j<10;j++){       //遍历下一个所有可能状态 i+1-9
                if(tmp.size()<1){//第一个数直接放入
                    tmp.push_back(j);
                    pre = j;
                    dfs(j+1,low,high,pre);//遍历下一个所有可能状态 i+1-9
                    tmp.pop_back(); 
                    //pre = j-1; //我开始把这个加上了,这个其实不应该加,因为第一个数之前没有数
                    
                }
                if(j-pre==1){
                    tmp.push_back(j);
                    pre = j;
                    dfs(j+1,low,high,pre);
                    tmp.pop_back(); 
                    pre = j-1;
                }     
        }
    }
};

22. 括号生成

题目

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。


示例:

输入:n = 3
输出:[
       "((()))",
       "(()())",
       "(())()",
       "()(())",
       "()()()"
     ]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/generate-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

求组合显然可以用回溯求解,

class Solution {
public:
    vector<string>solu;
    //在string后面加入char用push_back
    //加入string用+
    string s="";
    vector<string> generateParenthesis(int n) {
        dfs(n,0,0,"");
        return solu;
    }

    void dfs(int n,int l,int r,string s){//当前已经状态左括号和右括号使用的数量
        if(l ==n && r==n){   //边界条件
            solu.push_back(s); 
            return ;
        }
        if(r > l || r > n || l> n){  //不满足约束条件,剪枝
            return ;
        }
        
        //下面三种写法都可以
        //写法一:
        /*
        s.push_back(ch[0]);
        dfs(n,l+1,r,s);
        s.pop_back();

        s.push_back(ch[1]);
        dfs(n,l,r+1,s);
        s.pop_back();
        */
        
        //写法二
        /*
        for(int k=0;k<2;k++){
            
            if(k==0){
                s.push_back('(');
                l +=1;
                dfs(n,l,r,s);
                l -=1;

            }
            else{
                s.push_back(')');
                r +=1;
                dfs(n,l,r,s);
                r -=1;      //状态重置,这里r,和l需要重置的原因是,r,l在dfs里面修改了
            }
            s.pop_back();
        }
        */
        //写法三 
        for(int k=0;k<2;k++){ //遍历下一个可能的状态,使用左括号或者右括号
            
            if(k==0){     //使用左括号
                s.push_back('(');
                dfs(n,l+1,r,s);
            }
            else{        //使用右括号
                s.push_back(')');
                dfs(n,l,r+1,s);
            }
            s.pop_back();  //状态重置,这里r,和l不需要重置的原因是,r,l在dfs里面没有修改
        } 
    }
};

猜你喜欢

转载自blog.csdn.net/firesolider/article/details/108186349