《算法竞赛进阶指南》0x22深度优先搜索


深度优先搜索就是按照深度优先的顺序对“问题状态空间“进行搜索的算法。我们可以把”问题空间“类比为一张图,其中的状态类比为节点,状态之间的联系与可达性就用图中的边来表示,那么深度优先搜索算法求解问题,就相当于在一张图上进行深度优先遍历。

深度优先搜索与”递归“和”栈“密切相关。我们认为”递归“是与递推相对的一种单纯的遍历方法,除了搜索之外,还有很多算法都可以用递归实现。而”深搜“是一种单纯的遍历形式、状态记录与检索、剪枝优化等算法整体设计的统称。

在研究深度优先搜索算法之前,我们先来定义该过程产生的”搜索树“结构。在对树进行深度优先遍历处于点x时对于某些边(x,y),y是一个尚未访问过的节点,程序从x成功进入了更深层对y的递归;对于另外一些边(x,y),y已经被访问过,从而程序继续考虑其他分支。我们称所有点(问题空间的状态)与成功发生递归的边(访问两个状态之间的移动)构成的树是一棵“搜索树”。整个深搜的过程是基于该搜索树完成的——为了避免重复访问,我们对状态进行记录和检索;为了使程序更加高效,我们提前减除搜索树上不可能的答案的子树和分支,不去进行遍历。

例题
acwing165.小猫爬山

我们发现这道题目有两个可以剪枝的部分,一个是如果当前的答案已经大于了我们已知的最小答案,不用说直接return返回即可.第二个剪枝则是,我们可以将小猫的体重从大到小排序,这样我们的搜索树就会缩短许多,至于为什么,因为我们的剩余空间就变小了,然后可选择的猫也就少了。

#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_N 18
int cat[MAX_N+5],cab[MAX_N+5];
int n,w,ans=100;
bool cmp(int a,int b)
{
    return a>b;
}
void dfs(int now,int cnt)
{
    if(cnt>=ans)return ;
    if(now>n)
    {
        ans=min(ans,cnt);
        return ;
    }
    for(int i=1;i<=cnt;i++)
    {
        cab[i]+=cat[now];
        if(cab[i]<=w)dfs(now+1,cnt);
        cab[i]-=cat[now];
    }
    cab[cnt+1]=cat[now];
    dfs(now+1,cnt+1);
    cab[cnt+1]-=cat[now];
    return ;
}
int main()
{
    cin>>n>>w;
    for(int i=1;i<=n;i++)cin>>cat[i];
    sort(cat+1,cat+1+n,cmp);
    dfs(1,0);
    cout<<ans;
    return 0;
}

acwing166.数独

1.在每个状态下,从所有未填的位置里选择”能填的合法数字“最少的位置,考虑该位置填什么数,而不是任意找出一个位置。
2.使用位运算代替数组执行”对熟读各个位置所填数字的记录“以及”可行性的检查与统计“,这就是常说的”常数优化“。
(1).对于每行、每列、每个九宫格,分别用一个9位二进制数保存哪些数字可以填。
(2).对于每个位置,把他在行、列、九宫格的3个二进制数做位与运算,可以得到该位置能填哪些数。
(3).在一个位置填上某个数后,把该位置所在的行、列、九宫格记录的二进制数对应的位改为0,即可更新当前状态,回溯时改成1,即可还原现场。

#include<iostream>
using namespace std;
#define MAX_N 9
#define MAX_M 1<<MAX_N
char str[100];
int ones[MAX_M],map[MAX_M];
int row[MAX_N],col[MAX_N],cell[3][3];
int flag=0;
void Init()
{
    for(int i=0;i<MAX_N;i++)row[i]=col[i]=(1<<MAX_N)-1;
    for(int i=0;i<3;i++)
    for(int j=0;j<3;j++)
    cell[i][j]=(1<<MAX_N)-1;
}
void draw(int x,int y,int t,bool flag)
{
    if(!flag)str[x*MAX_N+y]='.';
    else str[x*MAX_N+y]='1'+t;
    int v=1<<t;
    if(!flag)v=-v;
    row[x]-=v;
    col[y]-=v;
    cell[x/3][y/3]-=v;
}
int lowbit(int x)
{
    return x&-x;
}
int get(int x,int y)
{
    return row[x]&col[y]&cell[x/3][y/3];
}
void dfs(int cnt)
{
    if(!cnt)
	{
	    flag=1;
		return ;
	}
    int min_v=10;
    int x,y;
    for(int i=0;i<MAX_N;i++)
    for(int j=0;j<MAX_N;j++)
    if(str[i*MAX_N+j]=='.')
    {
        int state=get(i,j);
        if(min_v>ones[state])
        {
            x=i,y=j;
            min_v=ones[state];
        }
    }
    int state=get(x,y);
    while(state)
    {
        int t=map[lowbit(state)];
        state-=lowbit(state);
        draw(x,y,t,true);
        dfs(cnt-1);
        if(flag)return ;
        draw(x,y,t,false);
    }
    return ;
}
int main()
{
    for(int i=0;i<MAX_N;i++)map[1<<i]=i;
    for(int i=0;i<MAX_M;i++)
    for(int j=0;j<MAX_N;j++)
    ones[i]+=i>>j&1;
    while(cin>>str,str[0]!='e')
    {
        Init();
        int cnt=0;
        flag=0;
        for(int i=0;i<MAX_N;i++)
        for(int j=0;j<MAX_N;j++)
        if(str[i*MAX_N+j]!='.')
        {
            int t=str[i*MAX_N+j]-'1';
            draw(i,j,t,true);
        }
        else cnt+=1;
        dfs(cnt);
        puts(str);
    }
    return 0;
}

->剪枝<-

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值