经典算法 - 回溯法解决骑士周游问题及贪心算法优化

骑士周游问题(马踏棋盘)

这是一个很经典的游戏,4399小游戏:马踏棋盘

经典算法 - 回溯法解决骑士周游问题及贪心算法优化_第1张图片

马只能走日字,棋盘每个格子只能走一次,现在要求马跳遍整个棋盘,最终回到最初的位置

如果靠人来想挺难的,但是我们有计算机

在这里插入图片描述

通常采用回溯法或启发式搜索类算法求解


回溯法

分析:

  1. 棋盘可以看做一个二维数组chessboard,可以用Point对象代表一个位置
  2. 一个马最多可以走8个方向,分别标号(这里标号的顺序不同会造成结果不同,都是问题的解)

经典算法 - 回溯法解决骑士周游问题及贪心算法优化_第2张图片

即可以写一个方法判断当前位置curPoint的下一步哪些位置可走(8个for循环)
创建数组points用于保存可走位置

经典算法 - 回溯法解决骑士周游问题及贪心算法优化_第3张图片

  1. 回溯算法traversalChessboard:从初始位置开始,找到所有的下一步位置,遍历这些位置;进入下一位置继续执行traversalChessboard方法,直到无法完成周游就回溯

进入下一步的顺序是按照我们next方法for循环查找的顺序

经典算法 - 回溯法解决骑士周游问题及贪心算法优化_第4张图片


Java代码实现

package com.company.十种算法.horse;

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

/**
 * Author : zfk
 * Data : 10:00
 */
public class HorseChessboard {
    //表示棋盘的列数
    private static int X;
    //表示棋盘的行数
    private static int Y;
    //标记棋盘的各个位置是否被访问过
    private static boolean[][] vistied;
    //设置一个属性,标记棋盘的所有位置是否都被访问
    private static boolean finished;

    public static void main(String[] args) {
        //8*8的棋盘
        X = 6 ; Y = 6;
        //创建棋盘
        int[][] chessboard = new int[Y][X];
        vistied = new boolean[Y][X];

        long start = System.currentTimeMillis();
        //初始位置(2,1)
        traversalChessboard(chessboard,1,2,1);

        long end = System.currentTimeMillis();

        System.out.println("执行时间:"+(end - start)+" ms");

        for (int[] rows : chessboard){
            for (int cols : rows){
                System.out.print(cols+"\t");
            }
            System.out.println();
        }
    }

    /**
     * 完成骑士周游问题
     * @param chessboard 棋盘
     * @param row 马当前位置的行,从0开始
     * @param col 马当前位置的列,从0开始
     * @param step 表示当前是第几步,初始位置是第1步
     */
    public static void traversalChessboard(int[][] chessboard,int row,int col,int step){
        //在棋盘位置上标记步数
        chessboard[row][col] = step;
        //标记该位置已经访问
        vistied[row][col] = true;
        //获取可以走的下一位置的集合,y是行row,x是列col
        ArrayList<Point> nextPoints = next(new Point(col, row));

        //遍历nextPoints,只要不等于null就一直遍历
        while (!nextPoints.isEmpty()){
            //取出下一个可以走的位置
            Point p = nextPoints.remove(0);
            //判断该点是否已经访问过
            if (!vistied[p.y][p.x]){
                traversalChessboard(chessboard,p.y,p.x,step + 1);
            }
        }
        //判断能否走完所有棋盘位置
        if (step < X * Y && !finished){
            //没有完成,需要把棋盘位置置0,且重置成未访问状态
            chessboard[row][col] = 0;
            vistied[row][col] = false;
        }
        else {
            finished = true;
        }

    }

    /**
     * 根据当前的位置(Point),就是马还能走哪些位置(Point),并放入到集合中
     * @param curPoint 当前位置
     * @return
     */
    public static ArrayList<Point> next(Point curPoint){
        ArrayList<Point> points = new ArrayList<>();

        Point p1 = new Point();
        //判断马是否可以走5的位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0){
            points.add(new Point(p1));
        }
        //判断马是否可以走6的位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0){
            points.add(new Point(p1));
        }
        //判断马是否可以走7的位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0){
            points.add(new Point(p1));
        }
        //判断马是否可以走0的位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0){
            points.add(new Point(p1));
        }
        //判断马是否可以走1的位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y){
            points.add(new Point(p1));
        }
        //判断马是否可以走2的位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y){
            points.add(new Point(p1));
        }
        //判断马是否可以走3的位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y){
            points.add(new Point(p1));
        }
        //判断马是否可以走4的位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y){
            points.add(new Point(p1));
        }

        return points;
    }

}

结果:

经典算法 - 回溯法解决骑士周游问题及贪心算法优化_第5张图片

进入游戏验证:

经典算法 - 回溯法解决骑士周游问题及贪心算法优化_第6张图片


贪心算法优化

从上面的算法步骤,可以看出有一定的穷举法的思想(不管策略一路走到黑)

我们可以用贪心算法进行一定的优化:

如:

当前步骤,马可以走6个位置,如何选择下一步的位置?

经典算法 - 回溯法解决骑士周游问题及贪心算法优化_第7张图片

贪心算法:我们要走的下一步位置p1,它的可选下一步位置应当最少;将下一步位置p1集合进行非递减排序

非递减排序:可以有重复值的递增排序,如{1,1,2,2,3,3}

如上图中:

  • 位置1的可选下一步位置有1个
  • 位置2的可选下一步位置有2个
  • 位置3的可选下一步位置有5个
  • 位置4的可选下一步位置有7个
  • 位置5的可选下一步位置有5个
  • 位置6的可选下一步位置有4个

非递减排序{1,2,6,3,5,4},按照这个顺序遍历,就可以减少很多次递归


优化后的代码

  • sort排序方法:根据下一步位置的个数排序
  • 在traversalChessboard方法中对nextPoints进行排序
package com.company.十种算法.horse;

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

/**
 * Author : zfk
 * Data : 10:00
 */
public class HorseChessboard {
    //表示棋盘的列数
    private static int X;
    //表示棋盘的行数
    private static int Y;
    //标记棋盘的各个位置是否被访问过
    private static boolean[][] vistied;
    //设置一个属性,标记棋盘的所有位置是否都被访问
    private static boolean finished;

    public static void main(String[] args) {
        //6*6的棋盘
        X = 6 ; Y = 6;
        //创建棋盘
        int[][] chessboard = new int[Y][X];
        vistied = new boolean[Y][X];

        long start = System.currentTimeMillis();
        //初始位置(2,1)
        traversalChessboard(chessboard,1,2,1);

        long end = System.currentTimeMillis();

        System.out.println("执行时间:"+(end - start)+" ms");

        for (int[] rows : chessboard){
            for (int cols : rows){
                System.out.print(cols+"\t");
            }
            System.out.println();
        }
    }

    /**
     * 完成骑士周游问题
     * @param chessboard 棋盘
     * @param row 马当前位置的行,从0开始
     * @param col 马当前位置的列,从0开始
     * @param step 表示当前是第几步,初始位置是第1步
     */
    public static void traversalChessboard(int[][] chessboard,int row,int col,int step){
        //在棋盘位置上标记步数
        chessboard[row][col] = step;
        //标记该位置已经访问
        vistied[row][col] = true;
        //获取可以走的下一位置的集合,y是行row,x是列col
        ArrayList<Point> nextPoints = next(new Point(col, row));
        //对nextPoints进行非递减排序
        sort(nextPoints);

        //遍历nextPoints,只要不等于null就一直遍历
        while (!nextPoints.isEmpty()){
            //取出下一个可以走的位置
            Point p = nextPoints.remove(0);
            //判断该点是否已经访问过
            if (!vistied[p.y][p.x]){
                traversalChessboard(chessboard,p.y,p.x,step + 1);
            }
        }
        //判断能否走完所有棋盘位置
        if (step < X * Y && !finished){
            //没有完成,需要把棋盘位置置0,且重置成未访问状态
            chessboard[row][col] = 0;
            vistied[row][col] = false;
        }
        else {
            finished = true;
        }

    }

    /**
     * 根据当前的位置(Point),就是马还能走哪些位置(Point),并放入到集合中
     * @param curPoint 当前位置
     * @return
     */
    public static ArrayList<Point> next(Point curPoint){
        ArrayList<Point> points = new ArrayList<>();

        Point p1 = new Point();
        //判断马是否可以走5的位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0){
            points.add(new Point(p1));
        }
        //判断马是否可以走6的位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0){
            points.add(new Point(p1));
        }
        //判断马是否可以走7的位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0){
            points.add(new Point(p1));
        }
        //判断马是否可以走0的位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0){
            points.add(new Point(p1));
        }
        //判断马是否可以走1的位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y){
            points.add(new Point(p1));
        }
        //判断马是否可以走2的位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y){
            points.add(new Point(p1));
        }
        //判断马是否可以走3的位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y){
            points.add(new Point(p1));
        }
        //判断马是否可以走4的位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y){
            points.add(new Point(p1));
        }

        return points;
    }


    //根据当前这一步的所有的下一步的选择位置,进行非递减排序
    public static void sort(ArrayList<Point> points){
        points.sort(new Comparator<Point>() {
            @Override
            public int compare(Point p1, Point p2) {
                //获取p1的下一步的所有位置个数
                int count1 = next(p1).size();
                int count2 = next(p2).size();

                if (count1 < count2){
                    return -1;
                }
                else if (count1 == count2){
                    return 0;
                }
                else {
                    return 1;
                }
            }
        });
    }
}

执行结果:12ms 对比 112ms,效率高了很多

经典算法 - 回溯法解决骑士周游问题及贪心算法优化_第8张图片

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