常用十大算法_回溯算法

回溯算法

回溯算法已经在前面详细的分析过了,详见猛击此处。

简单的讲:

回溯算法是一种局部暴力的枚举算法

循环中,若条件满足,进入递归,开启下一次流程,若条件不满足,就不进行递归,转而进行上一次流程。这个过程称之为回溯

结构往往是在一个循环中,嵌套一个条件判断,条件判断中,嵌套一个自身递归。三层结构,相互嵌套,缺一不可

【例子】在一个7*7的棋盘中,指定某个起始位置,如何才能使遵循马走日规则的棋子,将36个棋盘位置全部走一遍,其走过的位置不能再走

算法分析:

马走日是规则,极限情况下,一个棋子有如下图8种可选方案,每种方案都对应着一个if语句,8种方案组成我们马走日的行走策略

常用十大算法_回溯算法_第1张图片

 采用回溯算法需要先找到当前棋子所有的可选方案(代码中由next方法实现),然后遍历每个方案,每个方案采取上述相同的流程,直到找到可以符合题目要求的行走路线,或者是被判定无法再行走而终止递归(代码中由 traversalChessboard 方法实现)

通过上述回溯算法就能完成马踏棋盘问题求解

代码实现:

package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.backtrace;

import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

public class 回溯算法_backtrace_马踏棋盘 {
    private static int X;//棋盘宽度
    private static int Y;//棋盘高度
    private static boolean[] visited;//用于记录棋牌被访问情况
    private static boolean finish=false;//用于记录是否完成马踏棋盘
    public static void main(String[] args) {
        //初始化数据
        X=7;
        Y=7;
        int row=3;//起始点为3行3列
        int col=3;
        visited=new boolean[X*Y];
        int[][] chessboard=new int[X][Y];
        long start=System.currentTimeMillis();//计时开始
        //调用马踏棋盘解决方法
        traversalChessboard(chessboard,row-1,col-1,1);//为了人机交互方便,需要减-1操作
        long end=System.currentTimeMillis();//计时结束
        System.out.println("运算总耗时:"+(end-start));;
        //输出结果
        for (int[] temp:chessboard){
            System.out.println(Arrays.toString(temp));
        }
    }

    /**
     * 马踏棋盘解决方案主体(回溯算法)
     * @param chessboard 棋盘
     * @param row 行数
     * @param col 列数
     * @param step 步数
     */
    public static void traversalChessboard(int[][] chessboard,int row,int col,int step){
        chessboard[row][col]=step;//将步数赋值到对应棋盘位置上
        visited[row*X+col]=true;//将该处设置为已访问(row被-1)
        //调用next来获得可选方案,注意next方法处理的时候先处理列,再处理行,所以传入的point对象为point(y,x)
        ArrayList allow = next(new Point(col,row));//用于存储目前可以进入的棋盘(存储可选方案)
        //回溯算法核心:回溯式的找到所有位置
        while (!allow.isEmpty()){//可选方案非空时
            Point p=allow.remove(0);//拿取可选方案中的头一个位置
            if (!visited[p.y*X+p.x]){//当该方案位置没有被访问时
                traversalChessboard(chessboard,p.y,p.x,step+1);//以方案位置再进行上述查找
            }
        }
        //终止回溯条件
        if (step next(Point curPoint){
        ArrayList allowPoint=new ArrayList<>();//存储当前位置可以进入的位置
        Point allow=new Point();
        //下面的策略是先计算列,再计算行
        //走位置5
        if ((allow.x=curPoint.x-2)>=0 && (allow.y=curPoint.y-1)>=0){
            allowPoint.add(new Point(allow));
        }
        //走位置6
        if ((allow.x=curPoint.x-1)>=0 && (allow.y=curPoint.y-2)>=0){
            allowPoint.add(new Point(allow));
        }
        //走位置7
        if ((allow.x=curPoint.x+1)=0){
            allowPoint.add(new Point(allow));
        }
        //走位置0
        if ((allow.x=curPoint.x+2)=0){
            allowPoint.add(new Point(allow));
        }
        //走位置1
        if ((allow.x=curPoint.x+2)=0 && (allow.y=curPoint.y+2)=0 && (allow.y=curPoint.y+1)
运算总耗时:13322
[11, 8, 3, 20, 23, 16, 5]
[2, 21, 10, 7, 4, 19, 24]
[9, 12, 1, 22, 15, 6, 17]
[40, 33, 44, 13, 18, 25, 36]
[43, 30, 41, 34, 37, 14, 47]
[32, 39, 28, 45, 48, 35, 26]
[29, 42, 31, 38, 27, 46, 49]

上述代码使用了回溯算法,虽然回溯算法是小规模的暴力,本身已经相较于枚举算法有了提升。但上述实例中,在7*7的棋盘的3行3列上出发的棋子需要运算13322ms(结果不唯一)。这表明这种算法在处理大量数据时是低效率的(回溯了大量无用的步骤)。

为了提高算法效率,就要减少无用步骤的回溯次数

由于一个棋子可能存在多个可进入的位置,而进入这些位置中又可能会存在多个可进入位置。那么我们按照这些位置的可能进入位置数升序排列,先运算可能性小的,再运算可能性大的。即更改了回溯的策略。这样的优化一定程度上(与运气有关)可以大大优化算法效率。

优化代码:

package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.backtrace;

import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

public class 回溯算法_backtrace_马踏棋盘 {
    private static int X;//棋盘宽度
    private static int Y;//棋盘高度
    private static boolean[] visited;//用于记录棋牌被访问情况
    private static boolean finish=false;//用于记录是否完成马踏棋盘
    public static void main(String[] args) {
        //初始化数据
        X=7;
        Y=7;
        int row=3;//起始点为3行3列
        int col=3;
        visited=new boolean[X*Y];
        int[][] chessboard=new int[X][Y];
        long start=System.currentTimeMillis();//计时开始
        //调用马踏棋盘解决方法
        traversalChessboard(chessboard,row-1,col-1,1);//为了人机交互方便,需要减-1操作
        long end=System.currentTimeMillis();//计时结束
        System.out.println("运算总耗时:"+(end-start));;
        //输出结果
        for (int[] temp:chessboard){
            System.out.println(Arrays.toString(temp));
        }
    }

    /**
     * 马踏棋盘解决方案主体(回溯算法)
     * @param chessboard 棋盘
     * @param row 行数
     * @param col 列数
     * @param step 步数
     */
    public static void traversalChessboard(int[][] chessboard,int row,int col,int step){
        chessboard[row][col]=step;//将步数赋值到对应棋盘位置上
        visited[row*X+col]=true;//将该处设置为已访问(row被-1)
        //调用next来获得可选方案,注意next方法处理的时候先处理列,再处理行,所以传入的point对象为point(y,x)
        ArrayList allow = next(new Point(col,row));//用于存储目前可以进入的棋盘(存储可选方案)
        sort(allow);//!!!对结果进行排序优化!!!
        //回溯算法核心:回溯式的找到所有位置
        while (!allow.isEmpty()){//可选方案非空时
            Point p=allow.remove(0);//拿取可选方案中的头一个位置
            if (!visited[p.y*X+p.x]){//当该方案位置没有被访问时
                traversalChessboard(chessboard,p.y,p.x,step+1);//以方案位置再进行上述查找
            }
        }
        //终止回溯条件
        if (step next(Point curPoint){
        ArrayList allowPoint=new ArrayList<>();//存储当前位置可以进入的位置
        Point allow=new Point();
        //下面的策略是先计算列,再计算行
        //走位置5
        if ((allow.x=curPoint.x-2)>=0 && (allow.y=curPoint.y-1)>=0){
            allowPoint.add(new Point(allow));
        }
        //走位置6
        if ((allow.x=curPoint.x-1)>=0 && (allow.y=curPoint.y-2)>=0){
            allowPoint.add(new Point(allow));
        }
        //走位置7
        if ((allow.x=curPoint.x+1)=0){
            allowPoint.add(new Point(allow));
        }
        //走位置0
        if ((allow.x=curPoint.x+2)=0){
            allowPoint.add(new Point(allow));
        }
        //走位置1
        if ((allow.x=curPoint.x+2)=0 && (allow.y=curPoint.y+2)=0 && (allow.y=curPoint.y+1) allow){
        allow.sort(new Comparator(){//覆写排序方法,使用Lambda表达式完成
            @Override
            public int compare(Point p1,Point p2) {
                return next(p1).size()-next(p2).size();
            }
        });
    }
}
运算总耗时:637
[47, 16, 3, 30, 7, 18, 5]
[2, 29, 46, 17, 4, 31, 8]
[15, 48, 1, 40, 45, 6, 19]
[36, 41, 28, 49, 34, 9, 32]
[27, 14, 35, 44, 39, 20, 23]
[42, 37, 12, 25, 22, 33, 10]
[13, 26, 43, 38, 11, 24, 21]

你看运算时间从13秒优化到不到1秒,这就是编程的魅力(doge)

还有一个值得注意的地方是:两次结果不同。这是由于回溯算法的不确定性组成的(我们更改了回溯的策略)

 

 


其他常用算法,见下各链接

【常用十大算法_二分查找算法】

【常用十大算法_分治算法】

【常用十大算法_贪心算法】

【常用十大算法_动态规划算法(DP)】

【常用十大算法_KMP算法】

【常用十大算法_普里姆(prim)算法,克鲁斯卡尔(Kruskal)算法】

【常用十大算法_迪杰斯特拉(Dijkstra)算法,弗洛伊德(Floyd)算法】

 

【数据结构与算法整理总结目录 :>】<-- 宝藏在此(doge)  

 

你可能感兴趣的:(数据结构与算法,算法,java,数据结构)