八皇后和汉诺塔问题

汉诺塔问题 

 

 三个盘子的汉诺塔问题 需要7步。怎么移动大家都清楚。

四个盘子的汉诺塔问题 需要15步 怎么分析呢,把中间的看作目标柱子,把最大的移到右边,然后就是和三个盘子的是一样的分析了

a4=a3+a3+1.其实我们分析汉诺塔问题可以看作第n个和前n-1个两部分,一共就三个步骤:

把n-1个盘子移动到缓冲区。

把第n个盘子移动到终点。

然后把缓冲区的n-1个盘子也移动到终点。

else
  {
    hannuotower(n-1,begin,end,buffer);
    //把前n-1个移动到缓冲区(此时end相当于缓冲区)
    hannuotower(1,begin,buffer,end);
    //把第n个移动到终点(此时buffer相当于缓冲区)
    hannuotower(n-1,buffer,begin,end);
    //把剩下的n-1从缓冲区移动到终点(此时begin相当于缓冲区)
  }

所以这就是它的回溯步骤我个人觉得是看作两个整体来能简单易懂点,硬推公式的话感觉有点僵硬。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
void hannuotower(int n,char begin,char buffer,char end)
{
  if(n==1)
  {
    cout<<"Move "<<begin<<" to "<<end<<endl;
  }
  else
  {
    hannuotower(n-1,begin,end,buffer);
    //把前n-1个移动到缓冲区(此时end相当于缓冲区)
    hannuotower(1,begin,buffer,end);
    //把第n个移动到终点(此时buffer相当于缓冲区)
    hannuotower(n-1,buffer,begin,end);
    //把剩下的n-1从缓冲区移动到终点(此时begin相当于缓冲区)
  }
}
int main()
{
  int n;
  while(cin>>n)
  {
    cout<<pow(2,n)-1<<endl;
    hannuotower(n,'A','B','C');
  }
  return 0;
}

至于最后的那个2^n-1怎么来的,可以自己试着推一下,我太菜了,没有推出来,我把结论记下了。 

八皇后问题

八皇后问题历来是回溯中的必讲问题,以前学的时候感觉思想看不懂,现在感觉耳目一新。慢慢的就懂了,首先知道国际象棋的规则,皇后的功能和中国象棋中的车不一样,和皇后同一行,同一列,同一斜线的都会被皇后吃掉,所谓的八皇后问题就是将八位皇后放在一个8*8的格子里面,使得每位皇后都无法吃掉别的皇后,即任意两个皇后都不能在同一竖线,横线和斜线上,问一共有多少种摆法?

解决方法: 首先我么会想到暴力枚举的方法,不在同一行同一列同一斜线,从8*8的格子里面选择8个位置,放皇后,然后测试是否满足条件,满足条件,则结果加1,否则换个格子继续尝试,很显然C64取8种,大概计算结果为4.426*10^9,是一个十亿级的数字。

稍加分析,我们可以得到一个不是特别暴力的方法,第一行选择一个位置为(1,i)那么很显然,第二个皇后不能放在第一行在第二行,假设为(2,j),很明显i!=j,那么就有7种选择方法,依次推论,我们可以知道,最终的方案数大概有8!=40320种,很显然比第一种方法好了很多。但是尝试的方法依然是按照阶乘提出来的,时间复杂度依然很高,于是。递归回溯的方法被提出来,回溯这种思想我个人觉得不好去掌握。

为了去更好的理解八皇后问题,我们可以先想想四皇后问题。

现在我们把第一个皇后放在第一个格子,被涂黑的地方是不能放皇后的:

放第1个皇后

第二行的皇后只能放在第三格或第四格,比如我们放在第三格:

放第2个皇后

这样一来前面两位皇后已经把第三行全部锁死了,第三位皇后无论放在第三行的哪里都难逃被吃掉的厄运。所以我们应该是第二个皇后放错了位置,那么我们可以换一种方法去放置。

来给2号皇后换个位置:

给2号皇后换个位置

此时,第三个皇后只有一个位置可选,如果第三位皇后放在蓝色空位上,那么我们第四个皇后就没有地方放了,所以这种方法依然不行,那么我们继续换一种方法,我们现在能改的就只有第一个皇后的位置了则,因为第四个不能安放,则需要返回上层调整3号皇后,而3号皇后也别无可去,继续返回上层调整2号皇后,而2号皇后已然无路可去,则再继续返回上层调整1号皇后,1号皇后往后移一格位置如下,再继续往下安排:

回溯重新安排1号皇后

而这种放置方法很容易就能推出正确的放置方法 

下面我们说说实现过程,反正回溯的代码想不通的话多想几遍,然后自己整理一下思路就可以。

void findqueue(int row)
{
  if(row>7)
  {
    ans++;
    printqueen();
    return;
  }
  for(int column=0;column<8;column++)
  {
    if(check(row,column))
    {
      map[row][column]=1;
      findqueue(row+1);
    //  map[row][column]=0;
    }
      map[row][column]=0;
  }
}

这段代码虽短,但真的非常非常重要,是整个算法的核心和灵魂):

第一次进来,row=0,意思是要在第一行摆皇后,只要传进来的row参数不是8,表明还没出结果,就都不会走if里面的return,那么就进入到for循环里面,column从0开始,即第一列。此时第一行第一列肯定合乎要求(即check方法肯定通过),能放下皇后,因为还没有任何其他皇后来干扰。

关键是check方法通过了之后,在if里面又会调用一下自己(即递归),row加了1,表示摆第二行的皇后了。第二行的皇后在走for循环的时候,分两种情况,第一种情况:for循环没走到头时就有通过check方法的了,那么这样就顺理成章地往下走再调用一下自己(即再往下递归),row再加1(即摆第三行的皇后了,以此类推)。第二种情况:for循环走到头了都没有通过check方法的,说明第二行根本一个皇后都摆不了,也触发不了递归,下面的第三行等等后面的就更不用提了,此时控制第一行皇后位置的for循环column加1,即第一行的皇后往后移一格,即摆在第一行第二列的位置上,然后再往下走,重复上述逻辑。

注意,一定要添加清零的代码,它只有在皇后摆不下去的时候会执行清0的动作(避免脏数据干扰),如果皇后摆放很顺利的话从头到尾是不会走这个请0的动作的,因为已经提前走if里面的return方法结束了。

我的理解:首先我们来看一下递归出口递归出口是row>7代表8行已经放置好位置,直接输出ans就可以。我们从row=0开始分析,row=0,column=0,此时第一行第一列肯定满足要求,(满足check函数)进入if语句,如果满足check函数,则标记这个位置已经走过,接下来再次递归row+1,即第走到第二行,不会进行清零操作,依次论推,如果说check不满足条件的话,则说明这一列不可以,那么column++,直到满足条件为止进入这个if语句里面,调用自己然后行数加一,如果说for循环走到头了都没有通过check方法的,则说明这一行不行,我们可以假设这个是第二行,也就是说走到头第二行都没有合适的位置放皇后,也触发不了递归,下面的第三行以及后面的所有行都不用提了,此时控制第一行皇后位置的for循环column加1,即第一行的皇后往后移一格,看不懂的话可以模拟下四皇后的走法。

但是我有几个疑惑?

第一点,row=0,col=0满足条件,进入if语句,递归调用,row+1.

第二点 row=1,col=0;不满足条件,col++,直到满足check条件然后进入循环。

第三点 假如for循环走完的话,也没有一列是满足条件的,我们假设第二行没有合适位置,那么说明第一行的起始位置选择错误,那么我们起始位置为第一行第二列,依次进行下面的操作似乎感受到了回溯的魅力。

第四点 也就是我的疑惑点,清零操作,皇后摆不下去进行清0操作,但是不是摆不下去根本进入不了这个if语句?如何清零,而且清零的摆放位置是在循环内还是循环外,感觉运行结果并没有什么区别??有啥区别??????

回来问了舍友终于懂了,

for(int column=0;column<8;column++)
  {
    if(check(row,column))
    {
      map[row][column]=1;
      findqueue(row+1);
      map[row][column]=0;//我一直觉得这句话没有用啊,你都满足check语句了,你当然合法了就会一直row+1;

     //但是我想错了,就是上一行一个位置你满足了check条件,然后row+1,但是row+1你发现没有合适的位置可以放置这个皇后,        即这一行没有一个合法的位置可以去放置皇后,那么说明我们上一个位置虽然满足check条件但是放置是错误的,此时就会进        行清0操作重新找另一个适合的位置,然后我们继续操作。这就是清0的含义。
    }
    //  map[row][column]=0;清零放在这一块的作用和上一处的作用有点区别但是总体区别不大,这个是对每一个不合法的位置进        行清0,操作的清0次数可能比放在里面多一些,但是总体差别不大。
  }

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=10;
int map[maxn][maxn];
int ans;
void printqueen()
{
  cout<<ans<<endl;
  for(int i=0;i<8;i++)
  {
    for(int j=0;j<8;j++)
    {
      if(map[i][j]==1)
      {
        cout<<"o ";
      }
      else
      {
        cout<<"+ ";
      }
    }
    cout<<endl;
  }
}
bool check(int k,int j)
{
  for(int i=0;i<8;i++)//检查行列冲突
  {
    if(map[i][j]==1)
      return false;
  }
  for(int i=k-1,m=j-1;i>=0&&m>=0;i--,m--)//检查左对角线
  {
    if(map[i][m]==1)
      return false;
  }
  for(int i=k-1,m=j+1;i>=0&&m<=7;i--,m++)
  {
    if(map[i][m]==1)
      return false;
  }
  return true;
}
void findqueue(int row)
{
  if(row>7)
  {
    ans++;
    printqueen();
    return;
  }
  for(int column=0;column<8;column++)
  {
    if(check(row,column))
    {
      map[row][column]=1;
      findqueue(row+1);
      map[row][column]=0;
    }
    //  map[row][column]=0;
  }
}
int main()
{
  findqueue(0);
  return 0;
}

终于算是搞懂八皇后和汉诺塔了,一年前一脸懵逼,现在完美解决。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值