十大算法

二分查找的非递归实现,这个看起来比较好理解一些

二分查找的非递归实现

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;
}

分治算法

分治问题的经典算法,
汉诺塔的分析:
如果只有一个盘需要移动,直接A->C
如果有n>=2的情况,我们总是可以看做两个盘:最下面的盘,上面的所有盘
这个时候我们需要做的就是先把最上面的盘从A->B,把最下边的盘从A->C,把B塔的所有盘从B->C

public static void hanoiTower(int num,char a,char b,char c){
    //如果只有一个盘,
    if (num == 1) {
        System.out.println("第一个盘从"+a+"->"+c);
    } else {
        //如果我们有n>=2的情况,我们总是可以看作是两个盘,最下面的盘,和上面的盘
        //借助c把a上的盘移动到b
        hanoiTower(num-1,a,c,b);
        //把最下边的盘 a->c
        System.out.println("第"+num+"个盘从"+a+"->"+c);
        //把b塔的所有盘移动到c
        hanoiTower(num-1,b,a,c);
    }
}

动态规划算法

01背包问题的思路:
表的第一行和第一列都是0,
当w[i]>j时, v[i][j] = v[i-1][j] //当准备加入新增的商品的容量大于当前的容量时,就直接使用上一个单元格的装入策略
当j>w[i] 时,v[i][j] = v[i-1][j], v[i-1][j-w[i]]+v[i] 中的最大值
当准备加入的新增的商品的容量小于等于当前背包的容量
装入的方式:v[i-1] [j] 就是上一个单元格的最大值 v[i]表示当前商品的价值
v[i-1] [ j - w [ i ] ] :表示装入i-1商品到剩余空间 j-w[i]
具体的代码实现

for (int i = 1; i < v.length; i++) { //不处理第一行, i时从1开始的
    for(int j=1;j<v[0].length;j++){ //不处理第一列
        //公式
        if (w[i-1]>j){
            v[i][j] = v[i-1][j];
        } else {
            //说明:因为我们的i 从1开始,因此公式需要调整为
            //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
                path[i][j] = 1;
            } else {
                v[i][j] = v[i-1][j];
                //把当前的情况记录到path
                path[i][j] = 1;
            }
        }
   }
}
//查看最大放置的商品
int i = path.length-1;  //行的最大下标
int j = path[0].length-1; //列的最大下标
while (i > 0 && j>0 ) { //从path的最后开始找
    if (path[i][j] == 1) {
        System.out.print(i+"商品放入背包");
        j-=w[i-1];
    }
    i--;
}

KMP算法

暴力匹配算法:
  • 如果当前字符匹配成功 str[i] == str[j]
  • 那么 i++,j++
  • 如果失配 i=i-(j-1) ,j = 0 ,相当于每次匹配失败时,i回溯,j被置为0,
  • 用暴力方法解决的话就会有大量的回溯,比较浪费时间

代码演示

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; //i索引指向s1
        int j = 0; //j索引指向s2
        while (i < s1Len && j < s2Len) {//保证匹配时,不越界
            if (s1[i]==s2[j]){
                i++;j++;
            } else { //没有匹配成功
                i = i-(j-1);
                j=0;
            }
        }
        //根据j的值来判断是否匹配成功
        if (j == s2Len) {
            return i-j;
        }else {
            return -1;
        }
    }

KMP算法的具体实现,分为两步,一步是求next数组,第二部是具体的实现

public static int kmpSearch(String str1,String str2){

        //遍历
        for (int i = 0,j=0; i < str1.length(); i++) {
            //如果两个不相等的时候
            while (j>0&&str1.charAt(i)!=str2.charAt(j)){
                j = kmpNext(str2)[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; //如果字符串是长度为1,它的部分匹配值就是0
        for (int i = 1,j=0; i <dest.length(); i++) {
            //当条件不等时,从next[j-1]中获取新的j
            //知道我们发现有相等时再退出
            while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
                j=next[j-1];
            }

            //当这个条件满足时,部分匹配值就需要+1
            if (dest.charAt(i) == dest.charAt(j)){
                j++;
            }
            next[i] = j;
        }
        return next;
    }

贪心算法

  • 贪心算法指在对问题进行求解时,每一步都选择最好或者最优的解,从而希望能够导致结果是最好或者最优的算法
  • 但是贪心算法不一定是最优解

以下是一个例子,

public class GreedyAlgorithm {
    public static void main(String[] args) {
        //创建广播电台,放入道Map
        HashMap<String, HashSet> broadcasts = new HashMap<String, HashSet>();
        //将各个电台放入到broadcasts
        HashSet<String> hashSet1 = new HashSet<String>();
        hashSet1.add("北京");
        hashSet1.add("上海");
        hashSet1.add("天津");

        HashSet<String> hashSet2 = new HashSet<String>();
        hashSet2.add("广州");
        hashSet2.add("上海");
        hashSet2.add("深圳");

        HashSet<String> hashSet3 = new HashSet<String>();
        hashSet3.add("成都");
        hashSet3.add("上海");
        hashSet3.add("杭州");

        HashSet<String> hashSet4 = new HashSet<String>();
        hashSet4.add("天津");
        hashSet4.add("上海");

        HashSet<String> hashSet5 = new HashSet<String>();
        hashSet5.add("杭州");
        hashSet5.add("大连");

        //把所有的电台放入到map中
        broadcasts.put("k1", hashSet1);
        broadcasts.put("k2", hashSet2);
        broadcasts.put("k3", hashSet3);
        broadcasts.put("k4", hashSet4);
        broadcasts.put("k5", hashSet5);

        //allAreas 存放所有的地区
        HashSet<String> allAreas = new HashSet<String>();
        allAreas.add("北京");
        allAreas.add("上海");
        allAreas.add("天津");
        allAreas.add("广州");
        allAreas.add("成都");
        allAreas.add("杭州");
        allAreas.add("大连");
        allAreas.add("深圳");

        //创建一个ArrayList,存放选择的电台集合
        ArrayList<String> selects = new ArrayList<String>();

        //定义一个临时的集合,在遍历的过程中,存放还没有选择
        HashSet<String> tempSet = new HashSet<String>();

        //定义一个maxKey,保存在一次遍历过程中能够覆盖最多地区对应的电台的key
        String maxKey = null;
        //如果maxKey!=null,则会加入到 selects
        while (allAreas.size() != 0) {
            //每进行一次while,需要
            maxKey = null;

            //遍历broadcasts,取出对应key
            for (String key :
                    broadcasts.keySet()) {
                //每进行一次for循环,
                tempSet.clear();

                //当前这个key能够覆盖的地区
                HashSet<String> areas = broadcasts.get(key);
                tempSet.addAll(areas);
                //求出两个集合的交集并复制给tempSet
                tempSet.retainAll(allAreas);
                //如果当前集合包含的未覆盖地区数量,比maxKey指向的集合未覆盖的地区还多
                //就把最合适的maxKey赋值
                if (tempSet.size() > 0 &&
                        (maxKey == null || tempSet.size() > broadcasts.get(maxKey).size())) {
                    maxKey = key;
                }
            }
            //maxKey!=null ,就将maxKey加入到selects
            if (maxKey != null) {
                selects.add(maxKey);
                //将maxkey指向的电台从allAreas中去掉
                allAreas.removeAll(broadcasts.get(maxKey));
            }
        }
        System.out.println("得到的选择结果是"+selects);
    }
}

Prim算法

  • 设G=(V,E)是连通网,T(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合,
  • 从顶点u开始构造最小生成树,从集合V中取出顶点u放入集合U中,并且标记该顶点已经被访问visited[u]=1
  • 若集合U中顶点ui与集合V-U中的顶点vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,并将边(ui,vj)加入到集合D中,并且标记vj被访问过visited[vj]=1
  • 重复2步骤,也就是寻找权值最小的边,知道U和V相等,即所有的顶点都被标记为访问过,此时D中又n-1条边

具体的代码和注解如下

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 max = 10000;
        //邻接矩阵关系使用二维数组来进行表示
        int [][] weight = new int[][]{
                {max,5,7,max,max,max,2},
                {5,max,max,9,max,max,3},
                {7,max,max,max,8,max,max},
                {max,9,max,max,max,4,max},
                {max,max,8,max,max,5,4},
                {max,max,max,4,5,max,6},
                {2,3,max,max,4,6,max}
        };

        //创建MGraph对象
        MGraph graph = new MGraph(verxs);
        MinTree minTree = new MinTree();
        minTree.createGraph(graph,verxs,data,weight);
        //输出邻接矩阵
        minTree.showGraph(graph);

        //测试prim算法
        minTree.prim(graph,0);
    }
}

//创建最小生成树
class MinTree{
    //创建图的邻接矩阵

    /**
     *
     * @param graph 图对象
     * @param verxs 图对应的顶点个数
     * @param data 图的各个顶点的值
     * @param weight 图的邻接矩阵
     */
    public void createGraph(MGraph graph,int verxs,char data[],int[][] weight){
        int i,j;
        for(i=0;i<verxs;i++){
            graph.data[i] = data[i];
            for(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));
        }
    }

    //编写prim算法,得到最小生成树

    /**
     *
     * @param graph 图
     * @param v 表示从图的第几个顶点开始生成
     */
    public void prim(MGraph graph,int v){
        //1.visited[] 标记节点是否被访问过
        int visited[] = new int[graph.verxs];
        //visited[] 默认元素的值都是0,表示没有访问过
        /*for (int i = 0; i < visited.length; i++) {
            visited[i] = 0;
        }*/

        //把当前这个节点标记为已访问,
        visited[v] = 1;
        //h1和h2记录两个顶点的下标
        int h1 = -1;
        int h2 = -1;
        int minWeight = 10000; //将minWeight初始化为一个大数,后面遍历会被替换
        for (int k = 1; k < graph.verxs; k++) {//verxs个顶点,prim算法结束之后有verxs-1条边
            //这是确定每一次生成的子图,和哪个节点的距离最近,
            //相当于n的2次方把所有的边遍历一遍选出和当前图相连的并且最短的一个边加入图中
            for(int i=0;i<graph.verxs;i++){
                for(int j=0;j<graph.verxs;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重新设置为最大值
            minWeight = 10000;
        }

    }
}

class MGraph{
    int verxs; //标售图的节点个数
    char[] data; //存放节点数据
    int[][] weight; //存放边,邻接矩阵

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

克鲁斯卡尔算法

  • 按照权值从小到大的顺序选择n-1条边,并且保证这n-1条边不构成回路
  • 首先构造一个只含有n个顶点的森林,然后按照权值从小到大选择边加入到森林中,并使森林不产生回路,直到森林变成一棵树为止
  • 我们加入的边的两个顶点不能都指向同一个终点,这样就会形成回路

代码实现

public class KruskalCase {
    private int edgeNum; //边的个数
    private char[] vertexs; //顶点数组
    private int[][] matrix; //邻接矩阵
    //使用INF表示两个顶点不连通
    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();
        kruskalCase.kruskal();
    }

    //构造器
    public KruskalCase(char[] vertexs,int [][] matrix){
        //初始化顶点数和边的个数
        int vlen = vertexs.length;

        //初始化顶点
        this.vertexs = new char[vlen];
        for (int i = 0; i < vertexs.length; i++) {
            this.vertexs[i] = vertexs[i];
        }
        //this.vertexs = vertexs;
        //初始化边
        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[] result = new EData[edgeNum];

        //获取图中所有的边的集合,一共有12条边
        EData[] edges = getEdges();
        System.out.println("图的边的集合"+Arrays.toString(edges)+",共"+edges.length+"条边");;

        //按照边的权值大小进行排序
        sortEdges(edges);

        //遍历edges数组,将边添加到最小生成树,并判断是否构成了回路
        for (int i = 0; i < edgeNum; i++) {
            //获取第i条边的第一个顶点(起点)
            int p1 = getPosition(edges[i].start);
            //获取第i条边的第二个顶点
            int p2 = getPosition(edges[i].end);

            //获取p1这个顶点在已有的最小生成树中终点
            int m = getEnd(ends,p1);
            //获取p2这个顶底安在已有最小生成树中的终点
            int n = getEnd(ends,p2);
            //判断是否构成回路
            if (m != n) {//准备加入的这个边,他们并不指向同一个终点,也就是没有构成回路
                ends[m] = n; //设置起点的终点是终点
                result[index++] = edges[i]; //有一条边加入到result数组
            }
        }

        for (int i = 0; i < index; i++) {
            System.out.println(result[i]);
        }
        //统计并打印最小生成树
        //System.out.println("最小生成树为:"+Arrays.toString(result));;
    }

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

    //把所有的边进行排序
    /**
     *  功能:对边进行排序
     * @param edges 边的集合
     */
    private void sortEdges(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;
                }
            }
        }
    }

    /**
     *  根据顶点返回顶点对应的下标
     * @param ch 顶点的值, 比如'A'
     * @return 返回ch顶点对应的下标,如果找不到,返回-1
     */
    private int getPosition(char ch){
        for (int i = 0; i < vertexs.length; i++) {
            if (vertexs[i] == ch) {
                return i;
            }
        }
        //找不到,返回-1
        return -1;
    }

    /**
     *  获取图中的边,放到EData[]数组中,后面我们需要遍历该数组
     *  通过matrix邻接矩阵来获取
     *  存放的形式:[['A','B',12],['B','F',7]]
     * @return
     */
    private EData[] getEdges(){
        int index = 0;
        EData[] result = 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) {
                    result[index++] = new EData(vertexs[i],vertexs[j],matrix[i][j]);
                }
            }
        }
        return result;
    }

    /**
     * 获取下标为i的顶点的终点,用于后面判断两个顶点终点是否相同
     * @param ends 这个数组就对应了各个顶点对应的终点是哪一个,动态生成的
     * @param i 表示传入的顶点的下标
     * @return 返回的就是下标为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;
    }

    //重写toString,便于输出边
    @Override
    public String toString() {
        return "EData[" +
                "<" + start +
                ", " + end +
                ">=" + weight +
                ']';
    }
}

这个和prim算法都可以得到最小生成树

迪杰斯特拉算法

  • 设置触发顶点v,顶点集合V{v1,v2,vi…},v到V中各个顶点的距离构成距离集合Dis,v到vi的距离为di
  • 从dis中选择值最小的di并移出dis集合,同时移出V集合中对应的顶点vi,此时v到vi即为最短路径
  • 更新dis集合,更新规则为,比较v到V集合中顶点的距离值,与v通过vi到V集合中顶点的距离值,保留值较小的一个(同时也应该更新顶点的前驱节点为vi,表明是通过vi到达的)
  • 重复执行两步骤,直到最短路径顶点为目标顶点即可结束
  • 实际操作中需要使用到三个数组,V:记录被访问过的顶点, Dis:记录出发顶点到其他顶点的最短距离,在不断变化, 还有一个记录各个顶点的前驱顶点的数组

求图的最短路径,利用的是广度优先的搜索算法

public class DijkstraAlgorithm {
    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[]{N,5,7,N,N,N,2};
        matrix[1] = new int[]{5,N,N,9,N,N,3};
        matrix[2] = new int[]{7,N,N,N,8,N,N};
        matrix[3] = new int[]{N,9,N,N,N,4,N};
        matrix[4] = new int[]{N,N,8,N,N,5,4};
        matrix[5] = new int[]{N,N,N,4,5,N,6};
        matrix[6] = new int[]{2,3,N,N,4,6,N};

        Graph graph = new Graph(vertex,matrix);
        graph.showGraph();

        graph.djs(6);
        graph.showDjs();
    }
}

class Graph{
    private char[] vertex; //顶点数组
    private int[][] matrix; //邻接矩阵
    private VisitedVertex vv;
    public Graph(char[] vertex,int[][] matrix){
        this.vertex = vertex;
        this.matrix = matrix;
    }

    public void showDjs(){
        vv.show();
    }

    public void showGraph(){
        for (int[] link :
                matrix) {
            System.out.println(Arrays.toString(link));
        }
    }

    /**
     * 迪杰斯特拉算法的实现
     * @param index 出发顶点对应的下标
     */
    public void djs(int index){
        vv = new VisitedVertex(vertex.length,index);
        update(index); //更新index顶点到周围顶点的距离

        for(int j=1;j<vertex.length;j++){
            index = vv.updateArr(); //选择并返回新的访问顶点
            update(index); //更新新的顶点到周围地暖管点的距离和前驱节点
        }
    }


    /**
     * 更新index下标顶点到周围顶点的距离和周围顶点的前驱节点
     * @param index
     */
    private void update(int index){
        int len = 0;
        //根据遍历邻接矩阵的matrix[index]行
        for (int i = 0; i < matrix[index].length; i++) {
            //len:出发顶点到index距离+index到i的距离,求最短路径的试探
            len = vv.getDis(index)+matrix[index][i];
            if (!vv.isUse(i)&& len<vv.getDis(i)){
                vv.updatePre(i,index); //更新i顶点的前驱为index
                vv.updateDis(i,len); //更新最短路径
            }
        }

    }
}

class VisitedVertex{
    private int[] already_arr;
    private int[] pre_visited;
    private int[] dis;

    /**
     *
     * @param length :表示顶点的个数
     * @param index :出发顶点对应的下标,比如G顶点,传入的下标就是6
     */
    public VisitedVertex(int length,int index){
        this.already_arr = new int[length];
        this.pre_visited = new int[length];
        this.dis = new int[length];
        Arrays.fill(dis,65535);
        this.already_arr[index] = 1; //设置出发顶点被访问过
        this.dis[index] = 0; //设置出发顶点自身的访问距离为0
    }

    /**
     * 判断index顶点是否被访问过
     * @param index
     * @return
     */
    public boolean isUse (int index){
        return already_arr[index] == 1;
    }

    /**
     *  更新出发顶点到index顶点的距离,越来越小
     * @param index
     * @param len
     */
    public void updateDis(int index,int len){
        dis[index] = len;
    }

    /**
     * 更新顶点pre的前驱为index节点
     * @param pre
     * @param index
     */
    public void updatePre(int pre,int index){
        pre_visited[pre] = index;
    }

    /**
     * 返回出发顶点到index顶点的距离
     * @param index
     */
    public int getDis(int index){
        return dis[index];
    }

    /**
     * 继续选择并返回新的访问顶点,
     * 从还没有访问过的节点里面找一个距离最小的,并且把下标返回
     * @return
     */
    public int updateArr(){
        int min = 65535,index=0;
        for (int i = 0; i < already_arr.length; i++) {
            if (already_arr[i] == 0 && dis[i] < min) {
                min = dis[i];
                index = i;
            }
        }
        already_arr[index] = 1;
        return index;
    }

    /**
     *  将三个数组的情况输出
     */
    public void show(){
        System.out.println("======================");
        for (int i :
                already_arr) {
            System.out.print(i+" ");
        }
        System.out.println();
        System.out.println("======================");
        for (int i :
                pre_visited) {
            System.out.print(i+" ");
        }
        System.out.println();
        System.out.println("======================");
        for (int i :
                dis) {
            System.out.print(i+" ");
        }

        char[] vertex = {'A','B','C','D','E','F','G'};
        int count = 0;
        for (int i :
                dis) {
            if (i != 65535) {
                System.out.println(vertex[count]+"("+i+")");
            } else {
                System.out.println("N");
            }
            count++;
        }
        System.out.println();
    }
}

弗洛伊德算法

  • 设置顶点vi到顶点vk的最短路径Lik,顶点vk到vj的最短路径为Lij,则vi到vj的最短路径为min(lik+lkj),lij , vk的取值为图中所有顶点,则可获得vi到vj的最短路径
  • vi到vk的最短路径Lik或者vk到vj的最短路径Lkj,以同样的方式获得
  • 一共遍历三次,先确定中间节点,然后再找其他节点以路过其他各个节点的最短路径,进行比较,最后得出最短路径

代码很简单,理解起来也不难,但是时间复杂度为n的三次方

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顶点,经过k
                for (int j = 0; j < dis.length; j++) {
                    len = dis[i][k]+dis[k][j]; //i到j的距离的一种可能
                    if (len<dis[i][j]){
                        dis[i][j] = len; //更新最短距离
                        pre[i][j] = pre[k][j]; //更新前驱顶点
                    }
                }
            }
        }
    }

马踏棋盘问题

这是图的深度遍历的一个应用

  • 创建一个二维数组的棋盘
  • 将当前位置设置为已访问,然后根据当前位置,计算马儿还能走哪些位置,并放入到一个集合中ArrayList,最多有8个位置,每走一步,就让step+1,
  • 遍历ArrayList中存放的所有位置,看哪个可以走通,如果能够走通,就继续走,走不通就回溯,
  • 判断是否跑完了整个棋盘,室友step和棋盘大小进行比较,
  • 马儿的不同走法,会得到不同的结果,效率也会有影响

代码的实现,使用的是递归

    /**
     * 完成其实周游问题的算法
     * @param chessboard 棋盘
     * @param row 马儿当前的坐标
     * @param column
     * @param step 当前是马儿走的第几步
     */
    public static void traversalChessboard(int[][] chessboard,int row,int column,int step){
        chessboard[row][column] = step;
        visited[row*X+column] = true; //标记该位置已经访问
        //获取当前位置可以走的下一个位置的集合
        ArrayList<Point> ps = next(new Point(row, column));
        //遍历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][column] = 0;
            visited[row*X+column] = false; //说明按照这个路线走不行,那就把棋盘恢复
        } else {
            finished = true;
        }
    }

    /**
     * 根据当前的位置计算马儿还能走哪些位置
     * @param curPoint
     * @return
     */
    public static ArrayList<Point> next(Point curPoint){

        //创建一个ArrayList
        ArrayList<Point> ps = new ArrayList<Point>();
        //创建一个Point
        Point p1 = new Point();
        if ((p1.x = curPoint.x-2)>=0&&(p1.y=curPoint.y-1)>=0){
            ps.add(new Point(p1));
        }
        if ((p1.x = curPoint.x-1)>=0&&(p1.y=curPoint.y-2)>=0){
            ps.add(new Point(p1));
        }
        if ((p1.x = curPoint.x+1)< X &&(p1.y=curPoint.y-2) >= 0){
            ps.add(new Point(p1));
        }
        if ((p1.x = curPoint.x+2)<X&&(p1.y=curPoint.y-1)>=0){
            ps.add(new Point(p1));
        }
        if ((p1.x = curPoint.x+2)<X&&(p1.y=curPoint.y+1)<Y){
            ps.add(new Point(p1));
        }
        if ((p1.x = curPoint.x+1)<X&&(p1.y=curPoint.y+2)<Y){
            ps.add(new Point(p1));
        }
        if ((p1.x = curPoint.x-1)>=0&&(p1.y=curPoint.y+2)<Y){
            ps.add(new Point(p1));
        }
        if ((p1.x = curPoint.x-2)>=0&&(p1.y=curPoint.y+1)<Y){
            ps.add(new Point(p1));
        }
        return ps;
    }

这里可以使用贪心算法进行优化一下,既然一定要进行遍历,那为什么不能先把那些比较短的进行遍历呢

/**
     * 根据当前这一步的所有的下一步的选择位置,进行非递减排序
     * @param ps
     */
    public static void sort (ArrayList<Point> ps){
        ps.sort(new Comparator<Point>() {
            public int compare(Point o1, Point o2) {
                int count1 = next(o1).size();
                int count2 = next(o2).size();
                return count1<=count2?-1:1;
            }
        });
    }

这样做的思路就是尽可能的先把最短的进行遍历完成,然后再慢慢找复杂的,这样对于求出来一个解来说还是比较方便的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值