常用算法
二分查找非递归算法
public class BinarySearchNoRecur {
public static void main(String[] args) {
//测试
int[] arr = {1, 3, 8, 10, 11, 67, 100};
int index = binarySearch(arr, 8);
System.out.println(((index != -1) ? "index=" + index : "未找到..."));
}
/**
* 二分查找的非递归实现
*
* @param arr 要查找的数组
* @param target 需要查找的数
* @return 返回对应下标,-1表示没有找到
*/
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
}
分治算法
Divide-and-Conquer
汉诺塔
public class HanoiTower {
public static void main(String[] args) {
hanoiTower(3, '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
hanoiTower(num - 1, a, c, b);
//2. 把最下边的盘A->C
System.out.println("第" + num + "个盘从 " + a + "->" + c);
//3. 把B塔的所有盘 从B->C
hanoiTower(num - 1, b, a, c);
}
}
}
动态规划算法
0-1背包问题
public class KnapsackSelf03 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入物品数量: ");
int num = scanner.nextInt();
int[] weight = new int[num + 1];
int[] value = new int[num + 1];
System.out.print("请输入背包容量: ");
int capacity = scanner.nextInt();
int[][] dp = new int[num + 1][capacity + 1];
weight[0] = 0;
for (int i = 1; i < num + 1; i++) {
System.out.print("请输入第" + i + "件物品重量: ");
weight[i] = scanner.nextInt();
}
value[0] = 0;
for (int i = 1; i < num + 1; i++) {
System.out.print("请输入第" + i + "件物品价值: ");
value[i] = scanner.nextInt();
}
for (int i = 1; i < dp.length; i++) { //先遍历物品
for (int j = 1; j < dp[i].length; j++) { //再遍历背包
if (weight[i] > j) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
}
for (int i = 0; i < num + 1; i++) {
for (int j = 0; j < capacity + 1; j++) {
System.out.print(dp[i][j] + "\t\t");
}
System.out.println();
}
System.out.println("背包内最大的物品价值总和为:" + dp[num][capacity]);// 有num个物品可选,且背包的容量为capacity的情况下,能装入背包的最大价值
int[] item = new int[num + 1];
int j = capacity;
int i = num;
while (i != 0) {
if (dp[i][j] != dp[i-1][j]) {
item[i] = i;
j -= weight[i];
i--;
} else {
i--;
}
}
for (int w = item.length - 1; w >= 0; w--) {
if (item[w] != 0) {
System.out.print( "放入物品编号:" + item[w] + "\t");
}
}
}
}
滚动数组
完全背包问题
public class CompleteKnapsack {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
int[] weight = new int[num + 1];
int[] value = new int[num + 1];
weight[0] = 0;
for (int i = 1; i < num + 1; i++) {
weight[i] = sc.nextInt();
}
value[0] = 0;
for (int i = 1; i < num + 1; i++) {
value[i] = sc.nextInt();
}
int capacity = sc.nextInt();//背包容量
//动态规划表
int[][] dp = new int[num + 1][capacity + 1];
int[]temp = new int[num + 1];
Arrays.fill(temp, 0);
//动态规划表
for (int i = 0; i < num + 1; i++) {
for (int j = 0; j < capacity + 1; j++) {
if (i == 0) {
dp[i][j] = 0;
} else if (j == 0) {
dp[i][j] = 0;
} else {
if (weight[i] > j) {
dp[i][j] = dp[i - 1][j];
} else {
for (int k = 0; k * weight[i] <= j; k++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - k * weight[i]] + k * value[i]);
}
}
}
System.out.println("最大价值=" + dp[i][j]);
}
System.out.println();
}
for (int i = 0; i < num + 1; i++) {
for (int j = 0; j < capacity + 1; j++) {
System.out.print(dp[i][j] + "\t");
}
System.out.println();
}
}
}
暴力匹配算法
public class ViolenceMatch {
public static void main(String[] args) {
//测试暴力匹配算法
String str1 = "詹姆 詹姆詹姆 勒布朗詹姆姆斯詹姆斯";
String str2 = "詹姆斯";
int index = violenceMatch(str1, str2);
if (index != -1) {
System.out.println("index=" + index);
} else {
System.out.println("匹配失败...");
}
}
//暴力匹配算法
public static int violenceMatch(String str1, String str2) {
char[] s1 = str1.toCharArray();
char[] s2 = str2.toCharArray();
int s1_len = s1.length;
int s2_len = s2.length;
int i = 0; //i指针指向s1
int j = 0; //j指针指向s2
while (i < s1_len && j < s2_len) { //保证匹配时,不越界
if (s1[i] == s2[j]) {
i++;
j++;
} else { //匹配失败
i = i - j + 1; //回溯到上一次起始位置的下一个位置
j = 0;
}
}
//判断是否匹配成功
if (j == s2_len) {
return i - j;
} else {
return -1;
}
}
}
KMP算法
正月点灯笼
public class KmpAlgorithm02 {
public static void main(String[] args) {
String text = "ABABABCABAABABABAB";
String find = "ABABCABAA";
kmp_search(text, find);
}
public static int[] KmpNext(String find) {
char[] chars = find.toCharArray();
int[] next_table = new int[chars.length];
next_table[0] = 0;
int len = 0;
int i = 1;
while (i < chars.length) {
if (chars[i] == chars[len]) {
len++;
next_table[i] = len;
i++;
} else {
if (len > 0) {
len = next_table[len - 1];
} else {
next_table[i] = 0;
i++;
}
}
}
return next_table;
}
//例如ababc假设失配位置是c,c左边的最大公共匹配长度为2,移动后利于kmpSearch方法的查找
public static void move_next_table(int[] next_table) {
for (int i = next_table.length - 1; i > 0; i--) {
next_table[i] = next_table[i - 1];
}
next_table[0] = -1;
}
public static void kmp_search(String textStr, String findStr) {
char[] text = textStr.toCharArray();
char[] find = findStr.toCharArray();
int[] next_table = KmpNext(findStr);
move_next_table(next_table);
int i = 0; //text的指针
int j = 0; //find的指针
while (i < text.length) {
if (j == find.length - 1 && text[i] == find[j]) {
System.out.printf("Found index at %d", i - j);
j = next_table[j];
}
if (text[i] == find[j]) {
i++;
j++;
} else {
j = next_table[j];
if (j == -1) {
i++;
j++;
}
}
}
}
}
解释为什么后移
例如ababc假设失配位置是c,c左边的最大公共匹配长度为2,移动后利于kmpSearch方法的查找
贪心算法
题目:假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少的广播台,让所有的地区都可以接收到信号
广播台 | 覆盖地区 |
---|---|
K1 | “杭州”, “北京”, “上海”, “天津” |
K2 | “广州”, “北京”, “上海”, “天津” |
K3 | “成都”, “武汉”, “杭州” |
K4 | “上海”, “天津” |
K5 | “广州”, “杭州”, “大连” |
**注意:**因为tempSet存储的是当前广播所覆盖的地区和所有未覆盖地区的交集,而broadcasts存储的是某个广播对应的所有地区,试想一下,如果首先取得的一个广播所对应的与未覆盖地区的交集最小,并且该广播所覆盖的地区是最大的,那么后面的广播即使出现于所有未覆盖地区的交集比maxKey所覆盖的地区和所有未覆盖地区的交集大,由于maxKey广播所覆盖的地区是最大的,那么它不会满足上面的的贪心策略, 继而不会更新maxKey。
所以贪心策略核心如下:
//tempMaxKeySet是指一个maxKey临时的集合,在遍历过程中,maxKey覆盖的地区和当前还没有覆盖的地区的交集
//while循环里的比较时会用到
tempSet.size() > tempMaxKeySet.size()
源代码如下
import java.util.*;
public class GreedyAlgorithm {
public static void main(String[] args) {
//创建广播电台,放入到Map
//利用hashmap键值对的特性,key表示广播站,value表示覆盖地区
//hashset用于存储对象,不允许有重复的值
HashMap<String, HashSet<String>> broadcasts = new HashMap<>();
//将各个电台放入到broadcasts
HashSet<String> hashSet1 = new HashSet<>();
hashSet1.add("北京");
hashSet1.add("上海");
hashSet1.add("天津");
HashSet<String> hashSet2 = new HashSet<>();
hashSet2.add("广州");
hashSet2.add("北京");
hashSet2.add("深圳");
HashSet<String> hashSet3 = new HashSet<>();
hashSet3.add("成都");
hashSet3.add("上海");
hashSet3.add("深圳");
HashSet<String> hashSet4 = new HashSet<>();
hashSet4.add("上海");
hashSet4.add("天津");
HashSet<String> hashSet5 = new HashSet<>();
hashSet5.add("杭州");
hashSet5.add("大连");
//加入到map
broadcasts.put("k1", hashSet1);
broadcasts.put("k2", hashSet2);
broadcasts.put("k3", hashSet3);
broadcasts.put("k4", hashSet4);
broadcasts.put("k5", hashSet5);
//测试贪心算法
List<String> selects = greedy(broadcasts);
System.out.println(selects);
}
//存放所有地区
public static List<String> greedy(HashMap<String, HashSet<String>> broadcasts) {
//allAreas存放所有地区
HashSet<String> allAreas = new HashSet<>();
for (Map.Entry<String, HashSet<String>> entry : broadcasts.entrySet()) {
HashSet<String> area = entry.getValue();
Iterator<String> iterator = area.iterator();
while (iterator.hasNext()) { //hasNext判断集合是否还有下个元素,如果是最后一个元素则返回false,不移动指针
allAreas.add(iterator.next()); //把迭代器的指向移到下一个位置,同时返回下一个元素的引用
}
}
//创建ArrayList集合,存放选择的电台集合
ArrayList<String> selects = new ArrayList<>();
//定义一个临时的集合,在遍历过程中,存放遍历过程中的电台覆盖的地区和当前还没有覆盖的地区的交集
HashSet<String> tempSet = new HashSet<>();
/*
定义一个maxKey,保存在一次遍历过程中,能够覆盖最大未覆盖的地区对应的电台的key
如果maxKey不为null,则加入到selects中
*/
String maxKey;
//定义一个maxKey临时的集合,在遍历过程中,maxKey覆盖的地区和当前还没有覆盖的地区的交集
HashSet<String> tempMaxKeySet = new HashSet<>();
while (!allAreas.isEmpty()) { //如果allAreas不为空,表示还没有覆盖到所有地区
//每次while循环都需要制空maxKey
maxKey = null;
//遍历broadcasts,取出对应的key
for (String key : broadcasts.keySet()) {
//每进行一次for
tempSet.clear();
//当前key所能覆盖的地区
HashSet<String> area = broadcasts.get(key);
tempSet.addAll(area);
//求出tempSet 和 allAreas 集合的交集,交集会赋给tempSet
tempSet.retainAll(allAreas);
//tempSet.size() > maxKeyArea.size() 体现出贪心算法的特点,每次选择最优的
if (tempSet.size() > 0 &&
(maxKey == null || tempSet.size() > tempMaxKeySet.size())) {
maxKey = key;
tempMaxKeySet = broadcasts.get(maxKey);
tempMaxKeySet.retainAll(allAreas);
}
}
//maxKey != null, 就应该将maxKey 加入 selects
if (maxKey != null) {
selects.add(maxKey);
//将maxKey指向的广播电台覆盖的地区从allAreas去除
allAreas.removeAll(broadcasts.get(maxKey));
}
}
return selects;
}
}
普利姆算法
求最小生成树
- (无向图)任意两个顶点都有路径相通 ——> 连通图
- (有向图)任意两个顶点都相互连接 ——> 强连通图
- 连通图中,每个边都有权值 ——> 连通网
- 连通图的连通子图,包含图中所有的顶点,但只有构成一棵树的 n - 1 条边 ——> 生成树
(生成树如果再添加任意一条边一定会生成环) - 在连通图中,所有边的代价最小的那棵树 ——> 最小生成树
import java.util.ArrayList;
import java.util.Arrays;
public class PrimAlgorithm {
public static void main(String[] args) {
//测试图是否创建成功
char[] data = new char[] {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int verxs = data.length;
//邻接矩阵的关系,使用二维数组描述
int[][] weight ={
{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 mGraph = new MGraph(verxs);
//创建一个MinTree对象
MinTree minTree = new MinTree();
minTree.createGraph(mGraph, verxs, data, weight);
//打印该图
minTree.showGraph(mGraph);
//测试普利姆算法
minTree.prim(mGraph, 1);
}
}
//创建最小生成树
class MinTree {
//创建邻接矩阵
/**
* @param graph 图对象
* @param vertexs 图对应的顶点个数
* @param data 图的各个顶点的值
* @param weight 图的邻接矩阵
*/
public void createGraph(MGraph graph, int vertexs, char[] data, int[][] weight) {
int i, j;
for (i = 0; i < vertexs; i++) {
graph.data[i] = data[i];
for (j = 0; j < vertexs; 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 mGraph 图
* @param v 表示图的第几个顶点开始生成
*/
public void prim(MGraph mGraph, int v) {
//集合存储已访问过的点
ArrayList<Integer> visited = new ArrayList<>();
//把当前顶点加入集合
visited.add(v);
//h1和h2记录两个顶点的下标
int h1 = -1;
int h2 = -1;
int minWeight = 10000;//将minWeight初始成一个大数,后面在遍历过程中,会被替换
//因为graph.vertexs顶点,普利姆算法结束后,会有graph.vertexs-1条边,因此从1开始遍历
for (int k = 1; k < mGraph.verxs; k++) { //直到连同所有顶点为止
//这是确定每一次生成的子图,哪个顶点和这次遍历的顶点的距离最近
for (int i = 0; i < visited.size(); i++) { //i表示已被访问过的顶点
for (int j = 0; j < mGraph.verxs; j++) { //j表示还没有访问过的顶点
if (! visited.contains(j)
&& mGraph.weight[visited.get(i)][j] < minWeight) {
//替换minWeight = graph.weight[visited.get(i)][j];
minWeight = mGraph.weight[visited.get(i)][j];
h1 = visited.get(i);
h2 = j;
}
}
}
//找到一条边最小
System.out.println("边<" + mGraph.data[h1] + "," + mGraph.data[h2] + "> 权值:" + minWeight);
//将当前这个顶点标记为已经访问
visited.add(h2);
//minWeight 重新设置为最大值10000
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];
}
}
克鲁斯卡尔算法
- 某城市新增 7 个站点(A, B, C, D, E, F, G) ,现在需要修路把 7 个站点连通
- 各个站点的距离用边线表示(权) ,比如 A – B 距离 12 公里
- 问:如何修路保证各个站点都能连通,并且总的修建公路总里程最短?
克鲁斯卡尔算法图解说明
第1步:将边<E,F>加入 R 中。
边<E,F>的权值最小,因此将它加入到最小生成树结果 R 中。
第2步:将边<C,D>加入 R 中。
上一步操作之后,边<C,D>的权值最小,因此将它加入到最小生成树结果 R 中。
第3步:将边<D,E>加入 R 中。
上一步操作之后,边<D,E>的权值最小,因此将它加入到最小生成树结果 R 中。
第4步:将边<B,F>加入 R 中。
上一步操作之后,边<C,E>的权值最小,但<C,E>会和已有的边构成回路;因此,跳过边<C,E>。同理,跳过边<C,F>。将边<B,F>加入到最小生成树结果 R 中。
第5步:将边<E,G>加入 R 中。
上一步操作之后,边<E,G>的权值最小,因此将它加入到最小生成树结果 R 中。
第6步:将边<A,B>加入 R 中。
上一步操作之后,边<F,G>的权值最小,但<F,G>会和已有的边构成回路;因此,跳过边<F,G>。同理,跳过边<B,C>。将边<A,B>加入到最小生成树结果 R 中。
此时,最小生成树构造完成!它包括的边依次是:<E,F> <C,D> <D,E> <B,F> <E,G> <A,B>。
克鲁斯卡尔算法分析
根据前面介绍的克鲁斯卡尔算法的基本思想和做法,我们能够了解到,克鲁斯卡尔算法重点需要解决的以下两个问题:
问题一: 对图的所有边按照权值大小进行排序。
问题二 :将边添加到最小生成树中时,怎么样判断是否形成了回路。
问题一很好解决,采用排序算法进行排序即可。
问题二,处理方式是:记录顶点在"最小生成树"中的终点,顶点的终点是"在最小生成树中与它连通的最大顶点"。然后每次需要将一条边添加到最小生存树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路。
并查集
public class DisJoint_Set {
public static void main(String[] args) {
int vertices = 6;
int[] parent = new int[vertices];
int[] rank = new int[vertices];
int[][] edges = new int[][]{
{0, 1}, {1, 2}, {1, 3},
{3, 4}, {2, 5}, {2, 4}
};
//初始化parent[],如果parent[i] = i说明该节点的父节点是本身
initialise(parent, vertices, rank);
for (int[] edge : edges) {
int x = edge[0];
int y = edge[1];
if (!union_vertices(x, y, parent, rank)) { //若没有合并成功说明有环
System.out.println("Cycle detected!");
break;
}
System.out.println("No found cycle");
}
}
public static void initialise(int[] parent, int vertices, int[] rank) {
for (int i = 0; i < vertices; i++) {
parent[i] = i;
rank[i] = 0;
}
}
//寻找该节点的根节点
//v 表示该节点的索引
public static int find_root(int v, int[] parent) {
//假设v就是父节点
if (parent[v] == v) {
return v;
} else {
parent[v] = find_root(parent[v], parent); //把每个双亲节点设置为根节点,路径压缩
return parent[v];
}
}
//true 成功合并
//false 合并失败
public static boolean union_vertices(int x, int y, int[] parent, int[] rank) {
int x_root = find_root(x, parent);
int y_root = find_root(y, parent);
if (x_root == y_root) {
return false;
} else {
if (rank[x_root] > rank[y_root]) {
parent[y_root] = x;
} else if (rank[x_root] < rank[y_root]) {
parent[x_root] = y;
} else {
parent[y_root] = x;
rank[x_root]++;
}
return true;
}
}
}
代码实现(Disjoint Set)
public class KruskalSelf02 {
private int edgeNum;
private char[] vertex;
private int[][] matrix;
private static final int INF = Integer.MAX_VALUE;
private int[] parent;
private int[] rank;
public static void main(String[] args) {
char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int[] parent = new int[vertex.length];
int[] rank = new int[vertex.length];
int[][] matrix = new int[][]{
/*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}
};
KruskalSelf02 kruskalSelf02 = new KruskalSelf02(vertex, matrix, parent, rank);
int index = 0;
int edgeNum = kruskalSelf02.getEdgeNum();
Edge_Data[] result = new Edge_Data[edgeNum];
Edge_Data[] edges = kruskalSelf02.getEdges();
kruskalSelf02.sortEdge(edges);
for (int i = 0; i < edgeNum; i++) {
int v1 = kruskalSelf02.getPosition(edges[i].start);
int v2 = kruskalSelf02.getPosition(edges[i].end);
if (union_vertices(v1, v2, parent, rank)) {
result[index++] = edges[i];
}
}
System.out.println("最小生成树");
for (int i = 0; i < index; i++) {
System.out.println(result[i]);
}
}
public KruskalSelf02(char[] vertex, int[][] matrix, int[] parent, int[] rank) {
this.vertex = new char[vertex.length];
this.vertex = vertex.clone();
this.matrix = new int[vertex.length][vertex.length];
for (int i = 0; i < vertex.length; i++) {
this.matrix[i] = matrix[i].clone();
}
this.parent = new int[vertex.length];
this.rank = new int[vertex.length];
for (int i = 0; i < vertex.length; i++) {
parent[i] = i;
rank[i] = 0;
}
}
public int getEdgeNum() {
for (int i = 0; i < vertex.length; i++) {
for (int j = i + 1; j < vertex.length; j++) {
if (matrix[i][j] != INF) {
edgeNum++;
}
}
}
return edgeNum;
}
public void print() {
System.out.println("邻接矩阵为 \n");
for (int i = 0; i < vertex.length; i++) {
for (int j = 0; j < vertex.length; j++) {
System.out.printf("%12d", matrix[i][j]);
}
System.out.println();
}
}
public void sortEdge(Edge_Data[] 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) {
Edge_Data temp = edges[j];
edges[j] = edges[j + 1];
edges[j + 1] = temp;
}
}
}
}
public int getPosition(char ch) {
for (int i = 0; i < vertex.length; i++) {
if (vertex[i] == ch) {
return i;
}
}
return -1;
}
public Edge_Data[] getEdges() {
int index = 0;
Edge_Data[] edges = new Edge_Data[edgeNum];
for (int i = 0; i < vertex.length; i++) {
for (int j = i + 1; j < vertex.length; j++) {
if (matrix[i][j] != INF) {
edges[index++] = new Edge_Data(vertex[i], vertex[j], matrix[i][j]);
}
}
}
return edges;
}
public static int find_root(int v, int[] parent) {
if (parent[v] == v) {
return v;
} else {
parent[v] = find_root(parent[v], parent);
return parent[v];
}
}
public static boolean union_vertices(int x, int y, int[] parent, int[] rank) {
int x_root = find_root(x, parent);
int y_root = find_root(y, parent);
if (x_root == y_root) {
return false;
} else {
if (rank[x_root] > rank[y_root]) {
parent[y_root] = x_root;
} else if (rank[x_root] < rank[y_root]) {
parent[x_root] = y_root;
} else {
parent[y_root] = x_root;
rank[x_root]++;
}
return true;
}
}
}
class Edge_Data {
char start;
char end;
int weight;
public Edge_Data(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public String toString() {
return "Edge_Data{" +
"<" + start +
"," + end +
"> =" + weight +
'}';
}
}
迪杰斯特拉算法
迪杰斯特拉(Dijkstra)算法是典型****最短路径算法****,用于计算一个结点到其他结点的[最短路径。它的主要特点是以 起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。
import java.util.Arrays;
public class DijkstraDemo {
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;
/* A B C D E F G*/
matrix[0] = new int[]{N, 5, 7, N, N, N, 2};/*A*/
matrix[1] = new int[]{5, N, N, 9, N, N, 3};/*B*/
matrix[2] = new int[]{7, N, N, N, 8, N, N};/*C*/
matrix[3] = new int[]{N, 9, N, N, N, 4, N};/*D*/
matrix[4] = new int[]{N, N, 8, N, N, 5, 4};/*E*/
matrix[5] = new int[]{N, N, N, 4, 5, N, 6};/*F*/
matrix[6] = new int[]{2, 3, N, N, 4, 6, N};/*G*/
//创建Graph对象
Graph graph = new Graph(vertex, matrix);
//测试邻接矩阵是否ok
graph.showGraph();
//测试迪杰斯特拉算法
graph.dsj(6);
graph.showDijkstra();
}
}
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 showGraph() {
for (int[] link : matrix) {
System.out.println(Arrays.toString(link));
}
}
/**
* 迪杰斯特拉算法
*
* @param index 出发顶点对应的下标
*/
public void dsj(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顶点的前驱节点和出发点到index顶点周围顶点的距离
}
}
private void update(int index) {
vv.already_arr[index] = 1;
int len = 0;
//根据遍历我们的邻接矩阵的 matrix[index]行
for (int j = 0; j < matrix[index].length; j++) {
len = vv.getDis(index) + matrix[index][j];
// 如果j顶点没有被访问过,并且len小于出发顶点到j顶点的距离,就需要更新
if (!vv.isVisited(j) && len < vv.getDis(j)) {
vv.updatePre(j, index); //更新j顶点的前驱为index顶点
vv.updateDis(j, len); //更新出发顶点到j顶点的距离
}
}
}
//显示结果
public void showDijkstra() {
vv.show();
}
}
class VisitedVertex {
//记录各个顶点是否访问过 1表示访问过,0表示未访问,会动态更新
public int[] already_arr;
//每个下标对应的值为前一个顶点的下标,会动态更新
public int[] pre_visited;
//记录出发顶点到其他所有顶点的距离,比如G为出发顶点,就记录G到其他顶点的距离,会动态更新,求得最短距离放入dis
public int[] dis;
/**
* 构造器
*
* @param vNum 表示顶点的个数
* @param vIndex 出发顶点的下标,比如 G点,下标为 6
*/
public VisitedVertex(int vNum, int vIndex) {
this.already_arr = new int[vNum];
this.pre_visited = new int[vNum];
this.dis = new int[vNum];
//初始化dis数组
Arrays.fill(dis, 65535);
this.dis[vIndex] = 0;// 设置出发顶点的访问距离为0
}
/**
* 判断index下标对应的顶点是否访问过
*
* @param index
* @return 如果访问过,就返回 true,否则返回 false
*/
public boolean isVisited(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];
}
/**
* 继续选择并返回新的访问顶点, 比如这里G访问完后,广度搜索选择距离最短的后继顶点作为新的访问顶点
*
* @return
*/
public int updateArr() {
int min = 65535;
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;
}
}
//更新 index 顶点被访问过
already_arr[index] = 1;
return index;
}
/**
* 显示最后结果
*/
public void show() {
System.out.println("==================================");
//输出already_arr
for (int i : already_arr) {
System.out.print(i + "\t\t");
}
System.out.println();
//输出pre_visited
for (int i : pre_visited) {
System.out.print(i + "\t\t");
}
System.out.println();
//输出dis
for (int i : dis) {
System.out.print(i + "\t\t");
}
}
}
弗洛伊德算法
- 和Dijkstra算法一 样,弗洛伊德(Floyd)算法也是一种用于寻找给定的加权图中顶点间最短路径的算法。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特弗洛伊德命名
- 弗洛伊德算法(Floyd)计算图中各个顶点之间的最短路径
- 迪杰斯特拉算法用于计算图中某一一个顶点到其他项点的最短路径。
- 弗洛伊德算法VS迪杰斯特拉算法:迪杰斯特拉算法通过选定的被访问顶点,求出从出发访问顶点到其他顶点的最短路径;弗洛伊德算法中每-一个顶点都是出发访问点,所以需要将每一个顶点看做被访问顶点,求出从每一个顶点到其他项点的最短路径。
代码实现
public class FloydSelf01 {
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 = 100000;
/* A B C D E F G*/
matrix[0] = new int[]{N, 5, 7, N, N, N, 2};/*A*/
matrix[1] = new int[]{5, 0, N, 9, N, N, 3};/*B*/
matrix[2] = new int[]{7, N, 0, N, 8, N, N};/*C*/
matrix[3] = new int[]{N, 9, N, 0, N, 4, N};/*D*/
matrix[4] = new int[]{N, N, 8, N, 0, 5, 4};/*E*/
matrix[5] = new int[]{N, N, N, 4, 5, 0, 6};/*F*/
matrix[6] = new int[]{2, 3, N, N, 4, 6, 0};/*G*/
int[][] path = new int[vertex.length][vertex.length];
floyd(matrix, path, N);
}
public static void floyd(int[][] matrix, int[][] path, int N) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
path[i][j] = -1;
}
}
for (int m = 0; m < matrix.length; m++) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
if (matrix[i][m] + matrix[m][j] < matrix[i][j]) {
matrix[i][j] = matrix[i][m] + matrix[m][j];
path[i][j] = m; //记录经过哪个点到达
}
}
}
}
for (int i = 0; i < matrix.length; i++) {
for (int j = i + 1; j < matrix.length; j++) {
if (matrix[i][j] == N) {
System.out.println(i + "到" + j + "不可到达");
} else {
System.out.print(i + "到" + j + "的最短路径长度为:" + matrix[i][j]);
System.out.print(" 其最短路径:" + i + "->");
findPath(i, j, path);
System.out.println(j);
}
}
}
}
public static void findPath(int i, int j, int[][] path) {
int m = path[i][j];
if (m == -1) {
return;
}
findPath(i, m, path);
System.out.print(m + "->");
findPath(m, j, path);
}
}
骑士周游问题
import java.awt.*;
import java.util.ArrayList;
import java.util.Comparator;
public class HorseChessBoard {
private static int X; //棋盘行数
private static int Y; //棋盘列数
//创建一个数组,标记棋盘各个位置是否被访问过
private static boolean[] visited;
//使用一个属性,表示是否棋盘的所有位置都被访问过
private static boolean finished; //如果为true,表示成功
public static void main(String[] args) {
//测试骑士周游算法是否正确
X = 8;
Y = 8;
int row = 1; //初始位置的行
int column = 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) + "毫秒");
//输出棋盘的最后情况
for (int[] rows : chessboard) {
for (int step : rows) {
System.out.print(step + "\t");
}
System.out.println();
}
}
/**
* 完成骑士周游问题的算法
*
* @param chessboard 棋盘
* @param row 马当前的位置行(从0开始)
* @param column 马当前位置的列 (从0开始)
* @param step 是第几步,初始位置就是第1步
*/
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进行排序,排序规则就是对ps的所有Point对象的下一步的位置的数目,进行非递减排序
sort(ps);
//遍历ps
while (!ps.isEmpty()) {
Point p = ps.remove(0); //取出下一个可以走的位置
//判断该点是否已访问
if (!visited[p.x * X + p.y]) { //说明还没有访问过
traversalChessBoard(chessboard, p.x, p.y, 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;
}
}
//根据当前位置(Point对象)计算马还能走到哪些位置(Point),并放入到一个集合中ArrayList
//最多有8个位置
public static ArrayList<Point> next(Point curPoint) {
//创建ArrayList
ArrayList<Point> ps = new ArrayList<>();
//创建一个Point
Point p1 = new Point();
//判断马是否可以走5的位置
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0) {
ps.add(new Point(p1));
}
//判断马是否可以走6的位置
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
ps.add(new Point(p1));
}
//判断马是否可以走7的位置
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
ps.add(new Point(p1));
}
//判断马是否可以走0的位置
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y) {
ps.add(new Point(p1));
}
//判断马是否可以走1的位置
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
ps.add(new Point(p1));
}
//判断马是否可以走2的位置
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
ps.add(new Point(p1));
}
//判断马是否可以走3的位置
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
ps.add(new Point(p1));
}
//判断马是否可以走4的位置
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
ps.add(new Point(p1));
}
return ps;
}
//根据当前这一步的所有的下一步的选择的位置,进行非递减排序
public static void sort(ArrayList<Point> ps) {
ps.sort(new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
//得到o1的下一步的所有位置的个数
int count1 = next(o1).size();
//得到o2的下一步的所有位置的个数
int count2 = next(o2).size();
if (count1 < count2) {
return -1;
} else if (count1 == count2) {
return 0;
} else {
return 1;
}
}
});
}
}
out.print(i + “\t\t”);
}
}
}
## 弗洛伊德算法
1. 和Dijkstra算法一 样,弗洛伊德(Floyd)算法也是一种用于寻找给定的加权图中顶点间最短路径的算法。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特弗洛伊德命名
2. 弗洛伊德算法(Floyd)计算图中各个顶点之间的最短路径
3. 迪杰斯特拉算法用于计算图中某一一个顶点到其他项点的最短路径。
4. 弗洛伊德算法VS迪杰斯特拉算法:迪杰斯特拉算法通过选定的被访问顶点,求出从出发访问顶点到其他顶点的最短路径;弗洛伊德算法中每-一个顶点都是出发访问点,所以需要将每一个顶点看做被访问顶点,求出从每一个顶点到其他项点的最短路径。
**代码实现**
```java
public class FloydSelf01 {
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 = 100000;
/* A B C D E F G*/
matrix[0] = new int[]{N, 5, 7, N, N, N, 2};/*A*/
matrix[1] = new int[]{5, 0, N, 9, N, N, 3};/*B*/
matrix[2] = new int[]{7, N, 0, N, 8, N, N};/*C*/
matrix[3] = new int[]{N, 9, N, 0, N, 4, N};/*D*/
matrix[4] = new int[]{N, N, 8, N, 0, 5, 4};/*E*/
matrix[5] = new int[]{N, N, N, 4, 5, 0, 6};/*F*/
matrix[6] = new int[]{2, 3, N, N, 4, 6, 0};/*G*/
int[][] path = new int[vertex.length][vertex.length];
floyd(matrix, path, N);
}
public static void floyd(int[][] matrix, int[][] path, int N) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
path[i][j] = -1;
}
}
for (int m = 0; m < matrix.length; m++) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
if (matrix[i][m] + matrix[m][j] < matrix[i][j]) {
matrix[i][j] = matrix[i][m] + matrix[m][j];
path[i][j] = m; //记录经过哪个点到达
}
}
}
}
for (int i = 0; i < matrix.length; i++) {
for (int j = i + 1; j < matrix.length; j++) {
if (matrix[i][j] == N) {
System.out.println(i + "到" + j + "不可到达");
} else {
System.out.print(i + "到" + j + "的最短路径长度为:" + matrix[i][j]);
System.out.print(" 其最短路径:" + i + "->");
findPath(i, j, path);
System.out.println(j);
}
}
}
}
public static void findPath(int i, int j, int[][] path) {
int m = path[i][j];
if (m == -1) {
return;
}
findPath(i, m, path);
System.out.print(m + "->");
findPath(m, j, path);
}
}
骑士周游问题
[外链图片转存中…(img-2Pf0VsI0-1664357828390)]
import java.awt.*;
import java.util.ArrayList;
import java.util.Comparator;
public class HorseChessBoard {
private static int X; //棋盘行数
private static int Y; //棋盘列数
//创建一个数组,标记棋盘各个位置是否被访问过
private static boolean[] visited;
//使用一个属性,表示是否棋盘的所有位置都被访问过
private static boolean finished; //如果为true,表示成功
public static void main(String[] args) {
//测试骑士周游算法是否正确
X = 8;
Y = 8;
int row = 1; //初始位置的行
int column = 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) + "毫秒");
//输出棋盘的最后情况
for (int[] rows : chessboard) {
for (int step : rows) {
System.out.print(step + "\t");
}
System.out.println();
}
}
/**
* 完成骑士周游问题的算法
*
* @param chessboard 棋盘
* @param row 马当前的位置行(从0开始)
* @param column 马当前位置的列 (从0开始)
* @param step 是第几步,初始位置就是第1步
*/
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进行排序,排序规则就是对ps的所有Point对象的下一步的位置的数目,进行非递减排序
sort(ps);
//遍历ps
while (!ps.isEmpty()) {
Point p = ps.remove(0); //取出下一个可以走的位置
//判断该点是否已访问
if (!visited[p.x * X + p.y]) { //说明还没有访问过
traversalChessBoard(chessboard, p.x, p.y, 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;
}
}
//根据当前位置(Point对象)计算马还能走到哪些位置(Point),并放入到一个集合中ArrayList
//最多有8个位置
public static ArrayList<Point> next(Point curPoint) {
//创建ArrayList
ArrayList<Point> ps = new ArrayList<>();
//创建一个Point
Point p1 = new Point();
//判断马是否可以走5的位置
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0) {
ps.add(new Point(p1));
}
//判断马是否可以走6的位置
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
ps.add(new Point(p1));
}
//判断马是否可以走7的位置
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
ps.add(new Point(p1));
}
//判断马是否可以走0的位置
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y) {
ps.add(new Point(p1));
}
//判断马是否可以走1的位置
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
ps.add(new Point(p1));
}
//判断马是否可以走2的位置
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
ps.add(new Point(p1));
}
//判断马是否可以走3的位置
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
ps.add(new Point(p1));
}
//判断马是否可以走4的位置
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
ps.add(new Point(p1));
}
return ps;
}
//根据当前这一步的所有的下一步的选择的位置,进行非递减排序
public static void sort(ArrayList<Point> ps) {
ps.sort(new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
//得到o1的下一步的所有位置的个数
int count1 = next(o1).size();
//得到o2的下一步的所有位置的个数
int count2 = next(o2).size();
if (count1 < count2) {
return -1;
} else if (count1 == count2) {
return 0;
} else {
return 1;
}
}
});
}
}