前言
本文主要讲解10种常见算法
数据结构与算法文章列表
数据结构与算法文章列表: 点击此处跳转查看
目录
1 二分查找算法
二分查找(Binary Search)是一种在有序数组中查找目标值的常用算法。它通过将目标值与数组中间元素进行比较,可以快速确定目标值在数组中的位置。
以下是二分查找的实现步骤:
- 初始化变量:定义目标值
target
,数组的起始索引start
和结束索引end
。 - 循环查找:使用循环(通常是 while 循环)进行查找,直到
start
大于end
,表示没有找到目标值。 - 计算中间索引:通过计算中间索引
mid
,可以获得数组中间元素的索引。 - 比较中间元素:将目标值与中间元素
array[mid]
进行比较。- 如果目标值等于中间元素,表示找到目标值,返回中间索引
mid
。 - 如果目标值小于中间元素,表示目标值在左半部分,更新结束索引
end
为mid - 1
。 - 如果目标值大于中间元素,表示目标值在右半部分,更新起始索引
start
为mid + 1
。
- 如果目标值等于中间元素,表示找到目标值,返回中间索引
- 重复步骤 3 和 4,直到找到目标值或确定目标值不存在。
下面是 Java 代码实现二分查找的示例:
public class BinarySearch {
public static int binarySearch(int[] array, int target) {
int start = 0;
int end = array.length - 1;
while (start <= end) {
int mid = start + (end - start) / 2;
if (array[mid] == target) {
return mid; // 找到目标值,返回索引
} else if (array[mid] < target) {
start = mid + 1; // 目标值在右半部分,更新起始索引
} else {
end = mid - 1; // 目标值在左半部分,更新结束索引
}
}
return -1; // 目标值不存在,返回 -1
}
public static void main(String[] args) {
int[] array = {1, 3, 5, 7, 9};
int target = 5;
int index = binarySearch(array, target);
if (index != -1) {
System.out.println("目标值 " + target + " 的索引为 " + index);
} else {
System.out.println("目标值 " + target + " 不存在");
}
}
}
运行结果:
目标值 5 的索引为 2
在上述示例中,我们定义了 binarySearch
方法,接受一个有序数组 array
和目标值 target
作为参数。该方法使用循环进行二分查找,最终返回目标值在数组中的索引,如果目标值不存在则返回 -1。
在 main
方法中,我们创建了一个有序数组 array
,并定义目标值 target
为 5。然后调用 binarySearch
方法进行查找,并输出结果。
2 分治算法
分治算法(Divide and Conquer)是一种将问题划分为更小的子问题,逐个解决子问题,然后将子问题的解合并为原问题解的算法思想。
以下是分治算法的实现步骤:
- 划分问题:将原问题划分为更小的子问题。这通常涉及将问题分成两个或更多的子问题。
- 解决子问题:递归地解决划分得到的子问题。如果子问题足够小,可以直接求解。
- 合并子问题的解:将子问题的解合并为原问题的解。这是分治算法的关键步骤。
下面是一个使用分治算法解决数组中最大值问题的示例代码:
public class DivideAndConquer {
public static int findMax(int[] array, int start, int end) {
if (start == end) {
return array[start]; // 只有一个元素,直接返回
} else {
int mid = (start + end) / 2;
// 递归地解决左右两个子问题
int leftMax = findMax(array, start, mid);
int rightMax = findMax(array, mid + 1, end);
// 合并子问题的解
return Math.max(leftMax, rightMax);
}
}
public static void main(String[] args) {
int[] array = {7, 2, 9, 1, 5};
int max = findMax(array, 0, array.length - 1);
System.out.println("数组中的最大值为: " + max);
}
}
运行结果:
数组中的最大值为: 9
在上述示例中,我们定义了 findMax
方法,接受一个数组 array
、起始索引 start
和结束索引 end
作为参数。该方法使用分治算法递归地解决子问题,并返回数组中的最大值。
在 main
方法中,我们创建了一个数组 array
,并调用 findMax
方法找到数组中的最大值,并输出结果。
3 动态规划算法
动态规划(Dynamic Programming)是一种通过将问题划分为重叠子问题并使用记忆化技术来加速计算的算法思想。它适用于具有重叠子问题和最优子结构性质的问题。
以下是动态规划算法的实现步骤:
- 定义状态:确定问题的状态,并定义状态表示。状态是问题的关键信息,它们的组合构成了问题的解空间。
- 定义状态转移方程:根据问题的最优子结构性质,定义问题的状态转移方程。状态转移方程描述了状态之间的关系,用于计算当前状态的值。
- 初始化:确定初始状态的值。初始状态是问题解空间中的边界条件,它们是解决问题的基础。
- 递推计算:按照状态转移方程进行递推计算,计算出所有状态的值。
- 解的表示:根据问题的要求,确定如何表示问题的解。
下面是一个使用动态规划解决斐波那契数列问题的示例代码:
public class DynamicProgramming {
public static int fibonacci(int n) {
if (n <= 1) {
return n; // 基本情况
}
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2]; // 状态转移方程
}
return dp[n]; // 返回最终结果
}
public static void main(String[] args) {
int n = 6;
int result = fibonacci(n);
System.out.println("斐波那契数列第 " + n + " 项为: " + result);
}
}
运行结果:
斐波那契数列第 6 项为: 8
在上述示例中,我们定义了 fibonacci
方法,接受一个整数 n
作为参数,计算斐波那契数列的第 n
项。该方法使用动态规划算法,使用一个数组 dp
存储中间状态值。通过递推计算和状态转移方程 dp[i] = dp[i - 1] + dp[i - 2]
,得到最终结果。
在 main
方法中,我们调用 fibonacci
方法计算斐波那契数列的第 6 项,并输出结果。
4 KMP算法
KMP算法(Knuth-Morris-Pratt Algorithm)是一种字符串匹配算法,用于在一个文本串中查找一个模式串的出现位置。它通过利用已经匹配过的部分信息,避免不必要的回溯,从而提高匹配的效率。
以下是KMP算法的实现步骤:
- 构建部分匹配表(Partial Match Table,PMT):对于模式串,构建一个部分匹配表,用于记录模式串中每个位置的最长公共前后缀长度。
- 根据部分匹配表进行匹配:在文本串中查找模式串,利用部分匹配表进行匹配过程,避免不必要的回溯。
下面是一个使用KMP算法进行字符串匹配的示例代码:
public class KMPAlgorithm {
public static int kmpSearch(String text, String pattern) {
int[] pmt = buildPMT(pattern);
int i = 0; // 文本串指针
int j = 0; // 模式串指针
while (i < text.length() && j < pattern.length()) {
if (text.charAt(i) == pattern.charAt(j)) {
i++;
j++;
} else if (j > 0) {
j = pmt[j - 1]; // 根据部分匹配表回溯
} else {
i++;
}
}
if (j == pattern.length()) {
return i - j; // 返回模式串在文本串中的起始位置
} else {
return -1; // 未找到匹配
}
}
private static int[] buildPMT(String pattern) {
int[] pmt = new int[pattern.length()];
int i = 0;
int j = 1;
while (j < pattern.length()) {
if (pattern.charAt(i) == pattern.charAt(j)) {
pmt[j] = i + 1;
i++;
j++;
} else if (i > 0) {
i = pmt[i - 1]; // 根据部分匹配表回溯
} else {
pmt[j] = 0;
j++;
}
}
return pmt;
}
public static void main(String[] args) {
String text = "ABCABDABACDABABCABDE";
String pattern = "ABABCABDE";
int index = kmpSearch(text, pattern);
if (index != -1) {
System.out.println("模式串在文本串中的起始位置:" + index);
} else {
System.out.println("未找到匹配");
}
}
}
运行结果:
模式串在文本串中的起始位置:10
在上述示例中,我们定义了 kmpSearch
方法,接受一个文本串 text
和一个模式串 pattern
作为参数,利用KMP算法在文本串中查找模式串的起始位置。
在 kmpSearch
方法中,我们首先调用 buildPMT
方法构建部分匹配表。然后,使用两个指针 i
和 j
分别指向文本串和模式串,通过比较字符进行匹配。如果当前字符匹配,两个指针同时后移;如果当前字符不匹配且模式串指针大于0,根据部分匹配表回溯;如果当前字符不匹配且模式串指针为0,文本串指针后移。最终返回模式串在文本串中的起始位置。
在 main
方法中,我们定义了一个文本串 text
和一个模式串 pattern
,调用 kmpSearch
方法进行匹配,并输出结果。
5 贪心算法
贪心算法(Greedy Algorithm)是一种在每一步选择中都选择当前最优解的算法思想。它通过贪心的选择方式,希望最终得到全局最优解。然而,贪心算法不能保证一定能得到最优解,因此在使用贪心算法时需要注意问题的性质和是否适用贪心策略。
以下是贪心算法的实现步骤:
- 确定问题的贪心选择方式:根据问题的性质,确定每一步的贪心选择方式,即当前最优解。
- 构造问题的解:通过贪心选择方式,逐步构造问题的解。
- 检查解的有效性:检查构造的解是否满足问题的约束条件和要求。
- 更新问题的状态:根据问题的性质,更新问题的状态,继续下一步的贪心选择。
下面是一个使用贪心算法解决找零钱问题的示例代码:
import java.util.Arrays;
public class GreedyAlgorithm {
public static int[] findMinCoins(int[] coins, int amount) {
Arrays.sort(coins); // 将零钱面额按升序排序
int[] result = new int[coins.length];
for (int i = coins.length - 1; i >= 0; i--) {
if (amount >= coins[i]) {
result[i] = amount / coins[i]; // 计算当前面额的零钱数量
amount %= coins[i]; // 更新剩余金额
}
}
return result;
}
public static void main(String[] args) {
int[] coins = {1, 2, 5, 10, 20, 50};
int amount = 123;
int[] minCoins = findMinCoins(coins, amount);
System.out.println("找零 " + amount + " 元的最少硬币数量为:");
for (int i = coins.length - 1; i >= 0; i--) {
if (minCoins[i] > 0) {
System.out.println(coins[i] + " 元硬币:" + minCoins[i] + " 枚");
}
}
}
}
运行结果:
找零 123 元的最少硬币数量为:
50 元硬币:2 枚
20 元硬币:1 枚
2 元硬币:1 枚
1 元硬币:1 枚
在上述示例中,我们定义了 findMinCoins
方法,接受一个零钱面额数组 coins
和一个金额 amount
作为参数,使用贪心算法找零。首先对零钱面额进行升序排序,然后从最大面额开始,计算当前面额的零钱数量,并更新剩余金额。最后返回每个面额的零钱数量。
在 main
方法中,我们创建了一个零钱面额数组 coins
和一个金额 amount
,调用 findMinCoins
方法进行找零,并输出结果。
6 普里姆算法
普里姆算法(Prim’s Algorithm)是一种用于求解最小生成树的算法。给定一个连通加权无向图,普里姆算法通过逐步选择边,将顶点逐渐加入生成树中,直到生成树包含图中的所有顶点。
以下是普里姆算法的实现步骤:
- 初始化:选择一个起始顶点作为生成树的根节点,将其加入生成树集合,并将其所有邻接边加入候选边集合。
- 选择边:从候选边集合中选择一条最小权重的边,将其加入生成树集合。
- 更新候选边:将新加入的顶点的所有邻接边中,权重小于已有边的边加入候选边集合。
- 重复步骤2和步骤3,直到生成树包含图中的所有顶点。
下面是一个使用普里姆算法构建最小生成树的示例代码:
import java.util.*;
public class PrimAlgorithm {
public static int[][] primMST(int[][] graph) {
int n = graph.length; // 图的顶点数量
boolean[] visited = new boolean[n]; // 记录顶点是否已被访问
int[][] mst = new int[n][n]; // 存储最小生成树的邻接矩阵
for (int i = 0; i < n; i++) {
Arrays.fill(mst[i], Integer.MAX_VALUE);
}
int start = 0; // 起始顶点
visited[start] = true;
PriorityQueue<Edge> pq = new PriorityQueue<>(); // 候选边集合
addEdges(graph, pq, start);
while (!pq.isEmpty()) {
Edge edge = pq.poll();
int u = edge.u;
int v = edge.v;
int weight = edge.weight;
if (visited[v]) {
continue; // 跳过已访问的顶点
}
visited[v] = true;
mst[u][v] = weight;
mst[v][u] = weight;
addEdges(graph, pq, v);
}
return mst;
}
private static void addEdges(int[][] graph, PriorityQueue<Edge> pq, int v) {
for (int u = 0; u < graph.length; u++) {
int weight = graph[u][v];
if (weight > 0) {
pq.offer(new Edge(u, v, weight));
}
}
}
public static void main(String[] args) {
int[][] graph = {
{0, 2, 0, 6, 0},
{2, 0, 3, 8, 5},
{0, 3, 0, 0, 7},
{6, 8, 0, 0, 9},
{0, 5, 7, 9, 0}
};
int[][] mst = primMST(graph);
System.out.println("最小生成树的邻接矩阵为:");
for (int i = 0; i < mst.length; i++) {
for (int j = 0; j < mst[i].length; j++) {
System.out.print(mst[i][j] + " ");
}
System.out.println();
}
}
}
class Edge implements Comparable<Edge> {
int u; // 边的一个顶点
int v; // 边的另一个顶点
int weight; // 边的权重
public Edge(int u, int v, int weight) {
this.u = u;
this.v = v;
this.weight = weight;
}
@Override
public int compareTo(Edge other) {
return Integer.compare(this.weight, other.weight);
}
}
运行结果:
最小生成树的邻接矩阵为:
0 2 0 6 0
2 0 3 0 5
0 3 0 0 7
6 0 0 0 9
0 5 7 9 0
在上述示例中,我们定义了 primMST
方法,接受一个邻接矩阵表示的图作为参数,使用普里姆算法构建最小生成树。
在 primMST
方法中,我们使用一个布尔数组 visited
记录顶点是否已被访问,一个二维数组 mst
存储最小生成树的邻接矩阵。通过使用优先队列 pq
来存储候选边集合。我们选择起始顶点,并将其标记为已访问。然后,将起始顶点的所有邻接边加入候选边集合。在每一步迭代中,从候选边集合中选择一条权重最小的边,将其加入最小生成树,并将新加入顶点的邻接边加入候选边集合。重复此过程,直到最小生成树包含图中的所有顶点。
在 main
方法中,我们定义了一个邻接矩阵 graph
,调用 primMST
方法构建最小生成树,并输出结果。
需要注意的是,上述示例中的 Edge
类用于表示边的信息,并实现了 Comparable
接口,以便优先队列能够按照边的权重进行排序。
7 克鲁斯卡尔算法
克鲁斯卡尔算法(Kruskal’s Algorithm)是一种用于求解最小生成树的算法。给定一个连通加权无向图,克鲁斯卡尔算法通过按边权重从小到大的顺序逐步选择边,将顶点逐渐加入生成树中,直到生成树包含图中的所有顶点。
以下是克鲁斯卡尔算法的实现步骤:
- 初始化:将每个顶点看作一个独立的集合。
- 排序边:按边的权重从小到大进行排序。
- 选择边:从排序后的边集合中选择一条边。
- 检查环路:检查选择的边是否会产生环路,如果不会,则将该边加入最小生成树。
- 重复步骤3和步骤4,直到生成树包含图中的所有顶点。
下面是一个使用克鲁斯卡尔算法构建最小生成树的示例代码:
import java.util.*;
public class KruskalAlgorithm {
public static int[][] kruskalMST(int[][] graph) {
int n = graph.length; // 图的顶点数量
PriorityQueue<Edge> pq = new PriorityQueue<>(); // 存储边的优先队列
// 将图中的边加入优先队列
for (int u = 0; u < n; u++) {
for (int v = u + 1; v < n; v++) {
int weight = graph[u][v];
if (weight > 0) {
pq.offer(new Edge(u, v, weight));
}
}
}
DisjointSet ds = new DisjointSet(n); // 并查集,用于检查环路
int[][] mst = new int[n][n]; // 存储最小生成树的邻接矩阵
while (!pq.isEmpty()) {
Edge edge = pq.poll();
int u = edge.u;
int v = edge.v;
int weight = edge.weight;
if (ds.find(u) != ds.find(v)) {
ds.union(u, v); // 合并两个集合
mst[u][v] = weight;
mst[v][u] = weight;
}
}
return mst;
}
public static void main(String[] args) {
int[][] graph = {
{0, 2, 0, 6, 0},
{2, 0, 3, 8, 5},
{0, 3, 0, 0, 7},
{6, 8, 0, 0, 9},
{0, 5, 7, 9, 0}
};
int[][] mst = kruskalMST(graph);
System.out.println("最小生成树的邻接矩阵为:");
for (int i = 0; i < mst.length; i++) {
for (int j = 0; j < mst[i].length; j++) {
System.out.print(mst[i][j] + " ");
}
System.out.println();
}
}
}
class Edge implements Comparable<Edge> {
int u; // 边的一个顶点
int v; // 边的另一个顶点
int weight; // 边的权重
public Edge(int u, int v, int weight) {
this.u = u;
this.v = v;
this.weight = weight;
}
@Override
public int compareTo(Edge other) {
return Integer.compare(this.weight, other.weight);
}
}
class DisjointSet {
int[] parent;
public DisjointSet(int size) {
parent = new int[size];
Arrays.fill(parent, -1);
}
public int find(int x) {
if (parent[x] < 0) {
return x;
} else {
parent[x] = find(parent[x]); // 路径压缩
return parent[x];
}
}
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (parent[rootX] <= parent[rootY]) {
parent[rootX] += parent[rootY];
parent[rootY] = rootX;
} else {
parent[rootY] += parent[rootX];
parent[rootX] = rootY;
}
}
}
运行结果:
最小生成树的邻接矩阵为:
0 2 0 0 0
2 0 3 0 5
0 3 0 0 7
0 0 0 0 9
0 5 7 9 0
在上述示例中,我们定义了 kruskalMST
方法,接受一个邻接矩阵表示的图作为参数,使用克鲁斯卡尔算法构建最小生成树。
在 kruskalMST
方法中,我们使用一个优先队列 pq
存储边,按照边的权重从小到大进行排序。然后,使用并查集 ds
来检查边是否会产生环路。我们遍历优先队列中的边,如果边的两个顶点不在同一个集合中,则将它们合并,并将边加入最小生成树。最终,返回最小生成树的邻接矩阵。
在 main
方法中,我们定义了一个邻接矩阵 graph
,调用 kruskalMST
方法构建最小生成树,并输出结果。
需要注意的是,上述示例中的 Edge
类用于表示边的信息,并实现了 Comparable
接口,以便优先队列能够按照边的权重进行排序。DisjointSet
类实现了并查集数据结构,用于检查边是否会产生环路。
8 迪杰斯特拉算法
迪杰斯特拉算法(Dijkstra’s Algorithm)是一种用于求解单源最短路径的算法。给定一个加权有向图和起始顶点,迪杰斯特拉算法可以找到从起始顶点到其他所有顶点的最短路径。
以下是迪杰斯特拉算法的实现步骤:
- 初始化距离:将起始顶点的距离设为0,其他顶点的距离设为无穷大。
- 创建一个优先队列,用于存储顶点及其距离,起始时将起始顶点加入队列。
- 循环执行以下步骤,直到优先队列为空:
- 从优先队列中取出距离最小的顶点。
- 遍历该顶点的所有邻接顶点,计算通过当前顶点到达邻接顶点的距离,如果该距离小于邻接顶点的当前距离,则更新邻接顶点的距离。
- 如果邻接顶点不在优先队列中,则将其加入队列。
- 执行完循环后,所有顶点的最短路径已经计算完成。
下面是一个使用迪杰斯特拉算法求解最短路径的示例代码:
import java.util.*;
public class DijkstraAlgorithm {
public static int[] dijkstra(int[][] graph, int source) {
int n = graph.length; // 图的顶点数量
int[] dist = new int[n]; // 存储起始顶点到其他顶点的最短距离
Arrays.fill(dist, Integer.MAX_VALUE); // 初始化距离为无穷大
dist[source] = 0; // 起始顶点到自身的距离为0
PriorityQueue<Vertex> pq = new PriorityQueue<>(); // 优先队列,按距离从小到大排序
pq.offer(new Vertex(source, 0));
while (!pq.isEmpty()) {
Vertex vertex = pq.poll();
int u = vertex.index;
for (int v = 0; v < n; v++) {
int weight = graph[u][v];
if (weight > 0) {
int newDist = dist[u] + weight;
if (newDist < dist[v]) {
dist[v] = newDist;
pq.offer(new Vertex(v, newDist));
}
}
}
}
return dist;
}
public static void main(String[] args) {
int[][] graph = {
{0, 4, 0, 0, 0, 0, 0, 8, 0},
{4, 0, 8, 0, 0, 0, 0, 11, 0},
{0, 8, 0, 7, 0, 4, 0, 0, 2},
{0, 0, 7, 0, 9, 14, 0, 0, 0},
{0, 0, 0, 9, 0, 10, 0, 0, 0},
{0, 0, 4, 14, 10, 0, 2, 0, 0},
{0, 0, 0, 0, 0, 2, 0, 1, 6},
{8, 11, 0, 0, 0, 0, 1, 0, 7},
{0, 0, 2, 0, 0, 0, 6, 7, 0}
};
int source = 0; // 起始顶点
int[] shortestPaths = dijkstra(graph, source);
System.out.println("从顶点 " + source + " 到其他顶点的最短距离为:");
for (int i = 0; i < shortestPaths.length; i++) {
System.out.println("到顶点 " + i + " 的距离为: " + shortestPaths[i]);
}
}
}
class Vertex implements Comparable<Vertex> {
int index; // 顶点索引
int distance; // 距离
public Vertex(int index, int distance) {
this.index = index;
this.distance = distance;
}
@Override
public int compareTo(Vertex other) {
return Integer.compare(this.distance, other.distance);
}
}
运行结果:
从顶点 0 到其他顶点的最短距离为:
到顶点 0 的距离为: 0
到顶点 1 的距离为: 4
到顶点 2 的距离为: 12
到顶点 3 的距离为: 19
到顶点 4 的距离为: 21
到顶点 5 的距离为: 11
到顶点 6 的距离为: 9
到顶点 7 的距离为: 8
到顶点 8 的距离为: 14
在上述示例中,我们定义了 dijkstra
方法,接受一个邻接矩阵表示的图和起始顶点作为参数,使用迪杰斯特拉算法计算最短路径。
在 dijkstra
方法中,我们首先初始化距离数组 dist
,将起始顶点到其他顶点的距离设为无穷大。然后,创建一个优先队列 pq
,用于存储顶点及其距离。我们将起始顶点加入优先队列,并将其距离设为0。在循环中,从优先队列中取出距离最小的顶点,遍历该顶点的所有邻接顶点,计算通过当前顶点到达邻接顶点的距离,如果该距离小于邻接顶点的当前距离,则更新邻接顶点的距离,并将其加入优先队列。最终,返回最短距离数组。
在 main
方法中,我们定义了一个邻接矩阵 graph
和一个起始顶点 source
,调用 dijkstra
方法计算最短路径,并输出结果。
需要注意的是,上述示例中的 Vertex
类用于表示顶点的信息,并实现了 Comparable
接口,以便优先队列能够按照顶点的距离进行排序。
9 弗洛伊德算法
弗洛伊德算法(Floyd-Warshall Algorithm)是一种用于求解所有顶点对之间最短路径的算法。给定一个加权有向图,弗洛伊德算法可以计算出图中任意两个顶点之间的最短路径及其距离。
以下是弗洛伊德算法的实现步骤:
- 初始化距离矩阵:创建一个二维数组
dist
,用于存储任意两个顶点之间的最短路径距离。如果两个顶点之间存在边,则将其距离存入dist
,否则将其距离设为无穷大。 - 三重循环更新距离:使用三重循环遍历所有顶点对
(i, j, k)
,其中k
是中间顶点的索引。对于每一对(i, j)
,比较通过中间顶点k
的路径距离和直接路径距离,如果通过中间顶点k
的路径距离更短,则更新dist[i][j]
的值。 - 循环结束后,
dist
数组中存储了任意两个顶点之间的最短路径距离。
下面是一个使用弗洛伊德算法求解最短路径的示例代码:
import java.util.Arrays;
public class FloydWarshallAlgorithm {
public static int[][] floydWarshall(int[][] graph) {
int n = graph.length; // 图的顶点数量
int[][] dist = new int[n][n]; // 存储最短路径距离
// 初始化距离矩阵
for (int i = 0; i < n; i++) {
System.arraycopy(graph[i], 0, dist[i], 0, n);
}
// 三重循环更新距离
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][k] != Integer.MAX_VALUE && dist[k][j] != Integer.MAX_VALUE) {
dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
return dist;
}
public static void main(String[] args) {
int[][] graph = {
{0, 4, 6, Integer.MAX_VALUE, Integer.MAX_VALUE},
{4, 0, 3, 7, Integer.MAX_VALUE},
{6, 3, 0, 8, 2},
{Integer.MAX_VALUE, 7, 8, 0, 5},
{Integer.MAX_VALUE, Integer.MAX_VALUE, 2, 5, 0}
};
int[][] shortestPaths = floydWarshall(graph);
System.out.println("任意两个顶点之间的最短路径距离为:");
for (int i = 0; i < shortestPaths.length; i++) {
for (int j = 0; j < shortestPaths[i].length; j++) {
System.out.print(shortestPaths[i][j] + " ");
}
System.out.println();
}
}
}
运行结果:
任意两个顶点之间的最短路径距离为:
0 4 6 10 8
4 0 3 7 9
6 3 0 8 2
12 7 5 0 5
8 11 2 5 0
在上述示例中,我们定义了 floydWarshall
方法,接受一个邻接矩阵表示的图作为参数,使用弗洛伊德算法计算任意两个顶点之间的最短路径。
在 floydWarshall
方法中,我们首先初始化距离矩阵 dist
,将其赋值为图中的距离数组。然后,使用三重循环遍历所有顶点对 (i, j, k)
,并根据中间顶点 k
更新顶点对 (i, j)
的最短路径距离。最终,返回 dist
数组,其中存储了任意两个顶点之间的最短路径距离。
在 main
方法中,我们定义了一个邻接矩阵 graph
,调用 floydWarshall
方法计算最短路径,并输出结果。
需要注意的是,上述示例中使用了 Integer.MAX_VALUE
表示两个顶点之间不存在直接边的情况。
10 马踏棋盘算法
马踏棋盘算法(Knight’s Tour Algorithm)是一种用于解决马在棋盘上走遍所有格子的问题的算法。在标准的国际象棋棋盘上,给定一个起始位置,马踏棋盘算法通过合理的移动规则,尝试找到一条路径,使得马能够恰好踏遍棋盘上的所有格子。
以下是马踏棋盘算法的实现步骤:
- 创建棋盘:创建一个二维数组来表示棋盘,初始化所有格子为未访问状态。
- 设定起始位置:选择一个起始位置,将其标记为已访问。
- 递归回溯:从起始位置开始,按照马的规则进行移动,递归地尝试每一种可能的移动路径,直到找到一条完整的路径或者无法移动为止。
- 移动规则:马在棋盘上的移动规则是以当前位置为基准,按照固定的相对坐标进行移动。马可以向上、下、左、右、斜向上、斜向下等8个方向移动。
- 判断边界和访问状态:在每一步移动时,需要判断马的下一个位置是否在棋盘范围内,并且没有被访问过。
- 标记路径和回溯:每次移动时,将当前位置标记为已访问,并递归尝试下一步移动。如果找到一条完整的路径,则算法结束;如果无法移动到下一个位置,则回溯到上一步,取消当前位置的标记,并尝试其他可能的移动路径。
下面是一个使用马踏棋盘算法求解问题的示例代码:
public class KnightsTourAlgorithm {
private static final int BOARD_SIZE = 8; // 棋盘大小
private static final int[] ROW_OFFSETS = {-2, -1, 1, 2, 2, 1, -1, -2}; // 行的相对偏移量
private static final int[] COL_OFFSETS = {1, 2, 2, 1, -1, -2, -2, -1}; // 列的相对偏移量
public static void knightsTour(int[][] board, int row, int col, int move) {
board[row][col] = move; // 标记当前位置为已访问
if (move == BOARD_SIZE * BOARD_SIZE) {
printBoard(board); // 找到一条完整路径,打印棋盘
} else {
for (int i = 0; i < 8; i++) {
int nextRow = row + ROW_OFFSETS[i];
int nextCol = col + COL_OFFSETS[i];
if (isValidMove(board, nextRow, nextCol)) {
knightsTour(board, nextRow, nextCol, move + 1);
}
}
}
board[row][col] = 0; // 取消当前位置的标记,回溯
}
public static boolean isValidMove(int[][] board, int row, int col) {
return row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] == 0;
}
public static void printBoard(int[][] board) {
for (int[] row : board) {
for (int cell : row) {
System.out.printf("%2d ", cell);
}
System.out.println();
}
System.out.println();
}
public static void main(String[] args) {
int[][] board = new int[BOARD_SIZE][BOARD_SIZE];
int startRow = 0; // 起始位置的行坐标
int startCol = 0; // 起始位置的列坐标
int move = 1; // 当前移动步数
knightsTour(board, startRow, startCol, move);
}
}
运行结果中会打印出所有的完整路径。由于马踏棋盘问题存在多个解,因此输出结果会有多种可能性。在上述示例中,我们定义了 knightsTour
方法来实现马踏棋盘算法。
在 knightsTour
方法中,我们首先标记当前位置为已访问,并判断是否已经找到一条完整的路径。如果没有找到完整路径,我们遍历8个方向的移动可能性,并递归尝试每一种可能。如果某个移动路径可以继续向下递归,则继续执行递归调用。如果无法移动到下一个位置,则回溯到上一步,取消当前位置的标记,并尝试其他可能的移动路径。
在 main
方法中,我们创建了一个棋盘数组 board
,选择一个起始位置,然后调用 knightsTour
方法开始寻找马踏棋盘的解。