回溯是一个非常重要的求解策略,简单来说就是通过不断的试探,返回来找到可行解.
(在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
相信这两个问题可以让你更好地理解回溯.