回溯算法---过河问题(商人过河)

过河问题:
  
  有三个商人和三个仆人过河,只有一条能装下两个人的船,在河的任何一岸上,如果仆人的人数大于商人的人数,那么该岸上的商人就会有危险。你能不能找出一种安全的渡河方法呢?
  
  过河问题是一个比较出名的问题,借解这个问题的同时,我们来讲讲回溯算法.
  首先,我们来分析下问题,商人们要过河,无非就是实现从全部未过河的状态到全部过河的状态.那么,问题可以转变为这样:
  左岸:商人3,仆人3=>右岸:商人3,仆人3.即要通过一定的步骤实现状态的转变。而每一个状态(每次都是指船到岸,并不包含船在水中央时的情况)包含有如下几个因素:
  1.左岸商人数;
  2.左岸仆人数;
  3.船在哪岸,即轮到哪岸的人上船.
  至于右岸的商人数和仆人数到底是多少,我们不必再记上,因为知道了左岸的人,就可以推算出右岸的人。这样分析,问题便变为:
  (3,3,1)=?=>(0,0,0).其中括号描述如下:(左岸商人数,左岸仆人数,船在左岸?1:0).
  
  接下来分析商人有危险的状态。可设一个matrix[4][4]的布尔型数组。matrix[i][j]表示i个商人,j个仆人时是否安全的信息。判断是否安全的标准为哪岸的仆人数大于商人数,这岸的商人就会有危险(当然,哪一岸商人为0时,也是没危险的).用两重循环,可很快解决问题:
  

for(int i=0;i<4;i++)
   for(int j=0;j<4;j++)
   {
   if(i==j||i==0||i==3);
   else matrix[i][j]=true;/*其中为真时,表示有危险.*/
   }


  
  再来分析人可以乘船的方案:以(商人数,仆人数)来表示,很明显,乘船的方案为如下5种:{0,2},{1,1},{2,0},{0,1},{1,0}.
  接着就是想算法了.一开始状态为(3,3,1),通过5种乘船方案,可转变为5种状态:(3,1,0),(2,2,0),(1,3,0),(3,2,0), (2,3,0).其中matrix[i][j]为true或者不合理或者重复状态的去掉,继续向下拓展.直到出现(0,0,0)为止.从表面上看,这样似乎并不好写。这就要用到了回溯算法.所谓回溯,就是寻找问题的解的一种可靠的方法是首先列出所有候选解,然后依次检查每一个,在检查完所有或部分候选解后,即可找到所需要的解。
  
  回溯方法的步骤如下:
  1) 定义一个解空间,它包含问题的解。
  2) 用适于搜索的方式组织该空间。
  3) 用深度优先法搜索该空间,利用限界函数避免移动到不可能产生解的子空间。
  
  回溯的这个深层搜索特性,便决定了它通常要用递归来做。应用到这题,显然递归的出口就是出现(0,0,0,)状态的时候。应用回溯的时候,通常有一个要特别要注意的地方,就是深入搜索完成的时候,要记得恢复前一步的状态,以便下一状态的转变。这在下面的程序会用黑体显现出来,这一点一定要注意,笔者自己也曾多次犯此错误。
  现把这个问题的全部代码附上:
  

#i nclude 
  #i nclude 
  using namespace std;
  bool matrix[4][4],turn;
  int record[128][2],pos=1;
  bool check[128];
  /*判断这一状态是否在前面已经出现过*/
  bool isRepeat(int x,int y,bool turn)
  {
   for(int i=0;i<POS;I++)
   if(x==record[i][0]&&y==record[i][1]&&turn==check[i])
   return true;
   return false;
  }
  void search(int px,int py)
  {
   static int goStep[5][2]={{0,2},{1,1},{2,0},{0,1},{1,0}};
   int i;
   if(px==0&&py==0)
   {
   for(i=0;i<POS;I++)
   {
   cout<<"("<<RECORD[I][0]<<","<<RECORD[I][1]<<
   ")<==>("<<3-record[i][0]<<","<<3-record[i][1]<<")"<<ENDL;
   }
   system("pause");
   }
   else if(turn==false)
   {
   for(i=0;i<5;i++)
   {
   px-=goStep[i][0];
   py-=goStep[i][1];
   if(px<0||px>3||py<0||py>3||matrix[px][py]||isRepeat(px,py,turn))
   {
   px+=goStep[i][0];
   py+=goStep[i][1];
   continue;
   }
   record[pos][0]=px,record[pos][1]=py;
   check[pos]=turn;
   pos++;
   turn=(turn==true?false:true);
   search(px,py);
   pos--;
   turn=(turn==true?false:true);
   px+=goStep[i][0];
   py+=goStep[i][1];
   }
   }
   else
   {
   for(i=0;i<5;i++)
   {
   px+=goStep[i][0];
   py+=goStep[i][1];
   if(px<0||px>3||py<0||py>3||matrix[px][py]||isRepeat(px,py,turn))
   {
   px-=goStep[i][0];
   py-=goStep[i][1];
   continue;
   }
   record[pos][0]=px,record[pos][1]=py;
   check[pos]=turn;
   pos++;
   turn=(turn==true?false:true);
   search(px,py);
   pos--;
   turn=(turn==true?false:true);
   px-=goStep[i][0];
   py-=goStep[i][1];
   }
   }
  }
  int main()
  {
   int px=3,py=3;
   for(int i=0;i<4;i++)
   for(int j=0;j<4;j++)
   {
   if(i==j||i==0||i==3);
   else matrix[i][j]=true;
   }
   record[0][0]=record[0][1]=3;
   check[0]=true;
   search(px,py);
   return 0;
  }

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值