程序员一些基础算法

基础算法

*以此谨记自己学习心得
今天是3月8号,是一个特殊的日子,也是我学完数据结构与算法的日子。
在这个特殊的日子里学完我最喜欢的数据结构与算法,也是对得起自己。
算法这个东西,要是想不到思路,会很难。像做数学题一样,想通了,就简单了。每次老师讲完,我都觉得:哇,好难啊,我都不想写代码自己做一遍。但是老师讲完一个算法,我都会自己去程序从头到尾想一遍,写一遍,每次开始想的时候总是困难的,但是也是很幸运,每次算法想个几个小时,最后总被我写出来了。好了,现在来介绍一下这几天学的算法

分治算法

因为我个人觉得分治算法比较简单,主要是要想通递归,我在这里把老师的讲解粘贴一下
分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
像我们之前学的归并排序一样。
分治法在每一层递归上都有三个步骤:
分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
合并:将各个子问题的解合并为原问题的解。
我在这里引一个案例,汉诺塔游戏。具体规则就不讲了。
汉诺塔最重要的是想通递归。
比如说三个盘子,我们可以分为2+1,将上面的2个看成1个,将下面的1个看成1个。然后首先要将上面那个整体从A,B,C三个柱子,从A移动到B,然后最底下那个移动到C。然后B上面的两个盘子又可以看成一个整体加另一个整体,只是起点、中间点、终点换了一下,最顶上的盘子从起点B移动到中间点A,底下的盘子移动到C。这里我个人觉得应该能想通,便不再多说。
直接上代码

package com.hanoiTower;

public class HanoiTower {
    public static void main(String[] args) {
        char A='A';
        char B='B';
        char C='C';
        autoPlace(3,A,B,C);

    }
    public  static void autoPlace(int num ,char start,char middle, char destination){
        if (num==1){
            System.out.println("第"+num+"个从"+start+"->"+destination);
        }
        else {
            autoPlace(num-1,start,destination,middle);
            System.out.println("第"+num+"个从"+start+"->"+destination);
            autoPlace(num-1,middle,start,destination);
        }
    }
}

动态规划算法

像上面的分治算法,递归与递归之间不会互相影响,而动态规划中,目前一步的操作会影响下一步操作。

动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法

动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。

与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。 ( 即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解 )

动态规划可以通过填表的方式来逐步推进,得到最优解.
举一个案例:
案例来自尚硅谷韩顺平老师
像这个案例,我们可以以表的形式展现出来
在这里插入图片描述
里面的最大值,就是最优的算法,打个比方说,当书包容量为4磅,那有两个选择。1:存放3磅的电脑,发现还剩余1磅,这个时候就放吉他。而这个过程就体现了动态规划算法。2:放4磅的音响。当我们最后在选择的时候,需要将两个选择进行比较。由于这个案例也不是很难,便不说思路了
代码如下

public class Backpack {
    int products[] = {1, 2, 3};//三件物品
    int value[] = {1500, 3000, 2000};//三件商品的价格
    int weight[] = {1, 4, 3};//三件商品的重量
    int packgecapacity[] = {1, 2, 3, 4};//背包的容量大小
    int array[][] = new int[products.length + 1][packgecapacity.length + 1];//创建的一个表
    int path[][] = new int[products.length + 1][packgecapacity.length + 1];

    public static void main(String[] args) {
        Backpack backpack = new Backpack();
        backpack.count();
        backpack.print();
        backpack.betterchoice();

    }

    public void count() {
        for (int i = 0; i < packgecapacity.length + 1; i++) {
            array[0][i] = 0;
        }
        for (int i = 0; i < products.length + 1; i++) {
            array[i][0] = 0;
        }
        for (int i = 1; i < products.length + 1; i++) {
            for (int j = 1; j < packgecapacity.length + 1; j++) {
                if (weight[i - 1] <= packgecapacity[j - 1]) {

                    int overage = packgecapacity[j - 1] - weight[i - 1];
                    //动态规划算法的核心思想
                    if (value[i - 1] + array[i - 1][overage] > array[i - 1][j]) {
                        array[i][j] = value[i - 1] + array[i - 1][overage];
                        path[i][j] = 1;
                    } else
                        array[i][j] = array[i - 1][j];

                } else {
//                    int rest = weight[i - 1] - packgecapacity[j - 1];//剩余的容量
//                    array[i][j]=value[i-1]+array[i-1][rest];

                    array[i][j] = array[i - 1][j];
                }
            }
        }
    }

    public void print() {
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                System.out.print(array[i][j] + "\t\t");
            }
            System.out.println();
        }
    }

    public void betterchoice() {
        int i = path.length - 1;
        int j = path[0].length - 1;
        while (i > 0 && j > 0) {
            if (path[i][j]==1){
                System.out.println("把第"+i+"加入背包");
                j=j-weight[i-1];
            }
            i--;
        }
    }

}

贪心算法

我个人认为贪心算法很重要,能够大大提高程序的效率。

贪婪算法(贪心算法)是指在对问题进行求解时,在每一步选择中都采取最好或者最优(即最有利)的选择,从而希望能够导致结果是最好或者最优的算法

贪婪算法所得到的结果不一定是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果。
举一个案例:
此案例来自于尚硅谷韩顺平老师
这个时候我们要让最少数量的广播台覆盖所有地区。
比方说我们当然可以让5个广播台都用上,但是这样不够贪。我们可以只使用K1,K2,K3,K5。当然也可以有其他组合,所有说贪心算法可能不是最优解,比如说K1成本很大。
我的思路分析,首先我们确定全部地区,也就是[成都, 上海, 广州, 天津, 大连, 杭州, 北京, 深圳]。然后再从K1,K2,K3,K4,K5中获取他们覆盖地区在全部地区中的数量。比方说第一次操作K1 发现覆盖的区域在全部地区中有3个,以此类推K2有3个,K3有3个,K4有2个,K5有2个。那我们可以发现K1,K2,K3所覆盖的地区是最多的,有3个,那我们再取的时候只取最大的,那我们从头开始取,那就取K1(因为没有其他广播电台大于K1的地区)。这里面取3个地区的电台,不取2个地区的电台就体现了贪心算法。
接下来,我们在全部地区中删除K1所覆盖的地区,并且删除K1电台,所有现在全部地区也就是[成都, 广州, 大连, 杭州, 深圳]。,得到了现在所有的未覆盖的地区后,我们就可以按照上一次方法,继续筛选最大的能够覆盖地区的广播台。这里面说明一下,我们在取电台的时候,比方说取到K3的时候,我们还要比较一下之前K2的覆盖地区数量,以及在取K4的时候还要获得K2,K3之中最大的覆盖地区数量,这样我们才能做到贪心,把最大的那个取出来,然后重复K1那时候的操作。
代码如下

public class GreedyTest {
    public static void main(String[] args) {
        HashMap<String, HashSet> station = new HashMap<>();
        HashSet<String> hashSet1 = new HashSet<>();
        HashSet<String> hashSet2 = new HashSet<>();
        HashSet<String> hashSet3 = new HashSet<>();
        HashSet<String> hashSet4 = new HashSet<>();
        HashSet<String> hashSet5 = new HashSet<>();
        HashSet<String> hashSet6 = new HashSet<>();

        hashSet1.add("北京");
        hashSet1.add("上海");
        hashSet1.add("天津");

        hashSet2.add("广州");
        hashSet2.add("北京");
        hashSet2.add("深圳");

        hashSet3.add("成都");
        hashSet3.add("上海");
        hashSet3.add("杭州");

        hashSet4.add("上海");
        hashSet4.add("天津");

        hashSet5.add("杭州");
        hashSet5.add("大连");

        hashSet6.add("");

        station.put("K1",hashSet1);
        station.put("K2",hashSet2);
        station.put("K3",hashSet3);
        station.put("K4",hashSet4);
        station.put("K5",hashSet5);
        station.put(null,hashSet6);


//        System.out.println(station);

        HashSet<String> tempset = new HashSet<>();//当前能够覆盖的地区
        HashSet<String> allarea = new HashSet<>();//所有电台能覆盖的总地区
        HashSet<String> staarea = new HashSet<>();//用于存放交集
        allarea.addAll(hashSet1);
        allarea.addAll(hashSet2);
        allarea.addAll(hashSet3);
        allarea.addAll(hashSet4);
        allarea.addAll(hashSet5);
        tempset.addAll(allarea);
        ArrayList<String> list = new ArrayList<>();
//        System.out.println(allarea);[成都, 上海, 广州, 天津, 大连, 杭州, 北京, 深圳]
//        System.out.println(tempset);[成都, 上海, 广州, 天津, 大连, 杭州, 北京, 深圳]
        String maxkey=null;
        while (tempset.size()!=0){
            maxkey=null;
            for (String key:station.keySet()) {

                HashSet localarea = station.get(key);
                localarea.retainAll(tempset);
                int size = localarea.size();


                HashSet oldset = station.get(maxkey);
                oldset.retainAll(tempset);
                int oldsize= oldset.size();
                //HashSet oldset = station.get(maxkey);
                // oldset.retainAll(tempset);
                // int oldsize= oldset.size();
                //size>oldsize
                //在这里体现了贪心算法,这个是我自己改的的,因为我认为size应该大于之前未覆盖的size
                //而不是老师所说的station.get(maxkey)
                if (size>0&&(maxkey==null||size>oldsize)){
                    maxkey=key;
                }


            }
            tempset.removeAll(station.get(maxkey));
            list.add(maxkey);
        }
        System.out.println(list);
    }
}

普利姆算法(Prim)

普利姆算法在讲的时候,引出一个案例:
此案例来自于尚硅谷韩顺平老师
这个修路问题就是典型的普利姆算法。
修路问题本质就是就是最小生成树问题, 先介绍一下最小生成树(Minimum Cost Spanning Tree),简称MST。
给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树
N个顶点,一定有N-1条边
包含全部顶点
N-1条边都在图中
举例说明(如图:)
求最小生成树的算法主要是普里姆算法和克鲁斯卡尔算法
先介绍一下普利姆算法(文字有点难理解)

普利姆(Prim)算法求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图
普利姆的算法如下:

设G=(V,E)是连通网,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合
若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入集合U中,标记顶点v的visited[u]=1
若集合U中顶点ui与集合V-U中的顶点vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,将边(ui,vj)加入集合D中,标记visited[vj]=1
重复步骤②,直到U与V相等,即所有顶点都被标记为访问过,此时D中有n-1条边
根据这个案例,我们来根据案例来讲,首先我们一般都从A开始找,我们找个A直接相连的村庄,那就是A ->B,A ->C,A ->G,然后发现他们之中最小的权值是A->G=2,那我们就确定了第一步,然后把A->G放入集合中,接下来再继续找A的直接相连的村庄(但是前提,不能找以及访问过的村庄,比如说第一步就访问了的村庄G),然后我们找到A->B,A->G,但是现在又多出来了一个村庄G,那我们需要再从G村庄开始,找和他直接相连的,但是又没被访问过的,我们找完后,将找到的权值和A所找的权值进行对比,找到一个最小的权值,发现是G->B=3,那也就是说我们下一步要把G和B的路修好,那现在就是A->G,G->B.以此类推下面的操作。
可得结果:
在这里插入图片描述
代码如下:

/**
 * 普利姆算法,具体问题:7个村庄修最短且都能连通的路。
 * 代码自己修改了
 */
public class PrimTest {
    public static void main(String[] args) {

        Graph graph = new Graph(7);
        graph.village= new String[]{"A", "B", "C", "D", "E", "F", "G"};
        graph.abutment= new int[][]{
                {10000, 5, 7, 10000, 10000, 10000, 2},
                {5, 10000, 10000, 9, 10000, 10000, 3},
                {7, 10000, 10000, 10000, 8, 10000, 10000},
                {10000, 9, 10000, 10000, 10000, 4, 10000},
                {10000, 10000, 8, 10000, 10000, 5, 4},
                {10000, 10000, 10000, 4, 5, 10000, 6},
                {2, 3, 10000, 10000, 4, 6, 10000},

        };
//        int h1=0;
//        int h2=0;
//        int minlength=10000;
//        ArrayList<Integer> connected = new ArrayList<>();
//        connected.add(1);
//        for (int k=1;k<graph.village.length;k++) {
//            minlength=10000;
//            for (int i :connected) {
//
//                for (int j = 0; j < graph.village.length; j++) {
//                    if (graph.isVisited[j]==0&&graph.abutment[i][j]<minlength){
//                        h1=i;
//                        h2=j;
//                        minlength=graph.abutment[i][j];
//                    }
//                }
//            }
//            System.out.println("连接"+graph.village[h1]+"和"+graph.village[h2]+"长度为"+graph.abutment[h1][h2]);
//            graph.isVisited[h1]=1;
//            graph.isVisited[h2]=1;
//            connected.add(h1);
//            connected.add(h2);
//        }
        graph.calcution(graph.village,graph.isVisited,graph.abutment);//把上面的方法都封装在方法种了

    }

}

class Graph{
    String [] village;
    int [][] abutment;
    int num;
    int [] isVisited;

    public Graph() {
    }

    public Graph(int num) {
        this.num=num;
        isVisited=new int [num];//默认是0
        abutment=new int[num][num];
        village=new String[num];

    }

    /**
     *
     * @param village graph.village.length
     * @param data graph.isVisited
     * @param con graph.abutment
     */
    public  void calcution(String[] village,int [] data,int [][] con){
        int h1=0;
        int h2=0;
        int minlength=10000;
        ArrayList<Integer> connected = new ArrayList<>();
        connected.add(0);
        for (int k=1;k<village.length;k++) {
            minlength=10000;
            for (int i :connected) {

                for (int j = 0; j < village.length; j++) {
                    if (data[j]==0&&con[i][j]<minlength){
                        h1=i;
                        h2=j;
                        minlength=con[i][j];
                    }
                }
            }
            System.out.println("连接"+village[h1]+"和"+village[h2]+"长度为"+con[h1][h2]);
            data[h1]=1;
            data[h2]=1;
            connected.add(h1);
            connected.add(h2);
        }

    }
}

克鲁斯卡尔算法Kruskal

先引出案例:
在这里插入图片描述
克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法。
基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路
具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止
克鲁斯卡尔和前面的普利姆算法,最大的区别就在于克鲁斯卡尔在判断时,需要判断是否出现了回路。
比如说这个案例,通过克鲁斯卡尔算法,我们先找距离最短的F->E=2,以此类推,当我们找到E->D=4后,按照道理应该要找E->C=5,但是我们会发现当把E-C连上时,站点E、C、D已经成为了一条回路,所有这个时候我们应该摒弃E->C=5,找下一个最小值。
个人认为,在克鲁斯卡尔过程中,最核心的就是判断是否为回路,当然前提是,我们要对路径的权值从小到大进行排序。
接下来我张贴一下判断是否为回路的代码
思路是先创建一个7个长度 ,值都为0的数组,然后打比方说E->F那我们就可以认为E的终点是F,那就在这个数组上E点(4)位置上赋值为5,代表终点是E(5),然后我们每次判断回路的时候,都要用这个方法判断,当不是回路的时候,说明一个点是另一个点的终点,那就继续赋值。
如果我们要找一个点的终点,那么应该在这个数组上,从这个起点开始找,一直找他的终点,直到终点的下一个索引为0,这个思路我在代码注释里写得很清楚了。

/**
     * 自己根据老师的代码又想了一个判断是否为回路的方法
     * @param end 初始为7个0的数组
     * @param start 路线的起点
     * @param last 路线的终点
     * @return 1代表是回路; 0代表不是回路,可以加入.
     * 代码分析:
     * 首先打个比方说传<4,5>那我就在数组索引4的位置上,也就是end[4]标记为5
     * 代码里的两个While,第一个while是寻找起点在数组上的一个值 a,而这个值是从起点开始遍历,直到a的下一个值 也就是end[a]==0
     * 也就是差一步就为0的那个值,这个值就是起点的终点
     * 然后再判断终点的终点
     * 如果这两个终点都是一样的,就证明是回路
     *
     * 比方说当执行到<4,6>的时候,数组已经变成了[0,5,3,4,5,0,0]
     * 对起点4进行寻找,发现终点为5,再对6进行寻找,发现终点是它自己
     * 那这个值就可以加入,但是这里要注意把4的值改为6,把4的终点5也改为6.
     */
    public static int whetherLoop(int[] end, int start, int last) {
//        start=end[start];
//        start=end[start];
//
//        start=end[start];
//        while (start != last && start != 0) {
//            start = end[start];
//        }
//        if (start == 0) {
//            //没有回路
//            return 1;
//        } else
//            return 0;
        while (end[start]!=0){
            start=end[start];

        }
        //现在的start是原传进的参数start的终点

        while (end[last]!=0){
            last=end[last];
        }
        if (start==last){
            //终点一样,不能加入
            return 0;
        }
        else return 1;

    }
}

迪杰斯特拉算法Dijkstra

继续引出案例在这里插入图片描图片述
迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个结点到其他结点的最短路径。 它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。

设置出发顶点为v,顶点集合V{v1,v2,vi…},v到V中各顶点的距离构成距离集合Dis,Dis{d1,d2,di…},Dis集合记录着v到图中各顶点的距离(到自身可以看作0,v到vi距离对应为di)
从Dis中选择值最小的di并移出Dis集合,同时移出V集合中对应的顶点vi,此时的v到vi即为最短路径

更新Dis集合,更新规则为:比较v到V集合中顶点的距离值,与v通过vi到V集合中顶点的距离值,保留值较小的一个(同时也应该更新顶点的前驱节点为vi,表明是通过vi到达的)
重复执行两步骤,直到最短路径顶点为目标顶点即可结束
直接分析案例,我们首先创建一个二维数组,标记各个点到其他点的位置。在这个图中,我们先从G开始找,根据顺序找到A,然后再通过A去找下一个点,比如说是B,然后再让G->B,得到G到B的距离,如果G-A,A-B的和小于G-B的值,那我们就可以将G-B的值重新赋值
上代码:

public class DijkstraTest {
    public static void main(String[] args) {
        Graph graph = new Graph(7);
        graph.village=new String[]{"A","B","C","D","E","F","G"};
        graph.abutment=new int[][]{
                {10000,5,7,10000,10000,10000,2},
                {5,10000,10000,9,10000,10000,3},
                {7,10000,10000,10000,8,10000,10000},
                {10000,9,10000,10000,10000,4,10000},
                {10000,10000,8,10000,10000,5,4},
                {10000,10000,10000,4,5,10000,6},
                {2,3,10000,10000,4,6,10000}
        };
//        for (int i = 0; i < graph.abutment.length; i++) {
//            for (int j = 0; j < graph.abutment[i].length; j++) {
//                System.out.printf("%10d",graph.abutment[i][j]);
//            }
//            System.out.println();
//        }
        ArrayList<Route> routes = new ArrayList<>();
        int start=2;
//        graph.isVisited[start]=1;
//        for (int i = 0; i < graph.abutment[start].length; i++) {
//            if (graph.abutment[start][i]<10000){
//                graph.isVisited[i]=1;
//                Route route = new Route(graph.village[start], graph.village[i], graph.abutment[start][i]);
//                routes.add(route);
//            }
//        }
//        Iterator<Route> iterator = routes.iterator();
//        while (iterator.hasNext()){
//            System.out.println(iterator.next());
//        }
        //核心:遍历整个二维数组,如果有三个结点,起始位置到中间位置到终点位置
        //在起始位置到终点位置的二维数组的索引上,将最小值赋给这个索引位
        //然后再输出这一行索引
        for (int i = 0; i < graph.abutment.length; i++) {
            for (int j = 0; j < graph.abutment[i].length; j++) {
                if (j!=start&&i!=start&&((graph.abutment[i][j]+graph.abutment[start][i])<graph.abutment[start][j])){
                    graph.abutment[start][j]=graph.abutment[i][j]+graph.abutment[start][i];
                }
            }
        }
//        for (Route isConnected:routes
//             ) {
//            String secondstart = isConnected.end;
//            int index = getIndex(graph.village, secondstart);
//            for (int i = 0; i < graph.abutment[index].length; i++) {
//                if (graph.abutment[index][i]<10000&&i!=start){
//                    Route route = new Route(graph.village[index], graph.village[i], graph.abutment[index][i]);
//                    routes.add(route);
//                }
//                if (i!=start&&graph.abutment[index][i]<10000&&((graph.abutment[index][i]+graph.abutment[start][index])<graph.abutment[start][i])){
//                    graph.abutment[start][i]=graph.abutment[start][index]+graph.abutment[index][i];
//                }
//            }
//
//        }
        for (int i = 0; i < graph.abutment[start].length; i++) {
            if (graph.abutment[start][i]==10000){
                graph.abutment[start][i]=0;
            }
            System.out.println(graph.abutment[start][i]);
        }

    }
    public static int getIndex(String [] strings,String s){
        for (int i = 0; i < strings.length; i++) {
            if (strings[i]==s){
                return i;
            }
        }
        return 0;
    }

}
class Route{
    String start;
    String end;
    int distance;

    public Route(String start, String end, int distance) {
        this.start = start;
        this.end = end;
        this.distance = distance;
    }

    @Override
    public String toString() {
        return "<"+start+","+end+">"+"="+distance;
    }
}
class Graph{
    String [] village;//村庄名字存放在数组里
    int num;//多少个村庄,走num-1条路
    int [] isVisited;//是否访问过
    int [][] abutment;

    public Graph(int num) {
        this.num=num;
        village=new String[num];
        isVisited=new int[num];
        abutment=new int[num][num];
    }
}

弗洛伊德算法Floyd

案例:
在这里插入图片描述
这里面和迪杰斯特拉算法不一样,这里面要求求出每个村庄到其他村庄的距离。弗洛伊德算法用的是一个很巧妙的方法,三个for循环。
弗洛伊德算法 VS 迪杰斯特拉算法:迪杰斯特拉算法通过选定的被访问顶点,求出从出发访问顶点到其他顶点的最短路径;弗洛伊德算法中每一个顶点都是出发访问点,所以需要将每一个顶点看做被访问顶点,求出从每一个顶点到其他顶点的最短路径。

设置顶点vi到顶点vk的最短路径已知为Lik,顶点vk到vj的最短路径已知为Lkj,顶点vi到vj的路径为Lij,则vi到vj的最短路径为:min((Lik+Lkj),Lij),vk的取值为图中所有顶点,则可获得vi到vj的最短路径
至于vi到vk的最短路径Lik或者vk到vj的最短路径Lkj,是以同样的方式获得

弗洛伊德(Floyd)算法图解分析-举例说明
通过设置起点、中间点、终点。来选取最短的路径并且时时更新。
代码如下:

/**
 * 佛洛依德算法,Floyd。
 * 这个算法很精妙,用了三个for循环,也就是确定一个起点,一个中间点,一个终点
 * 但是要注意在赋值的时候要时时对应,也就是后来的字母还要回过头去连接之前的字母
 * 看之前的是否是最小值,这个可以通过Debug来理解
 */
public class FloydTest {
    public static void main(String[] args) {
        Graph graph = new Graph(7);
        graph.village=new String[]{"A","B","C","D","E","F","G"};
        graph.abutment=new int[][]{
                {10000,5,7,10000,10000,10000,2},
                {5,10000,10000,9,10000,10000,3},
                {7,10000,10000,10000,8,10000,10000},
                {10000,9,10000,10000,10000,4,10000},
                {10000,10000,8,10000,10000,5,4},
                {10000,10000,10000,4,5,10000,6},
                {2,3,10000,10000,4,6,10000}
        };
        //备注老师是第一行为中间,第二行为起点,第三行为终点
        //我是第一行为起点,第二行为中间,第三行为终点
        //所以我这里要回头赋值的操作
        for (int i = 0; i < graph.village.length; i++) {
            for (int j = 0; j < graph.village.length; j++) {
                for (int k = 0; k < graph.village.length; k++) {
                    int len=graph.abutment[i][j]+graph.abutment[j][k];
                    if (len<graph.abutment[i][k]){
                        graph.abutment[i][k]=len;
                        //下面一行一定要写
                        //打比方说从A->D第一次是14
                        //但是从D->A还有更短的路径12,所有要重新把之前的A给重新赋值
                        graph.abutment[k][i]=len;
                    }
                }
            }
        }

        for (int i = 0; i < graph.abutment.length; i++) {
            for (int j = 0; j < graph.abutment[i].length; j++) {
                if (i==j){
                    graph.abutment[i][j]=0;
                }
                System.out.print(graph.village[i]+"到"+graph.village[j]+"的最短距离为"+graph.abutment[i][j]+"\t");
            }
            System.out.println();
        }

    }
}
class Graph{
    String [] village;//村庄名字存放在数组里


    int [][] abutment;

    public Graph(int num) {
            village=new String[num];
            abutment=new int[num][num];
    }
}

马踏棋盘算法

这个马踏棋盘算法要用到回溯算法和贪心算法
在这里插入图片描述
思路分析,我们首先要获取当前马的位置,下一个能跳哪个位置。
代码如下

public static ArrayList<Point> getNextWay(Point curPoint){
//        ArrayList<Point> list = new ArrayList<>();
//        int x;
//        int y;
//        if ((x=point.x-2)>=0&&(y=point.y-1)>=0){//5
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x-1)>=0&&(y=point.y-2)>=0){//6
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x+1)<X&&(y=point.y-2)>=0){//7
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x+2)<X&&(y=point.y-1)>=0){//8
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x+2)<X&&(y=point.y+1)<Y){//1
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x+1)<X&&(y=point.y+2)<Y){//2
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x-1)>=0&&(y=point.y+2)<Y){//3
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x-2)>=0&&(y=point.y+1)<Y){//4
//            list.add(new Point(x,y));
//        }
//        return list;
        //创建一个ArrayList
        ArrayList<Point> ps = new ArrayList<Point>();
        //创建一个Point
        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;

    }

获取完后,就是主函数里的回溯,其中核心的东西已经在代码中写了注释,这里要注意用到了贪心算法,我们在找马能跳哪个位置的时候应该选取能跳的最少的位置,方便回溯起来快。这里面我们用集合的sort重写算法,来帮助我们排序

/**
 * 马踏棋盘算法,核心是回溯
 * 已用贪心算法优化
 */
public class HorseChessTest {
     static int X;
     static int Y;
     static boolean flag;
    public static void main(String[] args) {
        X=8;
        Y=8;
        int[][] chess = new int[X][Y];
        int [] isVisited=new int[X*Y];
        solution(chess,isVisited,0,0,1);
        for (int i = 0; i < chess.length; i++) {
            for (int j = 0; j < chess[i].length; j++) {
                System.out.print(chess[i][j]+"\t");
            }
            System.out.println();
        }


    }
    public  static  void solution(int[][]chess,int [] isVisited,int row,int column,int step){
        isVisited[row*X+column]=1;//表示这个点被访问

        chess[row][column]=step;
        ArrayList<Point> nextWay = getNextWay(new Point(column, row));
        sort(nextWay);
        while (nextWay.isEmpty()!=true){
            Point remove = nextWay.remove(0);
            if (isVisited[remove.y*X+remove.x]==0) {

                solution(chess, isVisited, remove.y, remove.x,step+1);
            }

        }
        //这里flag作用是:比方说一个集合中的一次递归没有到达36步,那我们需要将递归退出后的每一次程序,把当前的二维数组给箐0,并且当作没访问过
        // 因为在中途选择某个集合时,这条路永远走不通
        //相反,如果一个集合的某个数据走到了最后,那么我的flag就置为true
        //也就是if (step<X*Y&&!flag) 这个语句永远不执行
        //所以我在这一个集合数据退出的每一次递归程序中,我的二维数组都不清0
        //这个flag一定要写,因为我调试过后,发现永远没有结果
        //经过分析,因为我每次走到最后一步,我返回递归的时候,我的二维数组和一维数组永远被清空
        //也就是说我的棋盘本来全是数据,然后在结束递归的时候有清空了
        //最重要的判断访问的一维数组被清空了,就代表我的程序死循环
        if (step<X*Y&&!flag){
            chess[row][column]=0;
            isVisited[row*X+column]=0;
        }
        else
            flag=true;

    }
    public static ArrayList<Point> getNextWay(Point curPoint){
//        ArrayList<Point> list = new ArrayList<>();
//        int x;
//        int y;
//        if ((x=point.x-2)>=0&&(y=point.y-1)>=0){//5
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x-1)>=0&&(y=point.y-2)>=0){//6
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x+1)<X&&(y=point.y-2)>=0){//7
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x+2)<X&&(y=point.y-1)>=0){//8
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x+2)<X&&(y=point.y+1)<Y){//1
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x+1)<X&&(y=point.y+2)<Y){//2
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x-1)>=0&&(y=point.y+2)<Y){//3
//            list.add(new Point(x,y));
//        }
//        if ((x=point.x-2)>=0&&(y=point.y+1)<Y){//4
//            list.add(new Point(x,y));
//        }
//        return list;
        //创建一个ArrayList
        ArrayList<Point> ps = new ArrayList<Point>();
        //创建一个Point
        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) {
                int size1 = getNextWay(o1).size();
                int size2 = getNextWay(o2).size();
                if (size1>size2){
                    return 1;
                }
                else if (size1==size2){
                    return 0;
                }
                else return -1;
            }
        });
    }
}


总结

到此为止,我所学的数据结构与算法也已经讲完了,很喜欢韩顺平老师的一句话,“无他,但手熟而”。代码这种东西一定要写得多,练的多。
的确,“无他,但手熟而”,加油,一起进步,最后也快过12点了,今天也是给自己一个满意的答卷了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值