算法-回溯

回溯是一个非常重要的求解策略,简单来说就是通过不断的试探,返回来找到可行解.

(在java中,新建一个整数数组,其元素值默认为0)

一.迷宫问题
在迷宫问题中,我们用一个M*N的矩阵表示地图,0表示可行,1表示阻塞.
回溯算法的描述1,使用栈:
用-1表示该点已经查询过.假设地图周围有一圈不可走的封闭的墙 (全部=1 )
从一个起点开始,按顺时针方向查询该点下一个可走的范围,一共有四种,上(0),右(1),下(2),左(3).为了能够在找不到可走方向后原路返回,
我们用一个栈保存搜寻的路径.如果找到了下一个可走的范围,将下一个点入栈.循环执行这一系列步骤:
1.取栈顶元素;
2.判断该点是否为出口,若是,输出栈中保存的路径,程序结束;否则,下一步;
3.搜寻该点的下一个可走方位,若找得到,将下一个点入栈,同时将该点修改为已搜寻;否则,出栈当前点,修改当前点为未搜寻;
4.跳到第一步;
示例引用自教材,说明,在这里定义了一个类用来表示栈,其实这不是必须的,如果你理解栈的运作的话,你仅仅只需一个数组和一个栈顶指针就足以.
源代码:
public class MazeProblemOne {
 static int MaxSize=1000;
 public static void main(String[] args) {
  int M=8,N=8;
  int[][] mg=new int[][]{
   {1,1,1,1,1,1,1,1,1,1},
   {1,0,0,1,0,0,0,1,0,1},
   {1,0,0,1,0,0,0,1,0,1},
   {1,0,0,0,0,1,1,0,0,1},
   {1,0,1,1,1,0,0,0,0,1},
   {1,0,0,0,1,0,0,0,0,1},
   {1,0,1,0,0,0,1,0,0,1},
   {1,0,1,1,1,0,1,1,0,1},
   {1,1,0,0,0,0,0,0,0,1},
   {1,1,1,1,1,1,1,1,1,1}
  };
  getMazePath(mg,1,1,M,N);
 }
 public static boolean getMazePath(int mg[][],int starti,int startj,int endi,int endj){
  Stack stack=new Stack(MaxSize);
  
  int num=0;
  int min=10000;
  int minPath=1;
  //起点入栈
  Point p=new Point(starti,starti,-1);
  stack.push(p);mg[starti][startj]=-1;
  boolean find=false;
  while(!stack.isEmpty()){
   Point curP=stack.getTop();
   //System.out.print("("+curP.i+","+curP.j+") ");
   if(curP.i==endi&&curP.j==endj){
    num++;
    System.out.println("\nPath "+num+":");
    int length=stack.print();
    if(length<min){
     minPath=num;
     min=length;
    }
    stack.pop();
    mg[curP.i][curP.j]=0;
    continue;
    
    /*1.列出一条路径后退出
     * return true;
     */
    /*2.列出所有可能的路径
     * stack.pop();
     mg[curP.i][curP.j]=0;
     continue;
     */
    /*3.列出最短路径
     *  int length=stack.print();
     *  if(length<min) Point[] points=stack.getPaths();
     *  print(points);
     */
    /
   }
   find=false;
   int i=0,j=0;
   while(curP.nextDir<4&&!find){
    curP.nextDir++;
    switch(curP.nextDir){
    case 0:i=curP.i-1;j=curP.j;break;
    case 1:i=curP.i;j=curP.j+1;break;
    case 2:i=curP.i+1;j=curP.j;break;
    case 3:i=curP.i;j=curP.j-1;break;
    }
    if(mg[i][j]==0)find=true;
   }
   if(find){
    stack.push(new Point(i,j,-1));
    mg[i][j]=-1;
    find=false;
   }else{
    stack.pop();
    mg[curP.i][curP.j]=0;
   }
  }
  System.out.println("\n最短路径编号:"+minPath);
  return false;
 }
}
//表示点
class Point{
 public Point(){}
 public Point(int i,int j,int nextDir){ this.i=i;this.j=j;this.nextDir=nextDir;}
 public int i;
 public int j;
 public int nextDir;
}
//表示栈
class Stack{
 private Point[] points;
 private int top;
 public Stack(int MaxSize){
  points=new Point[MaxSize];
  top=-1;
 }
 public boolean isEmpty(){ return top<=-1; }
 public boolean isFull(){ return top>=points.length-1;}
 public boolean push(Point p){
  if(!isFull()){
   top++;
   points[top]=p;
   return true;
  }
  return false;
 }
 public Point pop(){
  Point p;
  if(!isEmpty()){
   p=points[top];
   top--;
   return p;
  }
  return null;
 }
 public Point getTop(){
  if(!isEmpty()) return points[top];
  else return null;
 }
 public int size(){return top+1;}
 //打印数据
 public int print(){
  for(int i=0;i<size();i++){
   System.out.print("("+points[i].i+","+points[i].j+") ");
   if((i+1)%8==0)System.out.println();
  }
  return size();
 }
}



---------------------------------------------------
八皇后问题
八皇后也是个利用回溯策略的例子,非递归的回溯.网上已经有很多程序代码了.
这里我介绍一个很巧妙地方法-用来判断当前位置是否可以放置皇后.
用四个数组分别表示行,列,正对角线,反对角线,是否被占领.
实际上在求解时,你肯定是按行或者按列求解的,那么实际上就只需要三个数组了.
因为如果按行或者按列求解时,下一个皇后肯定要在下一行或者下一列,所以这点就不用考虑了.
你需要计算一下,三个数组的大小,也就是对角线有多少条.
好了,上代码吧!(java):

public class EightQueens2 {
 public static void main(String[] args) {
  fun2(0);
  System.out.println("n="+N+":number="+number);
 }
 static int number=0;
 static int N=8;
 static int[] a=new int[N];//保存皇后位置,横坐标
 //这里用三个数组表示,行,斜对角线是否有值.
 static int[] b=new int[N];//该行是否被占领
 static int[] c=new int[N*2-1];//正对角线
 static int[] d=new int[N*2-1];//反对角线
 //递归回溯:以列为推进顺序
 public static void fun2(int j){
  if(j>=N)return ;
  int i=0;
  for(;i<N;i++){
   if(b[i]==0&&c[i+j]==0&&d[j-i+N-1]==0){
    a[j]=i;//保存结果
    b[i]=1;c[i+j]=1;d[j-i+N-1]=1;//占领行列和对角线
    if(j==N-1){
     number++;
     print();
     //回溯:找到了一个解,为了找到其他解,假设该解不可行,将其还原
     a[j] = 0;
     b[i] = 0;
     c[i + j] = 0;
     d[j - i + N - 1] = 0;
     continue;
    }
    fun2(j + 1);
    //回溯:下一列找不到可以放置皇后的位置了,当前列位置不行,继续下一个位置
    a[j] = 0;
    b[i] = 0;
    c[i + j] = 0;
    d[j - i + N - 1] = 0;
   }
  }
 }
 public static void print(){
  for(int i=0;i<a.length;i++){
   System.out.print(a[i]+" ");
  }
  System.out.println();
 }
}



输出格式:
......
......
7 1 4 2 0 6 3 5
7 2 0 5 1 4 6 3
7 3 0 2 5 1 6 4
n=8:number=92

相信这两个问题可以让你更好地理解回溯.




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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值