2015信息学夏令营第1、2……天——搜索与递归

8 篇文章 0 订阅
4 篇文章 0 订阅


7-26,是我们2015年信息学夏令营的第一天,我们主要就是学习了搜索。

 

=================================================

 搜索又分宽度搜索与深度搜索。这两个搜索又有什么区别呢?深度搜索是指深度优先搜索,宽度搜索就是宽度优先搜索。

搜索主要是看搜索树:

     2015信息学夏令营第1、2……天——搜索与递归 - 周正华 - 周正华的博客

 

           ——这颗(搜索)树的开始的点就是源点。

          

小技巧:当一颗搜索树有多个源点时,可以把它们的根连到一个虚点,然后把这个虚点作为总连接点。

 

深度搜索就是从第一个(源点)一直找到底,然后再返回到最近的一个分支,再继续深度搜索,然后再返回······以此类推。

      宽度搜索就是从原点开始,把每一层的点都按顺序找,一直到最后一层。

=================================================

      我们这两天主要学的是深度搜索。

      深度搜索有两种实现方法:一是栈,二就是我们常用的递归。

      栈本身就跟深度搜索很相似,所以实现起来也应该比较容易理解,但是我们没有深入研究,那就等下次再写出来吧。栈大概就像叠盘子,后进来的,放在最上面;出去的也是最上面的先出去。如图:

     2015信息学夏令营第1、2……天——搜索与递归 - 周正华 - 周正华的博客

=================================================

      

那现在就讲一下递归吧。递归就是一个函数(子程序)自己调用自己。也就是这样:

int find(int k)

{

      ……

      k=find(k+1);  

}


可以看到,在这个find函数中,自己调用了自己,使这个程序又开始执行find函数。需要注意的是,在递归过程中,程序会有执行另外一个find,也就是说如果你不断调用自己,那么程序会不断出现新的find函数。进一步说,如果程序无限调用自己,那么就会导致栈溢出,从而使程序崩溃——就像是for或者while的死循环一样。所以递归必须有一个边界条件,当执行到某个程度时,就退出当前的函数。

像这样:

int find(int k)

{

if(k==100) return ;/*边界条件,当k=100时return*/

find(k+1);

}


可以看到这个程序就用到了边界条件,停止递归。

注意:当你使用递归时,新函数中的变量与你一开始那个函数出现的变量不一样。也就是说,上面程序中的一开始的k其实一直都没有改变,只是新函数中传进来的k改变了。

 

因为使用递归最好的方法就是先找出问题中的子问题。什么是递归的子问题呢?举一个例子吧:

2015信息学夏令营第1、2……天——搜索与递归 - 周正华 - 周正华的博客

      我们知道,上图中的“11、“2112、“321123都是回文数,即正过来读与倒过来读都一样。我们可以看到,“11在“2112的中间出现了,换句话说就是第2行比第1行两边多了2;同理,第3行又比第2行两边多了3。由此我们可以推断出回文数的第n行比第(n-1)行两边多了n,也就是f(n)=n f(n-1) n。这些“11,“2112等小问题就是递归的子问题。找到这些子问题,实现递归起来自然就比较容易了。

=================================================

      那么我们如何通过递归实现搜索呢?下面我们就一个程序来展示出来:

      要求打印出“1,2,3,4的全排列。

     

#include <iostream>

#include <fstream>

using namespace std;

 

ifstream fin("qpl.in");

ofstream fout("qpl.out");

#define cout fout

#define cin fin

 

int n;

bool f[1000];

int ans[1000];

 

int gcd(int a,int b)

{

      if(a%b==0) return b;

      return gcd(b,a%b);

}

 

void _print()

{

      for(int i=1;i<=n;i++)

           cout<<ans[i]<<" ";

      cout<<endl;

}

 

void search(int m)

{

      if(m>n)

      {

           _print();

           return ;

      }

     

      for(int i=1;i<=n;i++)

      {

           if( m==1 || ( m>1 && gcd(i,ans[m-1])==1 ))

                 if(f[i]==0)

                 {

                      f[i]=1;

                      ans[m]=i;

                      search(m+1);

                      f[i]=0;

                 }

      }

}

 

int main()

{

      cin>>n;

      search(1);

     

      return 0;

}


重点是看search(递归)这一段。

void search(int m)     /*dfs*/

{

      if(m>n)                 /*边界条件*/

      {

           _print();

           return ;

      }

     

      for(int i=1;i<=n;i++)

      {

           if( m==1 || ( m>1 && gcd(i,ans[m-1])==1 ))      /* 符合搜索的条件*/

                 if(f[i]==0)

                 {

                      f[i]=1;             /* 标记为用过 f表示这个数是否用过)*/

                      ans[m]=i;        /*把答案记录下来 */

                      search(m+1); /* 递归----自己调用自己*/

                      f[i]=0;            /*还原 */

                 }

      }

}

=================================================

我们还扯到了一点关于memset的知识,所以有几点要注意的:

   memset需要包含<cstring>库。因为有些编译器会自动包含一些常用哭,所以你在自己运行时即使没包含这个库也不会出错。但是一到测试软件时就直接out了。

   当你用memset初始化一个int类型的数组时,你最好就是把他初始化为0-1。若你要初始化成1的话,如图所示:

2015信息学夏令营第1、2……天——搜索与递归 - 周正华 - 周正华的博客

 

      那究竟是为什么呢?我们来分析一下:int4字节,所以这个a数组有400字节,也就是说sizeof(a)400。那么这个a数组中每个元素中的4个单元都会分别填充,而且还是以一种奇怪的方式填充:

      这个奇怪的方式是什么呢,因为我还在改题,那就下次在继续写吧。顺便说一下,我们还学了宽搜呀,位操作呀什么的。好,下次再写!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值