图的遍历也称为搜索,就是从图中某个顶点出发,沿着一些边遍历图中所有的顶点,且每个顶点仅被访问一次,遍历可采取两种不同的方式:深度优先搜索(DFS)和广度优先搜索(BFS)。

1.DFS算法思想`

从顶点v出发深度遍历图G的算法

① 访问v0顶点,置vis[v0]=1,搜索v0未被访问的邻接点w,若存在邻接点w,则dfs(w),直到到达所有邻接点都被访问过的顶点u为止,接着退回一步,看是否还有其他没有被访问的邻接点。如果有,则访问此顶点,进行前述类似的访问,如果没有,就在退回一步进行搜索,重复上述过程直到所有顶点都被访问过。

② dfs算法最大特色就在于其递归特性,使得算法代码简洁。

2. BFS算法思想

从顶点v出发广度遍历图G的算法

①  访问v0顶点,置vis[v0]=1,依次访问顶点v0各个未被访问的邻接点,将v0各个未被访问的邻接点都访问到,分别从这些邻接点出发,依次访问他们未被访问的邻接点,直到所有顶点都被访问过。常用队列实现。

② 非递归,好理解。

基本思想讲完了,下面说说今天做的四道搜索题吧。

1.POJ2251 Dungeon Master:http://poj.org/problem?id=2251

题意:给出一三维空间的地牢,要求求出由字符'S'到字符'E'的最短路径

移动方向可以是上,下,左,右,前,后,六个方向

每移动一次就耗费一分钟,要求输出最快的走出时间。

不同L层的地图,相同RC坐标处是连通的

思路:这道题和之前做的走迷宫什么的类似,就是多了一维,做的时候把每一维的次序弄乱了,调了很久才搞清楚。

int dx[6]= {0,0,0,0,1,-1};
int dy[6]= {0,0,1,-1,0,0};
int dz[6]= {1,-1,0,0,0,0};
用三个数组分别存x方向,y方向,z方向的操作。我用的BFS,建图的时候建对,就没什么问题了。
附上代码:
 #include<stdio.h>
#include<queue>
#include<string.h>
using namespace std;
int visit[][][];
int step[][][];
int dx[]= {,,,,,-};
int dy[]= {,,,-,,};
int dz[]= {,-,,,,};
int n,m,k,flag;
struct node
{
int x,y,z;
} start,end;
int bfs()
{
flag=;
queue <node> Q;
Q.push(start);
node temp;
visit[start.x][start.y][start.z]=;
while(!Q.empty())
{
temp=Q.front();
Q.pop();
if(temp.x==end.x&&temp.y==end.y&&temp.z==end.z)
{
flag=;
break;
}
int x=temp.x;
int y=temp.y;
int z=temp.z;
for(int i=; i<; i++)
{
if(!visit[z+dz[i]][x+dx[i]][y+dy[i]]&&x+dx[i]<m&&x+dx[i]>=&&y+dy[i]<k&&y+dy[i]>=&&z+dz[i]<n&&z+dz[i]>=)
{
temp.x=x+dx[i];
temp.y=y+dy[i];
temp.z=z+dz[i];
visit[temp.z][temp.x][temp.y]=;
Q.push(temp);
step[temp.x][temp.y][temp.z]=step[x][y][z]+;
}
}
}
return flag;
}
int main()
{
int i,j,t;
char ch;
while(scanf("%d%d%d",&n,&m,&k)!=EOF)
{
if(n==&&m==&&k==)
break;
memset(step,,sizeof(step));
getchar();
for(i=; i<n; i++)
{
for(j=; j<m; j++)
{
for(t=; t<k; t++)
{
scanf("%c",&ch);
if(ch=='.')
visit[i][j][t]=;
else if(ch=='#')
visit[i][j][t]=;
else if(ch=='S')
{
visit[i][j][t]=;
start.x=j;
start.y=t;
start.z=i;
}
else if(ch=='E')
{
visit[i][j][t]=;
end.x=j;
end.y=t;
end.z=i;
}
}
getchar();
}
if(i!=n-)
getchar();
}
if(bfs())
printf("Escaped in %d minute(s).\n",step[end.x][end.y][end.z]);
else
printf("Trapped!\n");
}
return ;
}

2.POJ 1426 Find The Multiple  :http://poj.org/problem?id=1426

题意: 给出n,求由0或1组成的一个整数能整除n。

思路:我是暴力加bfs水过的,用C++交时间超限了,看到有人说用G++交不会超时,一交果然AC了。。不会优化,先附上丑陋代码:

 #include<stdio.h>
#include<queue>
#include<string.h>
using namespace std;
int n;
long long bfs()
{
queue<long long> Q;
while(!Q.empty())
{
Q.pop();
}
Q.push();
while()
{
long long sum=Q.front();
if(sum%n==)
return sum;
Q.pop();
Q.push(sum*);
Q.push(sum*+);
}
}
int main()
{
while(scanf("%d",&n)!=EOF&&n)
{
printf("%lld\n",bfs());
}
return ;

看到网上别人思路:

    解题思路:

首先暴力枚举肯定是不可能的 1000ms 想不超时都难,而且枚举还要解决大数问题。。

要不是人家把这题放到搜索,怎么也想不到用BFS。。。

    解题方法: BFS+同余模定理

不说废话

    首先说说朴素的不剪枝搜索方法:

我以n=6为例

首先十进制数,开头第一个数字(最高位)一定不能为0,即最高位必为1

设6的 ”01十进制倍数” 为k,那么必有k%6 = 0

现在就是要用BFS求k值

1、先搜索k的最高位,最高位必为1,则此时k=1,但1%6 =1  !=  0

因此k=1不是所求,存储余数 1

2、搜索下一位,下一位可能为0,即 k*10+0,此时k=10,那么k%6=4

可能为1,即 k*10+1,此时k=11,那么k%6=5

由于余数均不为0,即k=10与k=11均不是所求

3、继续搜索第三位,此时有四种可能了:

对于k=10,下一位可能为0,即 k*10+0,此时k=100,那么k%6=4

下一位可能为1,即 k*10+1,此时k=101,那么k%6=5

对于k=11,下一位可能为0,即 k*10+0,此时k=110,那么k%6=2

下一位可能为1,即 k*10+1,此时k=111,那么k%6=3

由于余数均不为0,即k=100,k=101,k=110,k=111均不是所求

4、继续搜索第四位,此时有八种可能了:

对于k=100,下一位可能为0,即 k*10+0,此时k=1000,那么k%6=4

下一位可能为1,即 k*10+1,此时k=1001,那么k%6=5

对于k=101,下一位可能为0,即 k*10+0,此时k=1010,那么k%6=2

下一位可能为1,即 k*10+1,此时k=1011,那么k%6=3

对于k=110,下一位可能为0,即 k*10+0,此时k=1100,那么k%6=2

下一位可能为1,即 k*10+1,此时k=1101,那么k%6=3

对于k=111,下一位可能为0,即 k*10+0,此时k=1110,那么k%6=0

下一位可能为1,即 k*10+1,此时k=1111,那么k%6=1

我们发现k=1110时,k%6=0,即1110就是所求的倍数

从上面的演绎不难发现,用BFS是搜索 当前位数字 (除最高位固定为1),因为每一位都只有0或1两种选择,换而言之是一个双入口BFS

本题难点在于搜索之后的处理:对余数的处理,对大数的处理,余数与所求倍数间的关系

接下来说说处理大数问题和剪枝的方法:

首先我们简单回顾一下 朴素搜索 法:

n=6

1%6=1  (k=1)

{

(1*10+0)%6=4  (k=10)

{

 (10*10+0)%6=4   (k=100)

{

  (100*10+0)%6=4  (k=1000)

        (100*10+1)%6=5  (k=1001)

}

    (10*10+1)%6=5  (k=101)

{

  (101*10+0)%6=2  (k=1010)

        (101*10+1)%6=3  (k=1011)

}

}

   (1*10+1)%6=5  (k=11)

{

    (11*10+0)%6=2   (k=110)

{

    (110*10+0)%6=2  (k=1100)

        (110*10+1)%6=3  (k=1101)

}

 (11*10+1)%6=3   (k=111)

{

        (111*10+0)%6=0  (k=1110)   有解

        (111*10+1)%6=1  (k=1111)  由于前面有解,这个余数不存储

}

}

}

从上面可以看出余数的存数顺序(逐层存储):

用数组mod[]存储余数,其中mod[0]不使用,由mod[1]开始

那么mod中的余数依次为: 1 4 5 4 5 2 3 4 5 2 3 2 3 0  共14个

即说明我们得到 余数0 之前,做了14步*10的操作,那么当n值足够大的时候,是很容易出现k为大数的情况(事实上我做过统计,200以内的n,有18个n对应的k值为大数

那么我们再用int去存储k就显得不怎么明智了。

为了处理所有情况,我们自然会想到 是不是应该要用int[]去存储k的每一位?

而又由于k是一个01序列,那能不能把 *10得到k每一位的问题 转化为模2的操作得到k的每一位(0或1) 呢?

答案是可以的

首先我们利用 同余模定理 对得到余数的方式进行一个优化

(a*b)%n = (a%n *b%n)%n

(a+b)%n = (a%n +b%n)%n

随便抽取上面一条式子为例

前一步 (11*10+1)%6=2   即k=110 , k%6=2

当前步 (110*10+1)%6=2

由同余模定理  (110*10+1)%6 = ((110*10)%6+1%6 )%6 = ((110%6 * 10%6)%6 +1 )%6

不难发现下划线部分110%6等于 (11*10+0)%6 = 2

所以当前步(110*10+1)%6可以转变为  (2*10+1)%6=2

很显然地,这种处理把k=110 等价于 k=2

即用 前一步操作得到的余数 代替 当前步的k值

而n在200的范围内, 余数值不可能超过3位数, 这就解决了 大数的问题

通过这种处理手法,我们只需在BFS时顺手存储一个 余数数组mod[] ,就能通过mod[i-1]得到mod[i]  ,直到mod[i]==0 时结束,大大减少了运算时间

前面已经提到,n=6时,求余操作进行了14次,对应地,BFS时*10的操作也进行了14次。

令i=14,通过观察发现,i%2恰好就是 6 的倍数的最低位数字

i/2  再令 i%2 ,恰好就是 6 的倍数的 次低位数字。。。

循环这个操作,直到i=0,就能得到 6的 01倍数(一个01队列),倒序输出就是所求

这样就完成了 *10操作到 %2操作的过渡

由于n值有限,只是1到200的整数,因此本题也可以用打表做,通过上面的方法得到结果后,就把1~200的倍数打印出来,重新建立一个程序,直接打表就可以了。

不过打表比上面介绍的方法快不了多少

代码:

 //Memory Time
//2236K 32MS #include<iostream>
using namespace std; int mod[]; //保存每次mod n的余数
//由于198的余数序列是最长的
//经过反复二分验证,436905是能存储198余数序列的最少空间
//但POJ肯定又越界测试了...524286是AC的最低下限,不然铁定RE int main(int i)
{
int n;
while(cin>>n)
{
if(!n)
break; mod[]=%n; //初始化,n倍数的最高位必是1 for(i=;mod[i-]!=;i++) //利用同余模定理,从前一步的余数mod[i/2]得到下一步的余数mod[i]
mod[i]=(mod[i/]*+i%)%n;
//mod[i/2]*10+i%2模拟了BFS的双入口搜索
//当i为偶数时,+0,即取当前位数字为0 。为奇数时,则+1,即取当前位数字为1 i--;
int pm=;
while(i)
{
mod[pm++]=i%; //把*10操作转化为%2操作,逆向求倍数的每一位数字
i/=;
}
while(pm)
cout<<mod[--pm]; //倒序输出
cout<<endl;
}
return ;
}

3.POJ 3087 Shuffle'm Up:http://poj.org/problem?id=3087

题意:

已知两堆牌s1和s2的初始状态, 其牌数均为c,按给定规则能将他们相互交叉组合成一堆牌s12,再将s12的最底下的c块牌归为s1,最顶的c块牌归为s2,依此循环下去。

现在输入s1和s2的初始状态 以及 预想的最终状态s12

问s1 s2经过多少次洗牌之后,最终能达到状态s12,若永远不可能相同,则输出"-1"。

思路:从题意来看,直接模拟就好了,用不到BFS。好吧,我也的确直接用模拟做了,

附上代码:

 #include<stdio.h>
#include<string.h>
int main()
{
int t,l,i,j;
char s1[],s2[],s[],s12[],s3[];
scanf("%d",&t);
for(j=; j<=t; j++)
{
int ans=;
scanf("%d",&l);
getchar();
gets(s1);
gets(s2);
gets(s12);
strcpy(s,s1);
strcpy(s+l,s2);
s3[]='\0';
while(strcmp(s3,s12)!=)
{
for(i=; i<l; i++)
{
s3[i*+]=s1[i];
s3[i*]=s2[i];
}
s3[*l]='\0';
ans++;
if(strcmp(s3,s)==)
{
ans=-;
break;
}
for(i=; i<l; i++)
s1[i]=s3[i];
strcpy(s2,s3+l);
}
printf("%d %d\n",j,ans);
}
return ;
}

4.POJ 2488 A Knight's Journey:http://poj.org/problem?id=2488

题意:

马从A1开始走“日”,如果能走遍棋盘上所有点,输出路径,否则,输出impossible.

思路:

定义了一个结构体变量,存该step时的位置。

int dir[8][2]={{-2,-1},{-2,1},{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1}};可以保证最后按字典序输出。
做的时候还是没按字典序输出,最后发现是行和列搞反了。
附上代码:
 #include<stdio.h>
#include<string.h>
struct node
{
char c;
char z;
}move[];
int n,m;
int dir[][]={{-,-},{-,},{-,-},{-,},{,-},{,},{,-},{,}};
int visit[][];
int flag;
int dfs(int x,int y,int step)
{
int xx,yy,i;
if(step==n*m)
{
flag=;
return ;
}
for(i=; i<; i++)
{
xx=x+dir[i][];
yy=y+dir[i][];
if(xx>= && xx<=m&& yy>= && yy<=n&& !visit[xx][yy])
{
move[step].c=xx-+'A';
move[step].z=yy+'';
visit[xx][yy]=;
dfs(xx,yy,step+);
if(flag)
return ;
visit[xx][yy]=;
}
}
return ;
}
int main()
{
int t,i,k;
scanf("%d",&t);
for(k=; k<=t; k++)
{
flag=;
memset(visit,,sizeof(visit));
memset(move,,sizeof(move));
scanf("%d%d",&n,&m);
visit[][]=;
move[].c='A';
move[].z='';
printf("Scenario #%d:\n",k);
if(dfs(,,))
{
for(i=;i<n*m;i++)
{
printf("%c%c",move[i].c,move[i].z);
}
}
else
printf("impossible");
printf("\n\n");
}
return ;
}

总结完了。。弱菜要去看昨晚Bestcoder的题了,噶呜~

												

最新文章

  1. Jquery 实现 “下次自动登录” 记住用户名密码功能
  2. Android三种基本的加载网络图片方式(转)
  3. (WCF) WCF Service Hosting.
  4. java 回传参数
  5. ARCGIS二维三维放大缩小
  6. location对象,将url解析为独立片段search属性截取传递的参数
  7. 使用Python在2M内存中排序一百万个32位整数
  8. php5.4下配置zend guard loader
  9. 用超链接a来提交form表单
  10. pyqt5 在qt designer后以弹窗的方式连接多个UI图形界面
  11. C# into子句
  12. bzoj3198[Sdoi2013]spring 容斥+hash
  13. Web版记账本开发记录(三)开发过程遇到的问题小结2
  14. Sqlite3并发读写注意事项
  15. 5.原型模式(Prototype)
  16. IO流的分类
  17. vue与mapbox
  18. lvm磁盘分区
  19. Navicat备份、还原mysql数据库
  20. Spring+Mybatis整合过程中找不到.properties文件

热门文章

  1. javascript每日一练(六)——事件一
  2. java--join方法
  3. 基于visual Studio2013解决C语言竞赛题之0701排队输出
  4. CentOS6使用第三方yum源安装更多rpm软件包
  5. 关于js基础easy忘记的那些事儿
  6. spring利用扫描方式对bean的处理(对任何版本如何获取xml配置信息的处理)
  7. Android UI 之WaterFall瀑布流效果
  8. lua 与 php 通过AES数据加密进行通讯
  9. sql: DUAL
  10. tq2440+fedora安装qt4.5