集训第一天(2017/7/31):深度优先搜索dfs专项练习

       今天主要专项练习了深度优先搜索(dfs),终于还算是理解了这个算法的原理,今天总算没有虚度,下面就谈一谈我对这个算法的肤浅的理解吧:

      深度优先算法其实就是回溯+搜索。它的基本思想是:为了求得问题的解,先选择某一种可能的情况向前探索,在探索过程中,一旦发现这一步的选择是错误的,就退回一步返回上一层次,然后重新选择继续向下探索,如此反复,直至得解或证明无解。

      那么什么是回溯呢?回溯法是在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。回溯主要用递归来实现,在递归构造中,生成和检查过程可以有机结合起来,从而减少不必要的枚举。

      接下来附上我今天专项练习的代码,我都写上了详细的注释,如果这几道例题搞懂了,那么这个知识点也就掌握了。

eg1.n皇后问题(以6皇后为样例输入)

#include<iostream>
#include<cmath>
using namespace std;
void print(int*a,int n)
{
    for(int i=1;i<=n;i++)
    cout<<"("<<i<<","<<a[i]<<")"<<" ";
    cout<<endl;
}
bool judge(int*a,int i)//与前i-1行比较,判断第i行的当前位置是否合法
{
  for(int k=1;k<i;k++)
     if(a[k]==a[i]||fabs(i-k)==fabs(a[i]-a[k]))
      return 0;
  return 1;
}
void queen(int*a,int i,int n)/* 虽然只用了一个一维数组,但是其中已经保存了足够的信息。
                                因为每一行只能放一个皇后,所以一维数组的第i个位置存放的
                                是在第i行的哪一列(第j列)上放置了皇后。这个递归函数
                                每次处理一行,直到第n行(下标从1开始)。*/
{
    for(int j=1;j<=n;j++)
    {
        a[i]=j;
        if(judge(a,i)) /* 这个递归程序的结束条件是第n行放置完毕,
                          所以,当递归函数从调用queen(a, i + 1, n)返回时,
                          就是回到了第i行,继续搜索合适的位置。当第i + 1行的
                          所有位置都不能满足的时候,上面的调用就会返回,也就
                          是进行了所谓的回溯。这个回溯不需要显式的恢复以前的
                          调用环境,因为所需要的信息没有被破坏。*/
        {
            if(i==n)
            print(a,n);
            else
            queen(a,i+1,n);//下一行放置皇后
        }
    }
}
int main()
{
    int n;
    cin>>n;
    int *a=new int[n+1];
    queen(a,1,n);
return 0;
}

输入6

 输出结果:

一共有四种情况,每行代表一种情况。

 

eg2.跳马问题(骑士周游问题的变式)

问题简述:要求在8*8中国象棋格子,任意位置放一个马,如何不重复地把格子走完

#include<iostream>//8*8棋盘
#include<iomanip>
using namespace std;
int u[8]={1,2,1,2,-1,-1,-2,-2};//8个方向上x,y增量
int v[8]={2,1,-2,-1,2,-2,-1,1};
int a[100][100]={0},num=0;//a[][]记每一步走在棋盘的哪一格
bool b[100][100]={0};//记棋盘的每一格有没有被走过
int search(int,int,int);//以每一格为阶段,在每一步中试遍8个方向
int print();//打印方案
int main()
{
    a[1][1]=1;b[1][1]=1;//从(1,1)开始走
    search(1,1,2);//从(1,1)开始,搜第2步该怎样走
    cout<<num<<endl;//输出总方案数
    return 0;
}
int search(int i,int j,int n)
{
    int k,x,y;
    if(n>64)//打印最大规模统计方案
    {
        print();
        return 0;
    }
    for(k=0;k<=7;k++)//遍历8个方向
    {
        x=i+u[k];y=j+v[k];
        if(x<=8&&x>=1&&y<=8&&y>=1&&(!b[x][y]))
        {
            b[x][y]=1;
            a[x][y]=n;
            search(x,y,n+1);//从(x,y)去搜下一步该如何走
            b[x][y]=0;
            a[x][y]=0;
        }
    }

}
int print()
{
    num++;
    if(num<=8)
    {
        for(int k=1;k<=8;k++)
        {for(int kk=1;kk<=8;kk++)
        cout<<setw(8)<<a[k][kk];
        cout<<endl;
        }
        cout<<endl;
    }
}

......

 

eg3.素数环

     问题简述: 将从1到n这20个整数围成一个圆环,若其中任意2个相邻的数字相加,结果均为素数,那么这个环就成为素数环。

#include<iostream>
#include<cmath>
using namespace std;
bool b[21]={0};
int total=0,a[21]={0};
int search(int);//回溯过程
int print();//打印结果
bool pd(int,int);//判断素数
int main()
{
    search(1);

}
int search(int t)
{
    int i;
    for(i=1;i<=20;i++)
    if(pd(a[t-1],i)&&(!b[i]))//满足条件
    {
        a[t]=i;//保存结果
        b[i]=1;//保存结果
        if(t==20)
        {if(pd(a[20],a[1]))
            print();}//如果到达目的地输出解
        else search(t+1);/*
                          回溯就是让计算机自动的去搜索,碰到符合的情况就结束或者保存起来,
                          在一条路径上走到尽头也不能找出解,就回到上一级的岔路口,选择一条以前没有走过的路继续探测,直到找到解或者走完所有路径为止。
                          就这一点,回溯和所谓的DFS(深度优先搜索)是一样的。那现在的关键是,怎么实现搜索呢?
                          回溯既然一般使用递归来实现,那个这个递归调用该如何来写呢?
                          我现在的理解就是,进行回溯搜索都会有一系列的步骤,每一步都会进行一些查找。
                          而每一步的情况除了输入会不一样之外,其他的情况都是一致的。
                          这就刚好满足了递归调用的需求。通过把递归结束的条件设置到搜索的最后一步,就可以借用递归的特性来回溯了。
                          因为合法的递归调用总是要回到它的上一层调用的,
                          那么在回溯搜索中,回到上一层调用就是回到了前一个步骤。
                          当在当前步骤找不到一种符合条件情况时,那么后续情况也就不用考虑了,
                          所以就让递归调用返回上一层(也就是回到前一个步骤)找一个以前没有尝试过的情况继续进行。
                          当然有时候为了能够正常的进行继续搜索,需要恢复以前的调用环境。*/
        b[i]=0;
    }
}
int print()
{
    total++;
    if(total<=20)   //限定打印前20个结果
    {cout<<"<"<<total<<">";
    for(int j=1;j<=20;j++)
      cout<<a[j]<<" ";
      cout<<endl;}
}
bool pd(int x,int y)
{
    int k=2,i=x+y;
    while(k<=sqrt(i)&&i%k!=0) k++;
    if(k>sqrt(i)) return 1;
    else return 0;

}
输出结果:

 eg4.马的遍历

问题简述:

中国象棋半张棋盘如图所示。马自左下角往右上角跳。今规定只许往右跳,不许往左跳。比如图4(a)中所示为一种跳行路线,并将所经路线打印出

 

 

#include<iostream>
using namespace std;
int a[100][3];//a[m][1]记录第m步起始横坐标,a[m][2]记录第m步起始纵坐标
int t;//记录路径总数
int y[4]={1,2,-1,-2};
int x[4]={2,1,2,1};//四种移动规则,x代表横坐标,y代表纵坐标
int search(int);//搜索
int print(int);//打印
int main()
{
    a[1][1]=0;a[1][2]=0;//第一步从(0,0)开始,a[1][1]表示第一步起始横坐标,a[1][2]表示第一步起始纵坐标
    search(2);
    return 0;
}
int search(int i)
{
    for(int j=0;j<4;j++)//向4个方向跳
      if(a[i-1][1]+x[j]>=0&&a[i-1][1]+x[j]<=8&&a[i-1][2]+y[j]>=0&&a[i-1][2]+y[j]<=4)//判断马是否越界
      {
          a[i][1]=a[i-1][1]+x[j];
          a[i][2]=a[i-1][2]+y[j];//保存当前马的位置
          if(a[i][1]==8&&a[i][2]==4)
             print(i);
          else search(i+1);//搜索下一步
      }
}
int print(int ii)
{
    t++;
    cout<<t<<": ";
    for(int i=1;i<ii;i++)
    cout<<"("<<a[i][1]<<","<<a[i][2]<<")"<<"-->";
    cout<<"(4,8)";
    cout<<endl;
}

 输出结果:

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值