基础算法
*以此谨记自己学习心得
今天是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点了,今天也是给自己一个满意的答卷了。