数据结构和算法-14.程序员常用10种算法

1. 二分查找算法(非递归)

1.1 介绍

  • 二分查找算法只适用于从有序的数列种进行查找,将数列排序后再进行查找
  • 二分查找法的运行时间为对数时间O(log2 n),即查找到需要的目标位置最多只需要log2 n步

1.2 代码实现

public class BinarySearch {
    public static void main(String[] args) {
        int[] arr = {1, 3, 8, 10, 11, 67, 100};
        int index = binarySearch(arr, -7);
        System.out.println(index);
    }

    //二分查找的非递归实现
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] > target) {
                right = mid - 1;//向左边查找
            } else {
                left = mid + 1;//向右边查找
            }
        }
        return -1;
    }
}

2. 分治算法

2.1 介绍

  • 就是把一个复制的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并,分治算法可以求解的一些境点问题
  • 二分查找
  • 大整数乘法
  • 棋盘覆盖
  • 合并排序
  • 快速排序
  • 线性时间选择
  • 最接近点对问题
  • 循环赛日程表
  • 汉诺塔

2.2 基本步骤

  1. 分解:将原问题分解成若干个规模较小,相互独立,与原问题形式相同的子问题
  2. 解决:若子问题规模较小而容易被解决则直接解决,否则递归各个子问题
  3. 合并:将各个子问题的解合并为原问题的解

2.3 汉诺塔问题

2.3.1 思路

  • 如果仅有一个盘,A->C
  • 有>=2个盘,总是可以看做是两个盘1.最下面的盘;2.上面的盘
  • 先把最上面的盘A->B
  • 最下面的盘A->C
  • B塔的所有盘B->C

2.3.2 代码实现

public class Hanoitower {
    public static void main(String[] args) {
        hanoiTower(10, 'A', 'B', 'C');
    }

    //使用分治算法,汉诺塔的移动方法
    public static void hanoiTower(int num, char a, char b, char c) {
        //只有一个盘
        if (num == 1) {
            System.out.println("第1个盘从" + a + "->" + c);
        } else {
            hanoiTower(num - 1, a, c, b);
            System.out.println("第" + num + "个盘从" + a + "->" + c);
            hanoiTower(num - 1, b, a, c);
        }
    }
}

3. 动态规划算法

3.1 介绍

  • 动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法
  • 与分治相似,也是将待求解问题分解成若干个子问题,先求子问题,然后从这些子问题的解得到原问题的解
  • 与分治不同的是,适用于用动态规划求解的问题,经分解得到子问题不是互相独立的,下一个子阶段的求解是建立在上一个子阶段解的基础上进行进一步求解
  • 动态规划可以通过填表的方式来逐步推进,得到最优解

3.2 背包问题

3.2.1 思想

  • 总价值最大,重量不能超出
  • 装入物品不能重复
物品重量w价格v
吉他11500
音响43000
电脑32000

每次遍历到第i个物品,根据w[i]和v[i]来确定是否需要将该物品放入背包中,即对于给定的n个物品,C为背包容量,令v[i]·[j]表示再前i个物品总能够装入容量为j的背包中的最大价值

  • v[i]·[0]=v[0]·[j]=0
  • 当w[i]>j时,v[i]·[j]=v[i-1]·[j]
  • 当j>=w[i]时,v[i]·[j]=max{v[i-1]·[j],v[i]+v[i-1]·[j-w[i]}

背包问题

3.2.2 代码实现

public class KnapsackProblem {
    public static void main(String[] args) {
        int[] w = {1, 4, 3};//物品的重量
        int[] val = {1500, 3000, 2000};//物品的价值
        int m = 4;//背包的容量
        int n = val.length;//物品的个数

        int[][] v = new int[n + 1][m + 1];//v[i][j]表示在前i个物品中能装入容量为j的背包中的最大价值

        int[][] path = new int[n + 1][m + 1];

        for (int i = 1; i < v.length; i++) {
            for (int j = 0; j < v[0].length; j++) {
                if (w[i - 1] > j) {
                    v[i][j] = v[i - 1][j];
                } else {
                    //v[i][j] = Math.max(v[i - 1][j], val[i - 1] + v[i - 1][j - w[i - 1]]);
                    if (v[i - 1][j] < val[i - 1] + v[i - 1][j - w[i - 1]]) {
                        v[i][j] = val[i - 1] + v[i - 1][j - w[i - 1]];
                        path[i][j] = 1;
                    } else {
                        v[i][j] = v[i - 1][j];
                    }
                }
            }
        }

        for (int i = 0; i < v.length; i++) {
            v[i][0] = 0;
        }
        for (int i = 0; i < v[0].length; i++) {
            v[0][i] = 0;
        }
        for (int i = 0; i < v.length; i++) {
            for (int j = 0; j < v[i].length; j++) {
                System.out.print(v[i][j] + " ");
            }
            System.out.println();
        }

        int i = path.length - 1;
        int j = path[0].length - 1;
        while (i > 0 && j > 0) {
            if (path[i][j] == 1) {
                System.out.printf("第%d个商品放入到背包\n", i);
                j -= w[i - 1];
            }
            i--;
        }

    }
}

4. KMP算法

4.1 暴力匹配算法

  • 有一个字符串str1="中国你中你国你国中国你好"和一个子串str2=“中国你好”,判断str1是否含有str2,如果存在返回第一次出现的位置。如果没有,返回-1
  • 假设现在str1匹配到i位置,字串str2匹配到j位置,则
  • 如果当前字符匹配成功(str1[i]==str2[j]),则i++,j++,继续匹配下一个字符
  • 如果str1[i]!=str2[j],令i=i(j-1),j=0。相当于每次匹配失败时,i回溯,j置为0
  • 有大量回溯,效率很低

4.1.1 代码实现

public class ViolenceMatch {
    public static void main(String[] args) {
        String s1 = "中国你中你国你国中国你好";
        String s2 = "中国你好-";
        int i = violenceMatch(s1, s2);
        System.out.println(i);
    }

    public static int violenceMatch(String str1, String str2) {
        char[] s1 = str1.toCharArray();
        char[] s2 = str2.toCharArray();
        int s1Len = s1.length;
        int s2Len = s2.length;
        int i = 0;//指向s1
        int j = 0;//指向s2
        while (i < s1Len && j < s2Len) {
            if (s1[i] == s2[j]) {//匹配成功
                i++;
                j++;
            } else {
                i = i - (j - 1);
                j = 0;
            }
        }
        if (j == s2Len) {
            return i - j;
        } else {
            return -1;
        }
    }
}

4.2 KMP算法

  • KMP算法常用于在一个文本串S内查找一个模式串P出现的位置
  • KMP通过一个next数组,保存模式串中前后最长公共子序的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去了大量的计算时间
  • str1 = “BBC ABCDAB ABCDABCDABDE”,str2="ABCDABD "
public class KMPAlgorithm {
    public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABD";

        int[] next = kmpNext(str2);
        System.out.println(Arrays.toString(next));

        int index = kmpSearch(str1, str2, next);
        System.out.println(index);
    }

    public static int kmpSearch(String str1, String str2, int[] next) {
        //遍历
        for (int i = 0, j = 0; i < str1.length(); i++) {
            while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
                j = next[j - 1];
            }

            if (str1.charAt(i) == str2.charAt(j)) {
                j++;
            }
            if (j == str2.length()) {//找到了
                return i - j + 1;
            }
        }
        return -1;
    }

    //获取到一个子串的部分匹配表
    public static int[] kmpNext(String dest) {
        //保存部分匹配值
        int[] next = new int[dest.length()];
        next[0] = 0;
        for (int i = 1, j = 0; i < dest.length(); i++) {
            while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
                j = next[j - 1];
            }
            if (dest.charAt(i) == dest.charAt(j)) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}

5. 贪心算法

5.1 集合覆盖问题

  • 贪婪(贪心)算法值在对问题进行求解时,在每一步选择中都采取最好或最优的选择,从而希望能导致结果是最好或最优的算法
  • 得到的结果不一定是最优结果,但是都是相对近似最优解的结果
  • 假设存在下面需要付费的广播台以及可以覆盖的地区,如何选择最少的广播台,可以让所有地区都接受信号
广播台覆盖地区
K1北京,上海,天津
K2广州,北京,深圳
K3成都,上海,杭州
K4上海,天津
K5杭州,大连
  • 贪心算法最佳应用-集合覆盖
  • 使用穷举法实现列出每个可能的广播台的集合,称为幂集,有n个广播台,则广播台的组合总有2^n-1个
广播台数量n子集总数2^n需要时间
5323.2秒
101024102.4秒
1001.26*100^304*10^23

5.2 代码思路

  1. 遍历所有的广播电台,找到一个覆盖了最多未覆盖的地区的电台
  2. 将这个电台加入到一个集合中,想办法把该电台覆盖的地区在下一次比较时去掉
  3. 重复第1步直到覆盖了全部地区

5.3 代码实现

public class GreedyAlgorithm {
    public static void main(String[] args) {
        HashMap<String, HashSet<String>> broadcasts = new HashMap<>();
        HashSet<String> hashSet1 = new HashSet<>();
        hashSet1.add("北京");
        hashSet1.add("上海");
        hashSet1.add("天津");
        HashSet<String> hashSet2 = new HashSet<>();
        hashSet2.add("广州");
        hashSet2.add("北京");
        hashSet2.add("深圳");
        HashSet<String> hashSet3 = new HashSet<>();
        hashSet3.add("成都");
        hashSet3.add("上海");
        hashSet3.add("杭州");
        HashSet<String> hashSet4 = new HashSet<>();
        hashSet4.add("上海");
        hashSet4.add("天津");
        HashSet<String> hashSet5 = new HashSet<>();
        hashSet5.add("杭州");
        hashSet5.add("大连");
        broadcasts.put("K1", hashSet1);
        broadcasts.put("K2", hashSet2);
        broadcasts.put("K3", hashSet3);
        broadcasts.put("K4", hashSet4);
        broadcasts.put("K5", hashSet5);

        //存放所有地区
        HashSet<String> allAreas = new HashSet<>();
        for (Map.Entry<String, HashSet<String>> broadcast : broadcasts.entrySet()) {
            allAreas.addAll(broadcast.getValue());
        }
        //创建ArrayList存放选择的电台集合
         ArrayList<String> selects = new ArrayList<>();

        //一个临时集合,在遍历过程中,存放遍历过程中的电台覆盖的地区和当前还没有覆盖的地区的交集
        HashSet<String> tempSet = new HashSet<>();
        //保存在一次遍历过程中,能够覆盖最大未覆盖的地区对应的电台的key
        String maxKey = null;
        while (allAreas.size() != 0) {
            maxKey = null;

            for (String key : broadcasts.keySet()) {
                tempSet.clear();
                //当前key能覆盖的地区
                HashSet<String> areas = broadcasts.get(key);
                tempSet.addAll(areas);
                //求出tempSet和allAreas的交集
                tempSet.retainAll(allAreas);
                //如果当前这个结合包含的未覆盖地区的数量,比maxKey指向的集合地区还多
                if (tempSet.size() > 0 &&
                        (maxKey == null || tempSet.size() > broadcasts.get(maxKey).size())) {
                    maxKey = key;
                }
            }
            //将maxKey加入selects
            if (maxKey != null) {
                selects.add(maxKey);
                //将maxKey指向的广播电台覆盖的地区从allAreas去掉
                allAreas.removeAll(broadcasts.get(maxKey));
            }
        }
        System.out.println("结果" + selects);
    }
}

6. 普里姆(Prim)算法

6.1 应用场景

-有7个村庄(A,B,C,D…),如何修路将村庄连通,总路程最短?

Prim算法

6.2 最小生成树

  • 修路问题本质就是最小生成树(Minimum Cost Spanning Tree,MST)问题
  • 给定一个带权的无向连通图,如何选取一颗生成树,使树上的所有边上权的总和为最小,叫最小生成树
  • N个顶点,一定有N-1条边
  • 求最小生成树的算法主要是普里姆和克鲁斯卡尔算法

最小生成树

6.3 图解

图解

6.4 代码实现

public class PrimAlgorithm {
    public static void main(String[] args) {
        char[] data = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int verxs = data.length;
        int[][] weight = new int[][]{
                {100, 5, 7, 100, 100, 100, 2},
                {5, 100, 100, 9, 100, 100, 3},
                {7, 100, 100, 100, 8, 100, 100},
                {100, 9, 100, 100, 100, 4, 100},
                {100, 100, 8, 100, 100, 5, 4},
                {100, 100, 100, 4, 5, 100, 6},
                {2, 3, 100, 100, 4, 6, 100}
        };
        MGraph graph = new MGraph(verxs);
        MinTree minTree = new MinTree();
        minTree.createGraph(graph, verxs, data, weight);
        minTree.showGraph(graph);

        minTree.prim(graph, 0);
    }
}

//创建最小生成树
class MinTree {
    //创建图的邻接矩阵
    public void createGraph(MGraph graph, int verxs, char data[], int[][] weight) {
        for (int i = 0; i < verxs; i++) {//顶点
            graph.data[i] = data[i];
            for (int j = 0; j < verxs; j++) {
                graph.weight[i][j] = weight[i][j];
            }
        }
    }

    //显示图的方法
    public void showGraph(MGraph graph) {
        for (int[] link : graph.weight) {
            System.out.println(Arrays.toString(link));
        }
    }

    public void prim(MGraph graph, int v) {
        //标记节点是否被访问过
        int[] visited = new int[graph.verxs];
        //默认为0,表示没访问过
        for (int i = 0; i < graph.verxs; i++) {
            visited[i] = 0;
        }
        //把当前节点标记为已访问
        visited[v] = 1;
        //h1和h2记录两个顶点的下标
        int h1 = -1;
        int h2 = -1;
        int minWeight = 100;
        for (int k = 1; k < graph.verxs; k++) {

            for (int i = 0; i < graph.verxs; i++) {//i节点表示被访问过的节点
                for (int j = 0; j < graph.verxs; j++) {//j表示还没有访问过的节点
                    if (visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {
                        //替换minWeight
                        minWeight = graph.weight[i][j];
                        h1 = i;
                        h2 = j;
                    }
                }
            }
            System.out.println("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight);
            //将找到的节点标记为已访问
            visited[h2] = 1;

            minWeight = 100;
        }
    }
}

class MGraph {
    int verxs;//表示图的节点个数
    char[] data;//存放节点数据
    int[][] weight;//存放边

    public MGraph(int verxs) {
        this.verxs = verxs;
        data = new char[verxs];
        weight = new int[verxs][verxs];
    }
}

7. 克鲁斯卡尔(Kruskal)算法

7.1 应用场景

  • 某城市新增7个站点,ABCDEFG,需要修路将7个站点连通
  • 各个站点的距离用边线表示权,
  • 如何修路保证各个站点都能连通,且修建总里程最短
  • 克鲁斯卡尔算法是用来求加权连通图的最小生成树的算法
  • 具体做法:首先构造一个含n个顶点的森林,然后依权值从小到大连通王忠选择边加入到森林中,并使森林中不产生回路,直到变成一棵树位置

Kruskal

7.2 图解

图解

7.3 代码实现

public class KruskalCase {
    private int edgeNum;//边的个数
    private char[] vertexs;//顶点数据
    private int[][] matrix;//邻接矩阵
    private static final int INF = Integer.MAX_VALUE;//表示两个顶点不能连通

    public static void main(String[] args) {
        char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int[][] matrix = {
                {0, 12, INF, INF, INF, 16, 14},
                {12, 0, 10, INF, INF, 7, INF},
                {INF, 10, 0, 3, 5, 6, INF},
                {INF, INF, 3, 0, 4, INF, INF},
                {INF, INF, 5, 4, 0, 2, 8},
                {16, 7, 6, INF, 2, 0, 9},
                {14, INF, INF, INF, 8, 9, 0}
        };
        KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);
        kruskalCase.print();
        EData[] edges = kruskalCase.getEdges();
        System.out.println(Arrays.toString(edges));//排序前
        kruskalCase.sortEdge(edges);
        System.out.println(Arrays.toString(edges));//排序后

        kruskalCase.kruskal();
    }

    //构造器
    public KruskalCase(char[] vertexs, int[][] matrix) {
        //顶点数
        int vlen = vertexs.length;
        //初始化顶点
        this.vertexs = new char[vlen];
        for (int i = 0; i < vlen; i++) {
            this.vertexs[i] = vertexs[i];
        }
        //初始化边
        this.matrix = new int[vlen][vlen];
        for (int i = 0; i < vlen; i++) {
            for (int j = 0; j < vlen; j++) {
                this.matrix[i][j] = matrix[i][j];
            }
        }

        //统计边的条数
        for (int i = 0; i < vlen; i++) {
            for (int j = i + 1; j < vlen; j++) {
                if (this.matrix[i][j] != INF) {
                    edgeNum++;
                }
            }
        }
    }

    public void kruskal() {
        int index = 0;//表示最后结果数组的索引
        int[] ends = new int[edgeNum];//用于保存已有最小生成树中的每个顶点在树中的终点
        //创建结果数组,保存最后的最小生成树
        EData[] rets = new EData[edgeNum];
        //获取图中所有边的集合
        EData[] edges = getEdges();

        //按照边的权值大小进行排序
        sortEdge(edges);
        //遍历edges数组,将边添加到最小生成树中,判断准备加入的边是否形成回路
        for (int i = 0; i < edgeNum; i++) {
            //获取第i条边的第一个顶点
            int p1 = getPosition(edges[i].start);
            //获取第二个顶点
            int p2 = getPosition(edges[i].end);
            //获取p1这个顶点在已有最小生成树中的终点
            int m = getEnd(ends, p1);
            int n = getEnd(ends, p2);
            //是否构成回路
            if (m != n) {//没有构成回路
                ends[m] = n;//设置m在已有最小生成树的终点
                rets[index++] = edges[i];//有一条边加入到rets
            }
        }

        //统计并打印“最小生成树”
        System.out.println("最小生成树" + Arrays.toString(rets));
    }

    //打印邻接矩阵
    public void print() {
        System.out.println("邻接矩阵为:\n");
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = 0; j < vertexs.length; j++) {
                System.out.printf("%12d\t", matrix[i][j]);
            }
            System.out.println();//换行
        }
    }

    //对边进行排序
    private void sortEdge(EData[] edges) {
        for (int i = 0; i < edges.length - 1; i++) {
            for (int j = 0; j < edges.length - 1 - i; j++) {
                if (edges[j].weight > edges[j + 1].weight) {
                    EData tmp = edges[j];
                    edges[j] = edges[j + 1];
                    edges[j + 1] = tmp;
                }
            }
        }
    }

    //传入顶点的值返回下标
    private int getPosition(char ch) {
        for (int i = 0; i < vertexs.length; i++) {
            if (vertexs[i] == ch) {
                return i;
            }
        }
        return -1;
    }

    //获取图中边,放到EData[]数组中,后面需要遍历该数组
    private EData[] getEdges() {
        int index = 0;
        EData[] edges = new EData[edgeNum];
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = i + 1; j < vertexs.length; j++) {
                if (matrix[i][j] != INF) {
                    edges[index++] = new EData(vertexs[i], vertexs[j], matrix[i][j]);
                }
            }
        }
        return edges;
    }

    //获取下标为i的顶点的终点,用于判断两个顶点的终点是否相同
    private int getEnd(int[] ends, int i) {
        while (ends[i] != 0) {
            i = ends[i];
        }
        return i;
    }
}

//一个类EData,他的对象实例就表示一条边
class EData {
    char start;//边的一个点
    char end;//边的另一个点
    int weight;//边的权值

    public EData(char start, char end, int weight) {
        this.start = start;
        this.end = end;
        this.weight = weight;
    }

    //输出边的信息
    @Override
    public String toString() {
        return "EData{" +
                "start=" + start +
                ", end=" + end +
                ", weight=" + weight +
                '}';
    }
}

8. 迪杰斯特拉(Dijkstra)算法

8.1 应用场景

  • 从G出发,到其他各个点的最短距离
  • 迪杰斯特拉算法是经典最短路径算法,用于计算一个节点到其他节点的最短路径,主要特点是以起始点为中心向外层扩展(广度优先搜索思想),直到扩展到终点为止

Dijkstra

8.2 图解

迪杰斯特拉

9. 弗洛依德(Floyd)算法

9.1 简介

  • 和Dijkstra算法一样,Floyd算法也是一种用于寻找给定的加权图中顶点间最短路径的算法
  • Floyd计算各个顶点之间的最短路径,Dijkstra是计算图中某一个顶点到其他顶点的最短路径
  • 设置顶点vi到顶点vk的最短路径已知为Lik,顶点vk到vj的最短路径已知为Lkj,顶点vi到vj的路径为Lij,则vi到vj的最短路径为min((Lik+Lkj),Lij),vk的取值为图中所有顶点,则可获取vi到vj的最短路径

9.2 图解

floyd图解
floyd图解2

9.3 代码实现

public class FloyAlgorithm {
    public static void main(String[] args) {
        char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int[][] matrix = new int[vertex.length][vertex.length];
        final int N = 65535;
        matrix[0] = new int[]{0, 5, 7, N, N, N, 2};
        matrix[1] = new int[]{5, 0, N, 9, N, N, 3};
        matrix[2] = new int[]{7, N, 0, N, 8, N, N};
        matrix[3] = new int[]{N, 9, N, 0, N, 4, N};
        matrix[4] = new int[]{N, N, 8, N, 0, 5, 4};
        matrix[5] = new int[]{N, N, N, 4, 5, 0, 6};
        matrix[6] = new int[]{2, 3, N, N, 4, 6, 0};

        Graph graph = new Graph(vertex.length, matrix, vertex);
        graph.floyd();
        graph.show();
    }
}

//创建图
class Graph {
    private char[] vertex;//存放顶点的数组
    private int[][] dis;//保持从各个顶点出发到其他顶点的距离
    private int[][] pre;//保存到达目标顶点的前驱顶点

    public Graph(int length, int[][] matrix, char[] vertex) {
        this.vertex = vertex;
        this.dis = matrix;
        this.pre = new int[length][length];
        //对pre数组初始化
        for (int i = 0; i < length; i++) {
            Arrays.fill(pre[i], i);
        }
    }

    //显示pre和dis数组
    public void show() {
        char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        for (int k = 0; k < dis.length; k++) {
            //输出pre数组的一行
            for (int i = 0; i < dis.length; i++) {
                System.out.print(vertex[pre[k][i]] + " ");
            }
            System.out.println();
            for (int i = 0; i < dis.length; i++) {
                System.out.print("(" + vertex[k] + "到" + vertex[i] + "的最短路径是" + dis[k][i] + ")");
            }
            System.out.println();
        }
    }

    //floyd算法
    public void floyd() {
        int len = 0;//变量保存距离
        //对中间顶点遍历,k就是中间顶点的下标
        for (int k = 0; k < dis.length; k++) {
            //从i顶点开始出发
            for (int i = 0; i < dis.length; i++) {
                //到达j顶点
                for (int j = 0; j < dis.length; j++) {
                    len = dis[i][k] + dis[k][j];//求出从i顶点出发,经过k中间顶点,到达j顶点的距离
                    if (len < dis[i][j]) {
                        dis[i][j] = len;
                        pre[i][j] = pre[k][j];//更新前驱顶点
                    }
                }
            }
        }
    }
}

10. 马踏棋盘算法

10.1 思路图解

马踏棋盘

  • 马踏棋盘实际上是图的深度优先搜索(DFS)的应用
  • 如果使用回溯来解决,加入走到了53步,坐标(1,0)发现到了尽头,只能回退,查看其它路径,在棋盘上不停回溯

骑士周游

10.2 代码实现

public class HorseChessboard {
    private static int X;//棋盘的列数
    private static int Y;//棋盘的行数
    //标记棋盘的各个位置是否被访问过
    private static boolean visited[];
    //标记是否棋盘的所有位置都被访问,true表示成功
    private static boolean finished;

    public static void main(String[] args) {
        X = 8;
        Y = 8;
        int row = 1;//初始位置的行
        int col = 1;//初始位置的列

        int[][] chessboard = new int[X][Y];
        visited = new boolean[X * Y];//初始值都是false
        long start = System.currentTimeMillis();
        traversalChessboard(chessboard, row - 1, col - 1, 1);
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - start));

        //输出棋盘的最后情况
        for (int[] rows : chessboard) {
            for (int step : rows) {
                System.out.print(step + "\t");
            }
            System.out.println();
        }
    }

    public static void traversalChessboard(int[][] chessboard, int row, int col, int step) {
        chessboard[row][col] = step;
        visited[row * X + col] = true;//标记该位置已访问
        //获取当前位置可以走的下一个位置的集合
        ArrayList<Point> ps = next(new Point(col, row));
        //对ps所有的Point对象的下一步的位置的数目,进行非递减排序
        sort(ps);
        //遍历ps
        while (!ps.isEmpty()) {
            Point p = ps.remove(0);//取出下一个可以走的位置
            //判断该点是否已经访问过
            if (!visited[p.y * X + p.x]) {//没有访问过
                traversalChessboard(chessboard, p.y, p.x, step + 1);
            }
        }
        if (step < X * Y && !finished) {
            chessboard[row][col] = 0;
            visited[row * X + col] = false;
        } else {
            finished = true;
        }
    }

    //根据当前位置,计算能走哪些位置,并放入一个集合中
    public static ArrayList<Point> next(Point curPoint) {
        ArrayList<Point> ps = new ArrayList<>();
        Point p1 = new Point();
        //表示可以走5的位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
            ps.add(new Point(p1));
        }
        //6位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0) {
            ps.add(new Point(p1));
        }
        //7位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
            ps.add(new Point(p1));
        }
        //0位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
            ps.add(new Point(p1));
        }
        //1位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
            ps.add(new Point(p1));
        }
        //2位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
            ps.add(new Point(p1));
        }
        //3位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y) {
            ps.add(new Point(p1));
        }
        //4位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
            ps.add(new Point(p1));
        }
        return ps;
    }

    //根据当前这一步的所有的下一步的选择位置,进行非递减排序
    public static void sort(ArrayList<Point> ps) {
        ps.sort(new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                //获取到o1的下一步的所有位置的个数
                int count1 = next(o1).size();
                int count2 = next(o2).size();
                if (count1 < count2) {
                    return -1;
                } else if (count1 == count2) {
                    return 0;
                } else {
                    return 1;
                }
            }
        });
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
根据引用,这篇博客是关于一位学生利用整个暑假的时间来学习《数据结构算法》课程并且持续更新的。而引用是一份关于Java数据结构算法的学习笔记,涵盖了数据结构算法的概述、分类以及算法分析的内容。引用给出了数据结构的官方解释和大白话解释,以及数据结构的分类和物理结构的介绍。 所以,关于数据结构算法的笔记,你可以参考这些资源来了解数据结构的概念、分类和物理结构,以及算法的分析和时间复杂度等内容。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [数据结构算法——学习笔记汇总](https://blog.csdn.net/qq_42025798/article/details/118864568)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Java数据结构算法1-概述学习笔记](https://blog.csdn.net/qq_45498432/article/details/124067892)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值