DFS个人总结

来源:互联网 发布:社会法则 知乎 编辑:程序博客网 时间:2024/05/22 07:04

                               本人写了十道左右的深搜题,感觉这种题实际就是一种模板题,有套路可寻,故挑了几题做个小总结。

①DFS代码大体上可以分为两种,一是走后再判,二是判后再走。现在分别举出用这两种方式解的题:


第一题: :百练 2815  城堡问题(网址:点击打开链接)



AC代码:

#include<iostream>#include<cstdio>#include<cstring> #include<algorithm>#define N 100using namespace std;int x,y;int map[N][N];int color[N][N];int roomnum,temp,maxnum;void DFS(int i,int j)      //类型一:走后再判{    if(color[i][j]!=0)     //判断(i,j)是否可以走  return ;    temp++;    color[i][j]=roomnum;   //做标记    if((map[i][j]&1)==0) DFS(i,j-1);    //先走到(i,j-1)上,而后在判断是否走过    if((map[i][j]&2)==0) DFS(i-1,j);    if((map[i][j]&4)==0) DFS(i,j+1);    if((map[i][j]&8)==0) DFS(i+1,j);  }int main(){//freopen("E:\\in.txt","r",stdin); cin>>x>>y;memset(map,0,sizeof(map));memset(color,0,sizeof(color));for(int i=0;i<x;i++)  for(int j=0;j<y;j++)           cin>>map[i][j];    for(int i=0;i<x;i++)   for(int j=0;j<y;j++)      if(color[i][j]==0)  {  roomnum++;  temp=0;  DFS(i,j);  maxnum=max(maxnum,temp);    }             cout<<roomnum<<endl<<maxnum<<endl;     return 0;}
第二题:百练4982 踩方格(网址:点击打开链接)



AC代码:

#include <iostream>#include <cstring>using namespace std;int visited[30][50];int dfs ( int i,int j,int n)   //类型二:先判再走{    if( n == 0)          //终止条件       return 1;    visited[i][j] = 1;   //做标记    int num = 0;    if( ! visited[i][j-1] )   //先(i,j-1)判断是否可以走       num+= dfs(i,j-1,n-1);      if( ! visited[i][j+1] )       num+= dfs(i,j+1,n-1);    if( ! visited[i+1][j] )       num+= dfs(i+1,j,n-1);    visited[i][j] = 0;   //消除标记       return num; }int main(){     int n;    cin >> n;    memset(visited,0,sizeof(visited));    cout << dfs(0,25,n) << endl;    return 0;}
PS:可以注意到上面两题除了“判”和“走”的次序不一样之外,还有就是第一题没有消除标记,而第二题有消除标记,因为第一题的各个路之间不存在互相交叉的情况,所以就不需要消除标记,而第二题各个路之间可能有交叉情况,所以退回原处之后就要消除上一条路所做的标记。

②上面两个DFS格式虽然都可以解题,也可以互相转化,不过最好根据具体题目具体做出题目,一般而言,第二种(先判后走)一般耗时更少些,下面举出Poj一个题目,我通过两个格式都交了一次,而且这题也涉及到了两种剪枝类型:可行性剪枝和最优性剪枝


题目:Poj Roads(地址:点击打开链接)


AC代码(先判后走):

#include<iostream>  #include<cstdio>  #include<string>  #include<cstring>  #include<algorithm>  #include<cstdlib>  #include<vector>  using namespace std;        struct Roads  {     int d;     int l;     int t;  };        vector<Roads> city[105];    //定义向量,相当于一个二维数组,其中第一维的长度无限大,第二维的长度是105,数组中的各个元素是之前定义的Roads结构体        int K,N,R;  int totallen,totalcost,minlen;  int minn[105][10005];    //minn[i][j]表示到达i城市且花费了j时所走的最小路程 ,用于下面每走到一个城市都可以进行剪枝  int visited[105];       //由于本题是求最短路径长度,而若是走的过程中,对一个城市走了不止一遍,那么肯定不再是最短路径长度,所以每一个城市都不可以重复走        void dfs( int c )  {    if( c == N )   //走到了终点    {    minlen = min( minlen , totallen );  //结束递归之前,先更新minlen的值    return ;    }    int rnum = city[c].size();    for( int i = 0 ; i < rnum ; ++i )   //遍历c城市所能走的所有路    {      if( visited[city[c][i].d] || totalcost + city[c][i].t > K )   //可行性剪枝        continue;      if( totallen + city[c][i].l >= minlen || totallen + city[c][i].l >= minn[city[c][i].d][totalcost + city[c][i].t] )  //最优性剪枝            continue;        totalcost += city[c][i].t;        totallen += city[c][i].l;        minn[city[c][i].d][totalcost] = totallen;        visited[city[c][i].d] = 1;   //更新状态        dfs(city[c][i].d);        totallen -=  city[c][i].l;        totalcost -= city[c][i].t;        visited[city[c][i].d] = 0;   //还原状态      }        }              int main()  {     freopen( "E:\\in.txt", "r" , stdin );     cin >> K;   //总共的钱     cin >> N >> R;     for(int i = 0; i < R ; ++i)   //构建图(连接表)     {       int s;       cin>>s;       Roads r;       cin>>r.d>>r.l>>r.t;       city[s].push_back(r);     }    totalcost = 0;  //初始化     totallen = 0;     minlen = 1 << 30;     for(int i = 0 ; i < 105 ; ++i)         for(int j = 0 ; j < 10005 ; ++j)              minn[i][j] = 1 << 30;     memset(visited,0,sizeof(visited));    visited[1]=1;    dfs(1);    //深搜开始     if(minlen == 1 << 30)    //minlen的值没有被改变 说明并没有找到合适的路       cout << "-1" << endl;     else       cout << minlen << endl;     return 0;     }  
运行结果:



AC代码(走后再判):

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<vector>#include<cstdlib>using namespace std;typedef struct Roads{    int D;    int L;    int T;}r;int K;int N;int R;int minlen;int totallen;int totalcost;int visited[105];int minn[105][10005]; vector<r> map[10005];void dfs(int n){if( visited[n] || totalcost > K )   //可行性剪枝             return ;    if( totallen >= minlen || totallen >= minn[n][totalcost] )  //最优性剪枝            return ;    minn[n][totalcost] = totallen;    visited[n] = 1;   //判断可走后,做标记 if( n == N )   {   minlen = min(minlen,totallen); return ;     }   for( int i = 0 ; i < map[n].size() ; ++i )   {       int d = map[n][i].D;            totallen += map[n][i].L;            totalcost += map[n][i].T; //走后再判,所以直接先跟新状态量             dfs(d);                   //回退到d后,还原状态值以及清除标记             visited[d] = 0;            totallen -= map[n][i].L;            totalcost -= map[n][i].T;   }}int main(){freopen("E:\\in.txt","r",stdin);    cin>>K>>N>>R;    for(int i = 0; i < R; ++i)    {        int s;        r R;        cin >> s >> R.D >> R.L >> R.T;         if(s != R.D)          map[s].push_back(R);    }    minlen=1<<30;    totalcost=0;    totallen=0;    memset(visited,0,sizeof(visited));    for(int i = 0; i < 105 ; ++i)         for(int j = 0; j < 10005 ; ++j)              minn[i][j]=1<<30;    dfs(1);    //由于是走后再判,所以dfs(1)之前不能将visited[1]=1     if(minlen == 1<<30)       cout << "-1" << endl;    else    cout<<minlen<<endl;    return 0;}
运行结果:


PS:①运行结果显示第一个代码比第二种快,见图

       ②由于“判”和“走”的先后次序不同,所以在main函数中第一步走之前,visited数组是否标记存在差异,需引起注意

最后附上北大郭炜老师在他课程里的总结的几种dfs模板:

①判断从V出发是否能走到终点:
bool Dfs(V) {
      if( V  为终点)
         return true;
     if( V  为旧点)
      return false;
      将V 标记为旧点;
    对和V 相邻的每个节点U {
      if( Dfs(U) == true)
        return true;
     }
     return false;
}

int main()
{
将所有点都标记为新点;
起点 = 1
终点 = 8
cout << Dfs( 起点);
}

将其转化为另一种格式:
bool Dfs(V) {
     if( V  为终点)
       return true;
     将V 标记为旧点;
   对和V 相邻的每个节点U {

     if(U是新点)

    {

     if( Dfs(U) == true)
     return true;
    }

   }   

return false;
  }

int main()
{
将所有点都标记为新点;
起点 = 1
终点 = 8
cout << Dfs( 起点);
}

②判断从V出发是否能走到终点,如果能,要记录路径:

Node path[MAX_LEN]; //MAX_LEN 取节点总数即可
int depth;
bool Dfs(V) {
if( V 为终点){
path[depth] = V;
return true;
}
if( V  为旧点)
return false;
将V 标记为旧点;
path[depth]=V;
++depth;
在图上寻找路径
13
对和V 相邻的每个节点U {
if( Dfs(U) == true)
return true;
}
--depth;
return false;
}
int main()
{
将所有点都标记为新点;
depth = 0;
if( Dfs( 起点)) {
for(int i = 0;i <= depth; ++ i)
cout << path[i] << endl;
}
}

③遍历图上所有节点

Dfs(V) {
if( V 是旧点)
return;
将V 标记为旧点;
对和V 相邻的每个点 U {
Dfs(U);
}
}
int main() {
将所有点都标记为新点;
while( 在图中能找到新点k)
Dfs(k);
}

原创粉丝点击