深度优先搜索和广度优先搜索法

在这里介绍两种基本的搜索算法:深度优先搜索和广度优先搜索法,以树的搜索为例,深度优先搜索法是优先扩展尚未扩展的且具有最大深度的结点;广度优先搜索法是在扩展完第K层的结点以后才扩展K+1层的结点。
深度优先搜索法与前面讲的回溯法差不多,主要的区别是回溯法在求解过程中不保留完整的树结构,而深度优先搜索则记下完整的搜索树,搜索树起记录解路径和状态判重的作用。为了减少存储空间,在深度优先搜索中,用标志的方法记录访问过的状态,这种处理方法使得深度优先搜索法与回溯法没什么区别了。在回溯法中,我们己分析了非递归的实现过程,在这里就只讨论深度优先的递归实现方法。
深度优先搜索的递归实现过程:
procedure dfs(i);
  for i:=1 to r do
if 子结点mr符合条件then 产生的子结点mr入栈;
  if 子结点 mr 是目标结点 then  输出
 else dfs(i+1);
栈顶元素出栈(即删去mr);
endif;
endfor;
在讲解递推法时,我们讨论了用递推法解骑土游历问题,在这里我们再看看如何用深度优先搜索法求解此题。
例1骑士游历:设有一个n*m的棋盘,在棋盘上任一点有一个中国象棋马,马走的规则为:1.马走日字  2.马只能向右走。当N,M 输入之后,找出一条从左下角到右上角的路径。例如:输入 N=4,M=4,输出:路径的格式:(1,1)->(2,3)->(4,4),若不存在路径,则输出"no"
算法分析:我们以4×4的棋盘为例进行分析,用树形结构表示马走的所有过程(如下图),求从起点到终点的路径,实际上就是从根结点开始深度优先搜索这棵树。
马从(1,1)开始, 
按深度优先搜索法,走一步到达(2,3),判断是否到达终点,若没有,则继续往前走,再走一步到达(4,4),然后判断是否到达终点,若到达则退出,搜索过程结束。为了减少搜索次数,在马走的过程中,判断下一步所走的位置是否在棋盘上,如果不在棋盘上,则另选一条路径再走。
程序如下:
const
  dx:array[1..4]of integer=(2,2,1,1);
  dy:array[1..4]of integer=(1,-1,2,-2);
type
  map=record
        x,y:integer;
      end;
var
  i,n,m:integer;
  a:array[0..50]of map;
procedure dfs(i:integer);
var j:integer;
begin
  for j:=1 to 4 do
if (a[i-1].x+dx[j]>0)and(a[i-1].x+dx[j]<=n) and(a[i-1].y+dy[j]>0)and(a[i-1].y+dy[j]<=n) then{判断是否在棋盘上}
    begin
      a[i].x:=a[i-1].x+dx[j];
      a[i].y:=a[i-1].y+dy[j];{入栈}
      if (a[i].x=n)and(a[i].y=m)then
      begin
        write('(',1,',',1,')');
        for j:=2 to i do write('->(',a[j].x,',',a[j].y,')');
        halt;{输出结果并退出程序}
      end;
        dfs(i+1);{搜索下一步}
        a[i].x:=0;a[i].y:=0;{出栈}
    end;
end;
begin
  a[1].x:=1;a[1].y:=1;
  readln(n,m);
  dfs(2);
  writeln('no');
end.
从上面的例子我们可以看出,深度优先搜索算法有两个特点:
1、己产生的结点按深度排序,深度大的结点先得到扩展,即先产生它的子结点。
2、深度大的结点是后产生的,但先得到扩展,即“后产生先扩展”,与栈的工作原理相同,因此用堆栈作为该算法的主要数据结构,存储产生的结点。
对于不同的问题,深度优先搜索算法基本上是一样的,但在具体处理方法和编程技巧上又都不相同,甚至会有很大的差别。我们再看看另一个例子。
题二 选数(存盘名:NOIP2002pj)
[问题描述]:已知 n 个整数 x1,x2,…,xn,以及一个整数 k(k<n)。从n 个整数中任选 k 个整数相加,可分别得到一系列的和。例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:3+7+12=22  3+7+19=29  7+12+19=38  3+12+19=34。现在,要求你计算出和为素数共有多少种。例如上例,只有一种的和为素数:3+7+19=29。
[输入]:键盘输入,格式为:
  n , k (1<=n<=20,k<n)
  x1,x2,…,xn (1<=xi<=5000000)
[输出]:屏幕输出,格式为:
  一个整数(满足条件的种数)。
[输入输出样例]:
输入:4 3
     3 7 12 19
输出:1
算法分析:本题是求从n个数中选k个数的组合,并使其和为素数。求解此题时,先用深度优先搜索法生成k个数的组合,再判断k个数的和是否为素数,若为素数则总数加1。
在程序实现过程中,用数组a存放输入的n个数,用s表示k个数的和,ans表示和为素数的个数。为了避免不必要的搜索,程序对搜索过程进行了优化,限制搜索范围,在搜索过程dfs(i,m)中,参数m为第i个数的上限,下限为n-k+i。
源程序:
var
  n,k,i: byte;
  ans,s:longint;
  a: array[1 .. 20] of Longint;
procedure prime(s:longint);{判断K个数的和是否为素数}
var
  i:integer;
begin
  i:=2;
  while (sqr(i)<=s)and(s mod i<>0) do inc(i);
  if sqr(i)>s then  inc(ans){若为素数则总数加1}
end;
 
procedure dfs(i,m:byte);{搜索第i个数, }
var
  j:byte;{j表示第i个数的位置
begin
  for j:=m to n-k+i do{枚举第i个数}
  begin
    inc(s,a[j]);{入栈}
    if i=k then prime(s)
     else dfs(i+1,j+1);{继续搜第i+1个数}
    dec(s,a[j]){出栈}
  end
end;
begin
  readln(n,k);
  for i:=1 to n do read(a[i]);
  ans:=0; s:=0;
  dfs(1,1);
  writeln(ans);
end.
从上面的两个例子我们可以看出,用递归实现深度优先搜索比非递归更加方便。
在使用深度搜索法解题时,搜索的效率并不高,所以要重视对算法的优化,尽可能的减少搜索范围,提高程序的速度。
在下一篇中将继续介绍另一种搜索方法——广度优先搜索法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值