视频链接:https://www.bilibili.com/video/BV1HQ4y1d7th
视频范围P184 - P193
1.普里姆算法
7个村庄(A,B,C,D,E,F,G),现在需要修路把7个村庄连通,各个村庄的距离用边线表示(权),比如A-B距离5公里,问:如何修路保证各个村庄都能连通,并且总的修建公路总里程最短?
普利姆(Prim)算法求最小生成树,也就是在包含n 个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图
图类:
package kruskal.prim;
public class MGraph {
//顶点数量
int verxs;
//顶点
char[] data;
//每条边的权重
int[][] weight;
public MGraph(int verxs) {
this.verxs = verxs;
data = new char[verxs];
weight = new int[verxs][verxs];
}
}
最小树类:
package kruskal.prim;
import java.util.Arrays;
public class MinTree {
public void createGraph(MGraph graph,int verxs,char[] data,int[][] weight){
for (int i = 0; i < verxs; i++) {
graph.data[i] = data[i];
for (int j = 0; j < verxs; j++) {
graph.weight[i][j] = weight[i][j];
}
}
}
//查看邻接矩阵
public void showGraph(MGraph graph){
for (int[] data : graph.weight){
System.out.println(Arrays.toString(data));
}
}
//从v进去
public void prim(MGraph graph,int v){
//标记被访问过的,0表示没有访问过,1表示访问过
int[] visied = new int[graph.verxs];
visied[v] = 1;
int x = -1;
int y = -1;
int minWeight = 10000;
//最小生成树 边 = 顶点 - 1
for (int i = 1; i < graph.verxs; i++) {
//被访问过的
for (int j = 0; j < graph.verxs; j++) {
//没有被访问过的
for (int k = 0; k < graph.verxs; k++) {
if (visied[j] == 1 && visied[k] == 0 && graph.weight[j][k] < minWeight){
minWeight = graph.weight[j][k];
x = j;
y = k;
}
}
}
System.out.println(graph.data[x] + "--->" + graph.data[y] + " = " + minWeight);
visied[y] = 1;
minWeight = 10000;
}
}
}
测试类:
package kruskal.prim;
public class PrimAlgorithm {
public static void main(String[] args) {
char[] data = {'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 = new MGraph(verxs);
MinTree minTree = new MinTree();
minTree.createGraph(mGraph,verxs,data,weight);
minTree.showGraph(mGraph);
minTree.prim(mGraph,0);
}
}
运行结果:
2.克鲁斯卡尔算法[此代码有问题!]
克鲁斯卡尔算法的核心思想是:在带权连通图中,不断地在边集合中找到最小的边,如果该边满足得到最小生成树的条件,就将其构造,直到最后得到一颗最小生成树。
注意:当顶点数和边数不是特别大的时候可以用克鲁斯卡尔算法
使用克鲁斯卡尔算法生成最小生成树
边类:
package kruskal.krusk;
public class Edge implements Comparable<Edge>{
int begin;
int end;
int weight;
@Override
public int compareTo(Edge o) {
return this.weight - o.weight;
}
}
图类:
package kruskal.krusk;
public class Graph {
Edge[] edges;
int[][] array;
}
测试类:
package kruskal.krusk;
import java.util.Arrays;
public class KruskalAlg {
public static void main(String[] args) {
Graph graph = new Graph();
int[][] array = new int[7][7];
//手动初始化数组
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 7; j++) {
if (i == j){
array[i][j] = 0;
}else{
array[i][j] = Integer.MAX_VALUE;
}
}
}
array[0][1] = 7;
array[0][3] = 5;
array[1][2] = 8;
array[1][3] = 9;
array[2][4] = 5;
array[3][4] = 15;
array[4][5] = 8;
array[4][6] = 9;
array[5][6] = 11;
graph.array = array;
int k = 0;
//初始化边的数组
Edge[] edge = new Edge[9];
for (int i = 0; i < edge.length; i++) {
Edge edge1 = new Edge();
edge[i] = edge1;
}
//将边的数组传入begin end weight
for (int i = 0; i < 6; i++) {
for (int j = i + 1; j < 7; j++) {
if (array[i][j] < Integer.MAX_VALUE){
edge[k].begin = i;
edge[k].end = j;
edge[k].weight = array[i][j];
k++;
}
}
}
graph.edges = edge;
Arrays.sort(edge);
kruskal(graph);
}
//是否会构成回路
public static int findParentRoot(int target,int[] parent){
while (parent[target] > 0){
target = parent[target];
}
return target;
}
//kruskal算法
public static void kruskal(Graph graph){
Edge[] edges = graph.edges;
int[][] arr = graph.array;
int[] parent = new int[7];
for (int i = 0; i < edges.length; i++) {
Edge edge = edges[i];
int begin = findParentRoot(edge.begin, parent);
int end = findParentRoot(edge.end, parent);
if (begin != end){
System.out.println(String.format("(%d,%d)---->%d",begin,end, edge.weight));
parent[begin] = end;
}
}
}
}
运行结果:
3.迪杰斯特拉算法
迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个结点到其他结点的最短路径。它的主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
package kruskal;
public class DijstraAlgorithm {
public static int MaxValue = 100000;
public static void main(String[] args) {
//顶点数
int vertex = 7;
//边数
int edge = 10;
//权重
int[][] matrix = new int[vertex][vertex];
for (int i = 0; i < vertex; i++) {
for (int j = 0; j < vertex; j++) {
matrix[i][j] = MaxValue;
}
}
matrix[0][1] = 6;
matrix[1][2] = 5;
matrix[0][3] = 2;
matrix[3][1] = 7;
matrix[3][4] = 5;
matrix[1][5] = 3;
matrix[4][5] = 5;
matrix[5][2] = 3;
matrix[4][6] = 1;
//原顶点 入口
int source = 0;
dijstra(matrix,source);
}
//迪杰斯特拉算法
public static void dijstra(int[][] matrix,int source){
//最短路径的长度
int[] shorttest = new int[matrix.length];
//判断是否能够算出该顶点的最短距离 0没有对比过 1对比过
int[] visited = new int[matrix.length];
//拼接有效的路径
String[] path = new String[matrix.length];
//初始化路径
for (int i = 0; i < matrix.length; i++) {
path[i] = new String(source + "--->" + i);
}
//原点的有效距离是0
shorttest[source] = 0;
//有效的路径
visited[source] = 1;
for (int i = 1; i < matrix.length; i++) {
int min = Integer.MAX_VALUE;
int index = -1;
for (int j = 0; j < matrix.length; j++) {
if (visited[j] == 0 && matrix[source][j] < min){
min = matrix[source][j];
index = j;
}
}
shorttest[index] = min;
visited[index] = 1;
for (int j = 0; j < matrix.length; j++) {
if (visited[j] == 0 && matrix[source][index] + matrix[index][j] < matrix[source][j]){
matrix[source][j] = matrix[source][index] + matrix[index][j];
path[j] = path[index] + "--->" + j;
}
}
}
for (int i = 0; i < matrix.length; i++) {
if (i != source){
if (shorttest[i] == MaxValue){
System.out.println(source + "到" + i + "不可以直接到达");
}else {
System.out.println(source + "到" + i + "的最短路径是:" + path[i] + "最短距离是:" + shorttest[i]);
}
}
}
}
}
运行结果:
4.弗洛伊德算法
弗洛伊德算法选取某个节点k作为i到j需要经过的中间节点,通过比较d(i,k)+d(k,j)和现有d(i,j)的大小,将较小值更新为路径长度,对k节点的选取进行遍历,以得到在经过所有节点时i到j的最短路径长度,通过不断加入中间点的方式更新最短路径。同时在 path 数组中存储i到j所经过的中间节点k,用于最后递归调用输出路径结果。
package kruskal;
public class FloydAlgorithm {
public static int MaxValue = 100000;
//中间结点存放的数组
public static int[][] path;
public static void main(String[] args) {
//顶点数
int vertex = 7;
//边数
int edge = 10;
//权重
int[][] matrix = new int[vertex][vertex];
for (int i = 0; i < vertex; i++) {
for (int j = 0; j < vertex; j++) {
matrix[i][j] = MaxValue;
}
}
path = new int[matrix.length][matrix.length];
matrix[0][1] = 6;
matrix[1][2] = 5;
matrix[0][3] = 2;
matrix[3][1] = 7;
matrix[3][4] = 5;
matrix[1][5] = 3;
matrix[4][5] = 5;
matrix[5][2] = 3;
matrix[4][6] = 1;
floyd(matrix);
}
public static void floyd(int[][] matrix){
//防止int类型数组中默认值是0,因为顶点也存在0的数据
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
path[i][j] = -1;
}
}
//计算i通过中间顶点k到达j的路径
for (int k = 0; k < matrix.length; k++) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
if (matrix[i][k] + matrix[k][j] < matrix[i][j]){
matrix[i][j] = matrix[i][k] + matrix[k][j];
path[i][j] = k;
}
}
}
}
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
if (i != j){
if (matrix[i][j] == MaxValue){
System.out.println(i + "到" + j + "不可以直接到达");
}else {
System.out.println(i + "到" + j + "的最短路径是:" + matrix[i][j]);
System.out.print("最短路径:"+ i + "----->");
searchPath(i,j);
System.out.println(j);
}
}
}
}
}
public static void searchPath(int i,int j){
int m = path[i][j];
if (m == -1){
return;
}
searchPath(i,m);
System.out.print(m + "--->");
searchPath(m,j);
}
}
运行结果:
5.马踏棋盘算法
马踏棋盘算法也被称为骑士周游问题
将马随机放在国际象棋的8×8棋盘 Board[0-7] [0-7]的某个方格中,马按走棋规则(马走日字)进行移动。要求每个方格只进入一次,走遍棋盘上全部 64个方格
package kruskal;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
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) {
//描述棋盘
X = 8;
Y = 8;
int row = 1;
int column = 1;
int[][] chessBoard = new int[X][Y];
visited = new boolean[X*Y];
long start = System.currentTimeMillis();
chessboard(chessBoard,row - 1,column - 1,1);
long end = System.currentTimeMillis();
System.out.println(end - start);//输出为:12
}
public static void chessboard(int[][] chessBoard,int row,int column,int step){
chessBoard[row][column] = step;
visited[row * X +column] = true;
//每跳一步,都要计算当前所在的位置下一步要跳的位置数量,可以存储在集合中去
ArrayList<Point> ps = next(new Point(column, row));
//预判未来要跳的次数,减少回溯
sort(ps);
while (!ps.isEmpty()){
Point nextPiont = ps.remove(0);
if (!visited[nextPiont.y * X + nextPiont.x]){
chessboard(chessBoard,nextPiont.y,nextPiont.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<Point> ps = new ArrayList<>();
Point point = new Point();
if ((point.x = curPoint.x - 2) >= 0 && (point.y = curPoint.y - 1) >= 0){
ps.add(new Point(point));
}
if ((point.x = curPoint.x - 1) >= 0 && (point.y = curPoint.y - 2) >= 0){
ps.add(new Point(point));
}
if ((point.x = curPoint.x + 1) < X && (point.y = curPoint.y - 2) >= 0){
ps.add(new Point(point));
}
if ((point.x = curPoint.x + 2) < X && (point.y = curPoint.y - 1) >= 0){
ps.add(new Point(point));
}
if ((point.x = curPoint.x + 2) < X && (point.y = curPoint.y + 1) < Y){
ps.add(new Point(point));
}
if ((point.x = curPoint.x + 1) < X && (point.y = curPoint.y + 2) < Y){
ps.add(new Point(point));
}
if ((point.x = curPoint.x - 1) >= 0 && (point.y = curPoint.y + 2) < Y){
ps.add(new Point(point));
}
if ((point.x = curPoint.x - 2) >= 0 && (point.y = curPoint.y + 1) < Y){
ps.add(new Point(point));
}
return ps;
}
public static void sort(ArrayList<Point> ps){
ps.sort(new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
int count1 = next(o1).size();
int count2 = next(o2).size();
if (count1 < count2){
return -1;
}else if (count1 == count2){
return 0;
}else {
return 1;
}
}
});
}
}