深搜+回溯+广搜小结

深搜+回溯+广搜小结,第1张

深搜

按照一定的顺序和规则,一直往深处走,直到走不通再返回,换一种路径重复上述步骤。
深搜一般可以找到问题的所有答案,但问题规模较大时,解集树的深度就会比较大并且比较宽,时间复杂度就会较高。与广搜相比,深搜的空间复杂度会较低,因为深搜是深度优先,只需要存储当前子树的状态,不需要像广搜那样存储大量状态。

优化

1、可行性剪枝和最优性剪枝
2、记忆化搜索
3、减少重复搜索

问题练习

P1451 求细胞数量
深搜的入门题,求出连通块,把搜索过的地方做标记,记录连通块数量即可

#include 
#define eps 1e-15
using namespace std;
int n,m,a[105][105],r[4]={-1,1,0,0},c[4]={0,0,-1,1};
void dfs(int i, int j){
        a[i][j]=0;
        for(int k=0; k<4; k++){
            int b=i+r[k],d=j+c[k];
            if(a[b][d] != 0 && 1<=b && b<=n && d>=1 && d<=m)dfs(b,d);
        }
}

int main()
{

	cin>>n>>m;
	for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++)
        scanf("%1d",&a[i][j]);
    int ans=0;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++){
            if(a[i][j]!=0)dfs(i,j),ans++;
        }
    cout<<ans;
    return 0;
}

P1162 填涂颜色
也是求连通块的题,如果找圈内的块,不知道具体在哪,不好去控制深搜程序。如果找出圈外的连通块,把圈外的涂色,就能区别出圈内的。

#include 
#define eps 1e-15
using namespace std;
int n,a[35][35],b[35][35],x[4]={-1,1,0,0},y[4]={0,0,-1,1};
void dfs(int r, int c){
    if(b[r][c]==1 || b[r][c]==2)return;
    b[r][c]=2;
    for(int k=0; k<4; k++){
        int rr=r+x[k],cc=c+y[k];
        if(rr>=1 && rr<=n && cc>=1 && cc<=n)dfs(rr,cc);
    }
}
/*
6
1 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1
*/
int main()
{
    cin>>n;
	for(int i=1 ;i<=n; i++)
        for(int j=1; j<=n; j++){
        cin>>a[i][j];
        b[i][j]=a[i][j];
        }
        //这样能保证找出圈外的所有连通块
    for(int i=1; i<=n; i++)
        dfs(1,i);
    for(int i=1; i<=n; i++)
        dfs(i,1);
    for(int i=1; i<=n; i++)
        dfs(n,i);
    for(int i=1; i<=n; i++)
        dfs(i,n);
    for(int i=1; i<=n; i++){
        for(int j=1; j<=n; j++)
        if(1==b[i][j])cout<<1<<" ";
        else if(b[i][j]==2)cout<<0<<" ";
        else if(b[i][j]==0)cout<<2<<" ";
        cout<<endl;
    }
    return 0;
}

P1141 01迷宫
还是连通块的题,但是技巧巧妙。
每次搜索都能找出一个连通块,连通块内能到达的数量都是一样的。
flag数组用来存答案数组的下标,ans数组用来存答案。
还需要注意搜索完后恢复a数组的状态。不能在每一层深搜结束后把当前的答案直接当成这个点的最终答案,因为这样不能保证这个连通块的每个值都是相同的。

#include 
#define eps 1e-15
using namespace std;
int n,a[1005][1005],flag[1005][1005],r[4]={-1,1,0,0},c[4]={0,0,-1,1},ans[1000005];
void dfs(int i, int j,int l){
    if(!flag[i][j]){ans[l]++;flag[i][j]=l;}
    int x=a[i][j];
    a[i][j]=-1;
    for(int k=0; k<4; k++){
        int b=i+r[k], d=j+c[k];
        if(b>=1 && b<=n && d>=1 && d<=n && a[b][d]+x == 1 && flag[b][d])continue;
        if(b>=1 && b<=n && d>=1 && d<=n && a[b][d]+x == 1 )dfs(b,d,l);
    }
    a[i][j]=x;
}
int main()
{
    int m,k=1;
    cin>>n>>m;
    for(int i=1; i<=n; i++)
    for(int j=1; j<=n; j++)
        scanf("%1d",&a[i][j]);
    while(k<=m){
        int in1,in2;
        scanf("%d%d",&in1,&in2);
        if(flag[in1][in2]!=0)cout<<ans[flag[in1][in2]];
        else dfs(in1,in2,k),cout<<ans[flag[in1][in2]];
        k++;
        if(k<=m)cout<<endl;
    }
    return 0;
}

P1036 [NOIP2002 普及组] 选数
和全排列写法有些许类似,感觉比全排列问题简单

#include 
#define eps 1e-15
using namespace std;
int n,k,a[25],sum,ans;
bool judge(int i){
    bool f=true;
    for(int j=2; j<=i/2; j++){
        if(i%j==0){f=false;break;}
    }
    return f;
}
void dfs(int ne, int i){
    if(i>k){
        if(judge(sum))ans++;
        return;
    }
    for(int j=ne; j<=n-k+i; j++)
        sum+=a[j],dfs(j+1,i+1),sum-=a[j];
}
int main()
{
    cin>>n>>k;
    for(int i=1; i<=n; i++)cin>>a[i];
    dfs(1,1);
    cout<<ans;
    return 0;
}

P2372 yyy2015c01挑战算周长
题意挺绕的,简单来说就是求连通块的周长。连通块内的每个点贡献的周长确定,应该看其上下左右又没有碰墙或不属于连通块的点。

#include 
#define eps 1e-15
using namespace std;
int n,m,visit[25][25],nx[8]={-1,1,0,0,-1,-1,1,1},ny[8]={0,0,-1,1,-1,1,1,-1},ans;
char a[25][25];
void dfs(int x,int y){
    if(visit[x][y] || a[x][y]=='.')return;
    visit[x][y]=1;
    for(int k=0; k<8; k++){
        int xx=x+nx[k],yy=y+ny[k];
        if(xx>=1&&xx<=n&&yy>=1&&yy<=m)dfs(xx,yy);
        if(k<=3 && (xx>n || xx<1 || yy<1 || yy>m || a[xx][yy]=='.'))ans++;
    }
}

int main()
{
    int x,y;
    cin>>n>>m>>x>>y;
    for(int i=1; i<=n; i++)
    for(int j=1; j<=m; j++)
            cin>>a[i][j];
	dfs(x,y);
	cout<<ans;
    return 0;
}
回溯

对于回溯算法,我现在也没有深入了解,只是在课上听了听,只敲了一个图的m着色问题。
回溯用深度优先搜索的方法搜索解空间,并通过剪枝去掉不可能产生解的子空间,回溯的空间诉求和深搜是一样的(废话。。。)。

#include 
#define eps 1e-15
using namespace std;
int n,k,m,a[105][105],co[105],ans;
bool judge(int i){

    for(int j=1; j<i; j++){
        if(a[i][j]==1 && co[i]==co[j])return false;
    }
    return true;
}
void mapColor(int i){
    if(i>n)ans++;
    else {
        for(int j=1; j<=m ;j++){
            co[i]=j;
            if(judge(i))mapColor(i+1);
        }
    }
}

int main()
{
    cin>>n>>k>>m;
    int r,c;
    for(int i=1 ;i<=k; i++){
        cin>>r>>c;
        a[r][c]=1;
        a[c][r]=1;
    }
	mapColor(1);
	cout<<ans;
    return 0;
}

广搜

广搜是一层一层的进行搜索,每找到一个符合条件的结点就入队,在下一次循环出队,直到队空或产生最终结果。
每一层的状态都是最优的,但需要大量空间,存储每个结点产生的状态。
P1443 马的遍历
应该算是广搜的模板题了。

#include 
#define eps 1e-15
using namespace std;
int visit[405][405],nx[8]={-2,-1,1,2,2,1,-1,-2},ny[8]={1,2,2,1,-1,-2,-2,-1};
struct horse{
    int xx,yy,p;
    horse(int xx,int yy,int pp){make(xx,yy,pp);}
    void make(int xxx,int yyy,int pp){xx=xxx;yy=yyy;p=pp;}
};
queue<horse> q;
int main()
{
    int n,m,x,y,a[405][405],num=0;
    cin>>n>>m>>x>>y;
    memset(visit,0,sizeof(visit));
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++)
        a[i][j]=-1;
    visit[x][y]=1;
    horse t(x,y,0);
	q.push(t);
	while(!q.empty()){
        t=q.front();
        q.pop();
        x=t.xx,y=t.yy;num=t.p;
        a[x][y]=t.p;
        //visit[x][y]=1;可能造成同一个点多次入队,在数据量大时就会MLE
        for(int i=0; i<8; i++){
            int xx=x+nx[i],yy=y+ny[i];
            if(xx>=1&&xx<=n&&yy>=1&&yy<=m && !visit[xx][yy])t.make(xx,yy,num+1),q.push(t),visit[xx][yy]=1;
        }
	}
	for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++)
            printf("%-5d",a[i][j]);
        printf("\n");
	}
    return 0;
}

P1746 离开中山路
找到最终结果直接结束

#include 
#define eps 1e-15
using namespace std;
int ans[1005][1005],a[1005][1005];
int main()
{
    int n,x1,x2,y1,y2;
    int nx[4]={-1,1,0,0},ny[4]={0,0,-1,1};
    queue<pair<int,int>> q;
    cin>>n;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
        scanf("%1d",&a[i][j]);
    cin>>x1>>y1>>x2>>y2;
    pair<int,int> t(x1,y1);
    q.push(t);
    a[x1][y1]=1;
    while(!q.empty()){
        t=q.front();
        q.pop();
        x1=t.first,y1=t.second;
        for(int i=0; i<4; i++){
            int xx=x1+nx[i],yy=y1+ny[i];
            if(xx>=1&&xx<=n&&yy>=1&&yy<=n&&a[xx][yy]==0)a[xx][yy]=1,ans[xx][yy]=ans[x1][y1]+1,t=make_pair(xx,yy),q.push(t);
        }
        if(ans[x2][y2]!=0)break;//找到结果一定最优,直接退出
    }
    cout<<ans[x2][y2];
    return 0;
}

如有错误或不足,还请您指出!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/707041.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-24
下一篇 2022-04-24

发表评论

登录后才能评论

评论列表(0条)

保存