本文是学习尚硅谷韩顺平老师的数据结构与算法中十大算法部分的笔记。

详细见:
尚硅谷韩顺平老师的数据结构与算法

文章目录

  • 算法
    • 概述:
  • 分治算法
    • 概述:
    • 分支法解决汉诺塔问题:
  • 动态规划算法
    • 概述:
      • 动态规划解决单一背包问题:
  • KMP算法
    • 概述:
    • 代码实现:
  • 贪心算法
    • 概述:
    • 贪心算法解决集合覆盖问题:
  • 普利姆(PRIM)算法
    • 概述:
    • 普利姆算法实现公交站问题:
  • 克鲁斯卡尔(Kruskal)算法
    • 概述:
    • 克鲁斯卡尔解决公交站问题
  • 迪杰斯特拉算法
    • 概述:
    • 迪杰斯特拉算法解决邮差路径问题:
  • 弗洛伊德算法:
    • 概述:
    • 弗洛伊德算法解决邮差送报问题
  • 马踏棋盘算法:
    • 概述:
    • 代码实现:

算法

概述:

1、定义

用来解决问题的思路

是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。

2、算法的特性

  1. 输入

  2. 输出

  3. 有穷性:在有限的步骤中执行出结果

  4. 确定性:一定数据在运行算法后有确定的结果

  5. 可行性:能够分析解决实际问题

3、算法的基本要求

  1. 正确性

  2. 可读性

  3. 健壮性

  4. 时间复杂度:算法占用的时间,运算速度。

  5. 空间复杂度:算法在运行时候占用的内存,占用的资源。

分治算法

概述:

一种把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并的算法。

是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)。

核心思想是将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

分治法所能解决的问题一般具有以下几个特征:

  1. 该问题的规模缩小到一定的程度就可以容易地解决

  2. 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

  3. 利用该问题分解出的子问题的解可以合并为该问题的解;

  4. 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

分治法的使用一般有以下步骤:

分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题

合并:将各个子问题的解合并为原问题的解。

常用于解决以下问题:

(1)二分搜索

(2)大整数乘法

(3)Strassen矩阵乘法

(4)棋盘覆盖

(5)合并排序

(6)快速排序

(7)线性时间选择

(8)最接近点对问题

(9)循环赛日程表

(10)汉诺塔

分支法解决汉诺塔问题:

代码实现:

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 {//如果我们有 n >= 2 情况,我们总是可以看做是两个盘 1.最下边的一个盘 2. 上面的所有盘//1. 先把 最上面的所有盘 A->B, 移动过程会使用到 chanoiTower(num - 1, a, c, b);//2. 把最下边的盘 A->CSystem.out.println("第" + num + "个盘从 " + a + "->" + c);//3. 把B塔的所有盘 从 B->C , 移动过程使用到 a塔  hanoiTower(num - 1, b, a, c);}}}

动态规划算法

概述:

动态规划算法的核心是将大问题划分为小问题进行解决,从而逐步获取目标解的方法。

动态规划与分治算法类似,区别在动态规划解决的问题往往子问题的解是下一个问题的解之一每个解是存在关系,逐步递进的

动态规划可以通过类似填表的方式解决

在使用动态规划算法时核心是要推出递归公式

动态规划常用问题:

  1. 硬币找零
  2. 字符串相似度/编辑距离(edit distance)
  3. 最长公共子序列(Longest Common Subsequence,lcs)
  4. 最长递增子序列(Longest Increasing Subsequence,lis)
  5. 最大连续子序列和/积
  6. 矩阵链乘法
  7. 背包问题
  8. 有代价的最短路径
  9. 瓷砖覆盖(状态压缩DP)
  10. 工作量划分
  11. 三路取苹果

动态规划解决单一背包问题:

题目描述

一个给定容量的背包、若干具有一定价值和重量的商品。选择放入该背包的商品价值最大。每个物品最多放一个。

代码实现:

public class KnapsackProblem {public static void main(String[] args) {int[] w = { 1, 4, 3 };// 物品的重量int[] val = { 1500, 3000, 2000 }; // 物品的价值 这里val[i] 就是前面讲的v[i]int m = 4; // 背包的容量int n = val.length; // 物品的个数int v[][] = new int[n + 1][m + 1];// 开始动态规划// 用遍历来完成表格//核心是选与不选,构建动态规划表for (int i = 1; i < v.length; i++) { //for (int j = 1; 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]]);     //如果小于或者等于,可以加入商品,就要比较加入与不加入的价格差别,此处比较不加入跟加入的价格过程就是用到了上一个问题的解的规律,体现了动态规划中的根据上一问题的解来推出这一问题的解的规律。}}}for (int i = 0; i <= n; i++) {v[i][0] = 0;}for (int i = 0; i <= m; i++) {v[0][i] = 0;}for (int i = 0; i <= n; i++) {for (int j = 0; j <= m; j++) {System.out.print(v[i][j] + " ");}System.out.println();}}}

KMP算法

概述:

KMP算法,又称模式匹配算法,能够在线性时间内判定字符串 T 是否为S 的子串,并求出字符串 T 在 S 中各次出现的位置。

KMP算法的核心是根据已经遍历的字符串中建立起一个部分匹配数组,next数组,在next数组中已经储存了已遍历字符的遍历数据,就是最长前缀后缀相同的长度,这个可以被利用与遍历过程中根据遍历过的字符串减少重复遍历的步骤。

代码实现:

public class KMPAlgorithm {public static void main(String[] args) {String str1 = "ABCSD ABCDF ASDCFG ABCDABCESADW";String str2 = "ABCDABCE";
//      int next[] = getNext(str2);
//      System.out.println(KMPAlgorithm(str1, str2, next));int next[] = getNext("ABCABCABCABCD");for(int i= 0;i<next.length;i++) {System.out.println(next[i]);}}/** kmp匹配核心是根据next数组储存的匹配记录来移动数组,避免匹配过程的重复步骤* str1:字符串* str2:目标匹配字串* next[]:next数组*/public static int KMPAlgorithm(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角标,移动原则也是依赖KMP的思想j =  next[j - 1];                                            //当目前的匹配不上后就会查看next数组中的j-1,也就是已经匹配过的数据的下标next数组位置}                                                               //就是在比较字符串的前n位跟字符串后除了最后一个的n位已经是相同的了,再次比较最后一个跟//第n+1个if(str1.charAt(i) == str2.charAt(j)) {                            j++;                                                      //匹配的话字串坐标+1   }                                                               //不匹配的话j不处理,i++(j放到前面循环开始的时候处理)if(j == str2.length()) {            return i - j + 1;                                          //找到的话返回i的开始值   }}return -1;}public static int [] getNext(String str) {                                     //根据字符返回next数组,next数组记录最长前缀后缀相同值(不包括头、尾)int [] next = new int[str.length()];                                      //定义next数组next[0] = 0;                                                             //第一个先确定为零for(int i = 1,j = 0;i < str.length();i++) {                                    //循环遍历字符串while(j > 0 && str.charAt(i) != str.charAt(j)) {                       //核心:用kmp的思想来将j定位,若最长的不匹配,用最长的前面一位匹配最长长度来开始匹配//而不用从头开始,也是一种避免重复的操作j = next[j - 1];}if(str.charAt(i) == str.charAt(j)) {                                  //相同的话j++ j++;}next[i] = j;                                                            //把当前值设为j}return next;}
}

贪心算法

概述:

贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅仅是在某种意义上的局部最优解。

贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。

必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性(即某个状态以后的过程不会影响以前的状态,只与当前状态有关。)

贪心算法最终的选择不一定会是最优解

贪心算法的基本思路是建立数学模型来描述问题,把求解的问题分成若干个子问题,对每个子问题求解,得到子问题的局部最优解,把子问题的解局部最优解合成原来问题的一个解。

贪心算法经典问题:

  1. 货币选择问题

  2. 区间调度问题

  3. 字典序最小问题

  4. 集合覆盖问题

贪心算法解决集合覆盖问题:

题目要求:

选择电台覆盖所有地区。

代码实现:


public class GreedyAlgorithm {public static void main(String[] args) {//先创建所有广播台集合HashMap<String,HashSet<String>> broadcasts = new HashMap<String, HashSet<String>>();//将各个电台放入到broadcastsHashSet<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("大连");//把电台放入到广播台集合中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<String> selects = new ArrayList<String>();//定义一个临时的集合, 在遍历的过程中,存放遍历过程中的电台覆盖的地区和当前还没有覆盖的地区的交集HashSet<String> tempSet = new HashSet<String>();HashSet<String> comSet = new HashSet<String>();while(allAreas.size() != 0) {String maxIndex = null;for(String k1 : broadcasts.keySet()) {//每进行一次循环,tempSet要清空tempSet.clear();tempSet.addAll(broadcasts.get(k1));              //将当前遍历的数据地区放到tempSet中tempSet.retainAll(allAreas);                  //求tempSet跟allAreas的交集//贪心算法的核心,每次找最优解if(tempSet.size() > 0 ) {if(maxIndex == null ) {maxIndex = k1;}else{comSet.clear();comSet.addAll(broadcasts.get(k1));comSet.retainAll(allAreas);if(tempSet.size() > 0 && tempSet.size() > comSet.size()) {maxIndex = k1;}}}}//如果此时manIndex不为空,则开始加入到selects集合中if(maxIndex != null) {selects.add(maxIndex);//再把目标集合中的数据删除allAreas.removeAll(broadcasts.get(maxIndex));}}System.out.println("得到的选择结果是" + selects);//[K1,K2,K3,K5]}
}

普利姆(PRIM)算法

概述:

普利姆算法是一种生成图的最小生成树的问题,最小生成树是指在n个顶点的图中找出只有n-1条边的联通n个顶点的最小连通图的算法。

普利姆算法实际就是逐步遍历所有节点的所有路径,选出一条最小的路径并且路径双顶点都未访问过(不构成回路),直到遍历所有顶点,算法完成。

实现步骤:

  1. 输入:一个加权连通图,其中顶点集合为V,边集合为E;

  2. 初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;

  3. 重复下列操作,直到Vnew = V:
    a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
    b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;

  4. 输出:使用集合Vnew和Enew来描述所得到的最小生成树。

普利姆算法实现公交站问题:

题目要求:

有n个地点,要求做出连通n个地点的最小公交线路。

public class PrimAlgorithm {public static void main(String[] args) {// 测试看看图是否创建okchar[] data = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };int verxs = data.length;// 邻接矩阵的关系使用二维数组表示,10000这个大数,表示两个点不联通int[][] weight = 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 }, };// 创建MGraph对象MGraph graph = new MGraph(verxs);// 创建一个MinTree对象MinTree minTree = new MinTree();minTree.createGraph(graph, verxs, data, weight);minTree.prim(graph, 1);}
}class MinTree {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.print(Arrays.toString(link));System.out.println();}}//编写prim算法// 第二个参数表示开始遍历的下标点public void prim(MGraph graph,int v) {int visited [] = new int[graph.verxs];     //记录遍历的情况,若为0表示未遍历System.out.println(Arrays.toString(visited));int minWeight = 10000;                       //记录当前的最小权值,初始化设为最大值,作用是可以选择到可以遍历的路径int h1 = -1;                                //h1,h2用来记录最小顶点相连的下标int h2 = -1;visited[v] = 1;                               //先将当前的坐标设置为遍历过 for(int k = 1 ;k<graph.verxs ; k ++) {        //循环遍历节点的总数-1次h1 = -1;h2 = -1;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 = graph.weight[i][j];h1 = i;h2 = j;}}}System.out.println("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight);//把指向的最小值设置为已遍历visited[h2] = 1;minWeight = 10000;             //重新把最小值设置为最大,可以寻找下一个可以遍历的节点}}
}class MGraph {int verxs; // 存放节点数char[] data; // 存放节点数据int[][] weight; // 存放边的权值,本图是用邻接矩阵在做public MGraph(int verxs) {super();this.verxs = verxs;data = new char[verxs];weight = new int[verxs][verxs];}}

克鲁斯卡尔(Kruskal)算法

概述:

Kruskal算法也是一种寻找最小路径的算法,与普利姆算法的不同之处在于:

Kruskal算法每次寻找的是最小的边,在不构成回路的条件下循环寻找未利用的最小边,直到连接所有的顶点。

基本步骤:

  1. 记Graph中有v个顶点,e个边

  2. 新建图Graphnew,Graphnew中拥有原图中相同的e个顶点,但没有边

  3. 将原图Graph中所有e个边按权值从小到大排序

  4. 循环:从权值最小的边开始遍历每条边 直至图Graph中所有的节点都在同一个连通分量中

             if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中添加这条边到图Graphnew中
    

克鲁斯卡尔解决公交站问题

题目要求:

有n个地点,要求做出连通n个地点的最小公交线路。

代码实现:

public class KruskalCase {private int EdgeNum;private char[] vertxs;private int[][] matrix;private static  final int INF = Integer.MAX_VALUE;private int [] ends;public static void main(String[] args) {char [] vertexs = {'A','B','C','D','E','F','G'};int matrix[][] = {/*A*//*B*//*C*//*D*//*E*//*F*//*G*//*A*/ {   0,  12, INF, INF, INF,  16,  14},/*B*/ {  12,   0,  10, INF, INF,   7, INF},/*C*/ { INF,  10,   0,   3,   5,   6, INF},/*D*/ { INF, INF,   3,   0,   4, INF, INF},/*E*/ { INF, INF,   5,   4,   0,   2,   8},/*F*/ {  16,   7,   6, INF,   2,   0,   9},/*G*/ {  14, INF, INF, INF,   8,   9,   0}}; KruskalCase case1 = new KruskalCase(vertexs, matrix);case1.kuruskal();}//开始Kruskal算法public void kuruskal() {int h1,h2;                //h1,h2 分别是边的两个顶点int index = 0 ;                   //目前放入边的坐标EData rets[] = new EData[EdgeNum];//获得边集EData edges1[] = getEDatas(matrix);//排序边集sort(edges1);for(int i = 0;i < EdgeNum ;i++) {h1 = getPosition(edges1[i].start);h2 = getPosition(edges1[i].end);//获得字符对应的下标if(getEnd(ends, h1) != getEnd(ends, h2)) {     //每次连接点时判断是否相同ends[h1] = h2;                               //不同时将起始点的终点设置为终点的终点,完成结果集的更新rets[index++] = edges1[i];                   //同时加入结果集}}//<E,F> <C,D> <D,E> <B,F> <E,G> <A,B>。//统计并打印 "最小生成树", 输出  retsSystem.out.println("最小生成树为");for(int i = 0; i < index; i++) {System.out.println(rets[i]);}}/*** 在本方法中,边的终点集合实际上实在一种动态的构建过程。* 在构建树的过程中,在开始是每个点的终点都可以设置为自己或者null,在使用kruskal算法时。* 就可以在每次构建过程中更新终点集,实质上是一种类似动态规划的思想,此终点的位置在其子节点的终点位置。* 在连接的过程中慢慢形成一个完整的终点集,更新的就是联通的。未更新的就是终点是自身的。* @param ends:边的终点集合* @param v:要找的边* @return*/public int getEnd(int ends[],int v) {while(ends[v] != 0) {v = ends[v];}return v;}//对边集合进行排序public void  sort(EData[] edatas) {for(int i = 0;i < edatas.length;i++) {          //使用冒泡排序法for(int j = 0;j < edatas.length - i -1 ;j++) {if(edatas[j].weight > edatas[j+1].weight) {//交换EData tmp = edatas[j];edatas[j] = edatas[j+1];edatas[j+1] = tmp;}}}}//根据矩阵获取边集合public EData[] getEDatas(int [][] martrix){int inedx = 0;EData []Edatas = new EData[EdgeNum];for(int i = 0 ; i < vertxs.length; i ++) {for(int j = i+1;j < matrix[0].length;j++) {  //遍历矩阵的上三角获取边数,减少重复操作if(matrix[i][j] != INF) {EData edata = new EData(vertxs[i], vertxs[j], martrix[i][j]);Edatas[inedx++] = edata; }}}return Edatas;}//根据传入的数据获得数据下标public int getPosition(char vertex) {for(int i = 0; i < vertxs.length; i ++) {if(vertxs[i] == vertex) {return i;}}return -1;}//构造器public KruskalCase(char[] vertxs, int[][] matrix) {int vlen = vertxs.length;                     //这里用的初始化方式是复制拷贝,不会改变传入的数组数据this.vertxs = new char[vlen];this.matrix = new int[vlen][vlen];ends = new int [vlen];for (int i = 0; i < vlen; i++) {this.vertxs[i] = vertxs[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(matrix[i][j] < INF) {EdgeNum++;}}}}//打印邻接矩阵public void print() {System.out.println("邻接矩阵为: \n");for(int i = 0; i < vertxs.length; i++) {for(int j=0; j < vertxs.length; j++) {System.out.printf("%12d", matrix[i][j]);}System.out.println();//换行}}// 边类:用于储存一条边的数据,可以更有效实现边的排序等操作class EData {char start;char end;int weight;public EData(char start, char end, int weight) {super();this.start = start;this.end = end;this.weight = weight;}@Overridepublic String toString() {// TODO Auto-generated method stubreturn "EData [< "+start +", " + end + ">=" + weight+ "]";}}
}

迪杰斯特拉算法

概述:

迪杰斯特拉算法定义:

求解单元点的最短路径问题,给定带权有向图G和源点v,求v到G中其他顶点的最短路径。

迪杰斯特拉最最朴素的思想就是按长度递增的次序产生最短路径。即每次对所有可见点的路径长度进行排序后,选择一条最短的路径,这条路径就是对应顶点到源点的最短路径。

思想是广度遍历图,用动态规划和贪心算法的思想,每次将规划完的结果保存,在遍历时将每次的最优解保存,最终形成一个对所有点的最路径解。

迪杰斯特拉算法解决邮差路径问题:

题目要求:

求出在几个顶点中邮差送信的最短路径。

代码实现:


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 graph = new Graph(vertex, matrix);//测试迪杰斯特拉算法graph.dsj(6);//Cgraph.showDijkstra();}
}//图的核心类
class Graph {char[] vertexs;int[][] matrix;public static final int IN = Integer.MAX_VALUE;VisitedVertex vv;// 已经访问过的数组public Graph(char[] vertexs, int[][] matrix) {super();this.vertexs = vertexs;this.matrix = matrix;}//显示结果public void showDijkstra() {vv.show();}//开始迪杰斯特啦算法//思想是广度遍历数组,用动态规划和贪心算法的思想,每次将规划完的结果保存,在遍历时将每次的最优解保存public void dsj(int index) {vv = new VisitedVertex(vertexs.length, index);//传入后先更新目标update(index);for (int i = 1; i < vertexs.length; i++) {             //再循环更新所有index = vv.updateArr();update(index);}}//核心方法,根据传入的节点来更新数组数据public void update(int index) {int len = 0;for (int i = 0; i < matrix[index].length; i++) {//将最短节点(当前节点)遍历所有长度值,并且将长度值设置为顶点到该节点再到i节点的长度len = vv.getDis(index) + matrix[index][i];//若通过此路径到该节点的长度小于原先的最小长度,则更新最小长度以及前驱,将前驱设置为当前节点(动态规划的思想)if(vv.getDis(i) > len && !vv.in(i)) {vv.updateDis(i, len);vv.updatePre(i, index);}}}public void show() {for(int link[] : matrix) {System.out.println(Arrays.toString(link));}}}//开始写访问过的类,此处是关键,写了index到各个点的距离数组。各个点的前驱节点,点index到其他节点的最短距离(结果集合)
class VisitedVertex{public static final int IN = Integer.MAX_VALUE;// 记录各个顶点是否访问过 1表示访问过,0未访问,会动态更新public int[] already_arr;// 每个下标对应的值为前一个顶点下标, 会动态更新public int[] pre_visited;// 记录出发顶点到其他所有顶点的距离,比如G为出发顶点,就会记录G到其它顶点的距离,会动态更新,求的最短距离就会存放到dispublic int[] dis;//构造器:根据传入的节点数量和初始节点index来开始初始化各个数组public VisitedVertex(int length ,int index) {already_arr = new int [length];pre_visited = new int [length];dis = new int [length];Arrays.fill(dis, IN);dis[index] = 0;                       //将除了自身以外的节点全部设置为INalready_arr[index] = 1;             //将自身设置为已访问}//开始写各种方法//是否访问过public boolean in(int index) {return already_arr[index] == 1;}//更新距离数组public void updateDis(int index,int length) {dis[index] = length;}//更新前驱节点数组public void updatePre(int pre,int index) {pre_visited[pre] = index;}/*** 继续选择并返回新的访问顶点, 比如这里的G 完后,就是 A点作为新的访问顶点(注意不是出发顶点)* @return*/public int updateArr() {int min = Integer.MAX_VALUE;int 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 int getDis(int index) {return dis[index];}//显示最后的结果//即将三个数组的情况输出public void show() {System.out.println("==========================");//输出already_arrfor(int i : already_arr) {System.out.print(i + " ");}System.out.println();//输出pre_visitedfor(int i : pre_visited) {System.out.print(i + " ");}System.out.println();//输出disfor(int i : dis) {System.out.print(i + " ");}System.out.println();//为了好看最后的最短距离,我们处理char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };int count = 0;for (int i : dis) {if (i != 65535) {System.out.print(vertex[count] + "("+i+") ");} else {System.out.print("N ");}count++;}System.out.println();}}

弗洛伊德算法:

概述:

弗洛伊德算法跟克鲁斯卡尔算法一样,都是用来解决最短路径问题

而弗洛伊德算法可以求所有节点到所有节点的最短路径。当然需要更高的复杂度

迪杰斯特拉算法同通过选定的被访问节点,求出访问顶点到其他顶点的最短路径。

简单来说就是用一个中间顶点来尝试连接两个节点,比较两个节点直接连接与通过中间节点连接的路径长度,取比较小的那个值为两个点间的最短路径,中间节点可能性取值为所有除两个节点外所有节点。

弗洛伊德每一个顶点都是出发访问点,需要将每一个顶点都看作出发访问点,从而求出一个顶点到其他顶点的最短路径。

弗洛伊德算法的步骤:

设置顶点vi到vk的最短路径已知为Lik,Vk到Vj得最短路径为Lkj,定点Vi到Vj的已知路径为Lij,则Vi到Vj的最短路径就是:min(Lik+Lkj,Lij),Vk为所有顶点可能性,最终遍历完就可以获得Lij的最短路径。

至于其他的Vi到Vk等最短路径也是上面一样的步骤执行。

弗洛伊德算法解决邮差送报问题

题目描述:

求出在几个顶点中邮差送信的最短路径。
附带找出最小路径要走的路径,用一个动态规划解决数组路径。

基本思路:

通过设置其前驱节点完成。
前驱节点数组在最短路径算法设置中很重要。
前驱节点数组就是用来寻找最短路径的具体路径。

代码实现:


public class FloydAlgorithm {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,vertex,matrix);graph.floyd();graph.show();graph.dsl(2, 5);}
}class Graph {private char[] vertexs;// 保存各个顶点的数据private int[][] dis; // 保存,从各个顶点出发到其它顶点的距离,最后的结果,也是保留在该数组private int[][] pre;// 保存到达目标顶点的前驱顶点public Graph(int length, char[] vertexs, int[][] matrix) {super();this.vertexs = vertexs;this.dis = matrix;pre = new int[length][length];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();// 输出dis数组的一行数据for (int i = 0; i < dis.length; i++) {System.out.print("(" + vertex[k] + "到" + vertex[i] + "的最短路径是" + dis[k][i] + ") ");}System.out.println();System.out.println();}}//找最短路劲长度public void dsl(int i,int j) {if(pre[i][j] == i) {System.out.println(vertexs[i] + "->" + vertexs[j] + "权值:" + dis[i][j]);return;}dsl(i,pre[i][j]);dsl(pre[i][j],j);}/*** pre数组的作用就是可以在遍历的时候不断的找回其路劲,用递归的方法,当找的的pre[i][j]!=i的时候* 说明还有中间值,再度遍历中间值进行遍历。* 在创建这个表的时候,用的也是动态规划的思想,在更新的时候pre[k][j]的意思便是*/public void floyd() {int len = 0;                //len表示储存遍历时候的数据长度for(int k = 0;k < vertexs.length;k++) {                 //以k为顶点for(int i = 0;i < vertexs.length;i++) {                //以i为顶点for(int j = 0;j < vertexs.length;j++) {            //以j为顶点len = dis[i][k] + dis[k][j];               //len设置为以k为中点走这条路的长度,这里蕴含着如果两边长度不连通。则加任意一个值都会大于不连通的值,所以不会又遍历上的遗漏if(len < dis[i][j]) {                      //如果小于直接到达ij的长度的话进行操作,如果不连通也会进行操作dis[i][j] = len;                   //这里体现的是动态规划的思想,用之前遍历过程中已经找到最短值来代替,//其思想就是找从i->k的用某个中点做桥梁的时候找的最小值,同时在照这个值得过程中也是用到了找其最小值得思想。pre[i][j] = pre[k][i];                          //把这两个点得中间值设置为k,表示通过k来走这条路。}}}}}
}

马踏棋盘算法:

概述:

马随机放在国际象棋的8×8棋盘Board[0~7][0~7]的某个方格中,马按走棋规则进行移动。要求每个方格只进入一次,走遍棋盘上全部64个方格。

马踏棋盘算法是深度优先遍历的一种应用。

遍历所有深度遍历可能的条件,若发现不可行,则开始回溯回上一步骤开始下一个步骤可能性,直到完成遍历。

优化方案很简单,运用贪心算法的思想,每次遍历时走下一步可以走的步骤最小的,也就是最有可能先完成目标的方法,可以有效减少很多遍历步骤。

代码实现:


public class HorseChessboard {//先定义棋盘private static int X;          //棋盘的行数private static int Y;            //棋盘的列数private static boolean visited[];        //棋盘是否访问过的标记private static boolean finished;        //遍历是否完全完成的标记public static void main(String[] args) {System.out.println("骑士周游算法,开始运行~~");//测试骑士周游算法是否正确X = 8;Y = 8;int row = 1; //马儿初始位置的行,从1开始编号int column = 1; //马儿初始位置的列,从1开始编号//创建棋盘int[][] chessboard = new int[X][Y];visited = new boolean[X * Y];//初始值都是false//测试一下耗时long start = System.currentTimeMillis();traversalChessboard(chessboard, row - 1, column - 1, 1);long end = System.currentTimeMillis();System.out.println("共耗时: " + (end - start) + " 毫秒");}//开始算法public static void traversalChessboard(int[][] chessboard, int row, int column, int step) {chessboard[row][column] = step;//row = 4 X = 8 column = 4 = 4 * 8 + 4 = 36visited[row * X + column] = true; //标记该位置已经访问//获取当前位置可以走的下一个位置的集合 ArrayList<Point> ps = next(new Point(column, row));//对ps进行排序,排序的规则就是对ps的所有的Point对象的下一步的位置的数目,进行非递减排序sort(ps);//遍历 pswhile(!ps.isEmpty()) {Point p = ps.remove(0);//取出下一个可以走的位置//判断该点是否已经访问过if(!visited[p.y * X + p.x]) {//说明还没有访问过traversalChessboard(chessboard, p.y, p.x, step + 1);}}//判断马儿是否完成了任务,使用   step 和应该走的步数比较 , //如果没有达到数量,则表示没有完成任务,将整个棋盘置0//说明: step < X * Y  成立的情况有两种//1. 棋盘到目前位置,仍然没有走完//2. 棋盘处于一个回溯过程if(step < X * Y && !finished ) {chessboard[row][column] = 0;visited[row * X + column] = false;} else {finished = true;}}//跟据传入的点的位置把所有可以访问的点返回一个集合,用内置点类来方便使用public static ArrayList<Point> next(Point point){//创建一个ListArrayList<Point> ps = new ArrayList<Point>();//用一个point来方便初始化调用Point p1 = new Point();//若往某个日字点走不越界则可以放到集合中if((p1.x = point.x-2) > 0 && (p1.y = point.y-1) > 0) {ps.add(new Point(p1));} if((p1.x = point.x - 1) >=0 && (p1.y=point.y-2)>=0) {ps.add(new Point(p1));}if ((p1.x = point.x + 1) < X && (p1.y = point.y - 2) >= 0) {ps.add(new Point(p1));}if ((p1.x = point.x + 2) < X && (p1.y = point.y - 1) >= 0) {ps.add(new Point(p1));}if ((p1.x = point.x + 2) < X && (p1.y = point.y + 1) < Y) {ps.add(new Point(p1));}if ((p1.x = point.x + 1) < X && (p1.y = point.y + 2) < Y) {ps.add(new Point(p1));}if ((p1.x = point.x - 1) >= 0 && (p1.y = point.y + 2) < Y) {ps.add(new Point(p1));}if ((p1.x = point.x - 2) >= 0 && (p1.y = point.y + 1) < Y) {ps.add(new Point(p1));}return ps;}//开始优化,优化方案,采用贪心算法,每次选取点可以走的集合中最小的那次进行,既找到最有可能先完成棋盘遍历的点//先根据传进来的集合进行排序public static void sort(ArrayList<Point> ps) {ps.sort(new Comparator<Point>() {@Overridepublic int compare(Point o1, Point o2) {// TODO Auto-generated method stub//获取到o1的下一步的所有位置个数int count1 = next(o1).size();//获取到o2的下一步的所有位置个数int count2 = next(o2).size();if(count1 < count2) {          //若小于将count放到后面,实现递增排序return -1;} else if (count1 == count2) {return 0;} else {return 1;}}});}
}

十大算法简单介绍以及例题相关推荐

  1. 数据挖掘十大算法之分类算法(分类介绍及评价指标)

    文章目录 1. 分类相关知识 1.1 分类的概念 1.2 分类的流程 1.3 分类模型评价标准 2. 二分类分类案例 参考文章: 接上篇文章,接下来学习挖掘算法中的分类算法: 首先我们应该知道数据挖掘 ...

  2. 人工智能领域的十大算法

    事实上,人工智能已经存在于我们生活中很久了.但对很多人来讲,人工智能还是一个较为"高深"的技术,然而再高深的技术,也是从基础原理开始的.人工智能领域中就流传着10大算法,它们的原理 ...

  3. [算法] 当今世界最为经典的十大算法--投票进行时

    当今世界最为经典的十大算法--投票进行时 ---------------------------------------- 第一部分.来自圣经的十大算法 第十名:Huffman coding(霍夫曼编 ...

  4. 常用十大算法 非递归二分查找、分治法、动态规划、贪心算法、回溯算法(骑士周游为例)、KMP、最小生成树算法:Prim、Kruskal、最短路径算法:Dijkstra、Floyd。

    十大算法 学完数据结构该学什么?当然是来巩固算法,下面介绍了十中比较常用的算法,希望能帮到大家. 包括:非递归二分查找.分治法.动态规划.贪心算法.回溯算法(骑士周游为例).KMP.最小生成树算法:P ...

  5. 【数据结构与算法】 常用的十大算法

    常用的十大算法: 文章目录 常用的十大算法: 1.二分查找算法(非递归): 2.分治算法 2.1分治算法介绍 2.2 分治算法的基本步骤 2.3 分治算法最佳实践-汉诺塔 2.4 动态规划算法 2.4 ...

  6. 神经网络十大算法有哪些,神经网络十大算法排名

    数学建模的十大算法 . 1.蒙特卡罗算法(该算法又称随机性模拟算法,是通过计算机仿真来解决问题的算法,同时可以通过模拟可以来检验自己模型的正确性,是比赛时必用的方法)2.数据拟合.参数估计.插值等数据 ...

  7. 数据挖掘与机器学习的十大算法

    机器学习与数据挖掘中的十大经典算法 背景: top10算法的前期背景是吴教授在香港做了一个关于数据挖掘top10挑战的一个报告,会后有一名内地的教授提出了一个类似的想法.吴教授觉得非常好,开始着手解决 ...

  8. 十大算法,描述+代码+演示+分析+改进(赶紧收藏!)

    十大算法 1.冒泡排序 ​ (1)算法描述 ​ 冒泡排序是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来.走访数列的工作是重复地进行直到没有再需要 ...

  9. java培训:Java的十大算法

    想要学好java语言,就要打好基础,java要学习的东西有很多,今天小编就来和大家说下java的十大算法. 算法一:快速排序算法 快速排序是由东尼·霍尔所发展的一种排序算法.在平均状况下,排序 n 个 ...

  10. 新手入门机器学习十大算法

    新手入门机器学习十大算法 2018年9月17日 磐石 TensorFlowNews, 机器学习 0 在机器学习的世界中,有一种被称为"无免费午餐"的定理. 它意在说明没有哪种算法能 ...

最新文章

  1. 分布式技术一周技术动态 2016-11-27
  2. 萌新的九宫棋(C语言)
  3. linux中probe函数传递参数的寻找(下)
  4. mysql两种引擎的适用场景_MySQL两种引擎的区别和应用场景
  5. imx6ull EMMC和NABD 的移植注意事项,差别
  6. 二叉树的镜像(C++)
  7. 使用Istio分布式跟踪应用程序
  8. 20160801java学习重点:函数
  9. python的基础网络编程是下列_python基础 - 网络编程
  10. mysql面试通关宝典,你看你知道多少
  11. RS232和RS485标准在DB9公/母头上的接线定义
  12. SpringBoot项目多环境配置(亲测有效)
  13. SteamVR简介(Yanlz+Steam+VR+Unity+AR+MR+XR+=)
  14. Java使用DFA算法处理敏感词汇
  15. 最简单的Tomcat9下载安装教程
  16. Excel中COUNTIFS函数统计词频个数出现次数
  17. TC限速原理和TC限速的实现
  18. quartus ii 增量编译
  19. 【输入法】不显示中文
  20. jquery获取checkbox选中的值

热门文章

  1. TCP-面向连接的、可靠的、基于字节流的 传输层通信协议
  2. 联想小新Air15-2021安装elementaryOS5.1(基于ubuntu18.04)
  3. catia三边倒角_CATIA倒圆角为什么圆角会被吃掉?
  4. [UVA 202]Repeating Decimals
  5. #CSDN软件工程师能力认证学习精选# NoSql是什么?
  6. 国密Fabric-ca集群负载均衡
  7. Word2010无法输入中文
  8. Java常用命令:jps、jstack、jmap、jstat(带有实例教程)
  9. 英语语法基础02(句子成分)
  10. 北京最新城市总体规划草案