publicclassLinkBlock{staticint count =0;publicstaticvoidmain(String[]args){char[][] maps ={{'*','*','*','#','#'},{'*','#','#','*','#'},{'*','#','*','*','#'},{'#','#','#','*','#'},{'#','#','*','*','#'}};int row =maps.length, col = maps[0].length;boolean[][] visited =newboolean[row][col];traverse(maps, visited);System.out.println(count);}publicstaticvoidtraverse(char[][]graph,boolean[][]visited){for(int i =0; i <graph.length; i++){for(int j =0; j < graph[0].length; j++){if(!visited[i][j]&& graph[i][j]=='#'){ count++;dfs(graph, visited, i, j);}}}}privatestaticvoiddfs(char[][]graph,boolean[][]visited,inti,intj){ // 边界if(i <0|| i >=graph.length|| j <0|| j >= graph[0].length)return; // 深度搜索if(!visited[i][j]&& graph[i][j]=='#'){ visited[i][j]=true;dfs(graph, visited, i +1, j);dfs(graph, visited, i -1, j);dfs(graph, visited, i, j +1);dfs(graph, visited, i, j -1);}}}
空间复杂度是 $O(n^2)$,由于每个字符只会被访问一次,时间复杂度也是 $O(n^2)$。
BFS 求最短路径
假设有一个迷宫,需要求解起点到终点的最短路径。即给一个 n 行 m 列的迷宫,S 表示迷宫的起点,T 表示迷宫的终点,* 表示不能通过的点,# 表示可以通过的点。现在要求从 S 出发走到 T,每次只能上下左右走动,并且只能进入能通过的点,每个点只能通过一次,问是否能够到达终点可以的话求出最短路程,如果不可以输出 -1。例如:
为了记录每个位置是否被访问过,以及从起点到当前位置的路程,可以直接在原二维数组上修改,访问到非 * 元素就将其设置为路程数,例如 S 起点设置为 0,则其下一步的 # 设置为 1,以此类推,直到终点,这时终点处修改后的值就是最终的最短路程。当然,也可以额外用一个同样大小的数组来标识每一个位置是否被访问过,另外一个数组来记录从开始位置到当前位置的路程(步数)。
该算法主要做三重循环,最外一层表示枚举中转点,里面的两层循环其实意味着任何两个节点 A 和 B,随着这个中转点 C 的加入,「A 到 C 的距离 + C 到 B 的距离」 可能更小,如果更小就更新:「A 到 B 的距离 = A 到 C 的距离 + C 到 B 的距离」,这种做法挺暴力的,但是简单易懂,而且求出来的是所有节点之间的最短路径。
如果使用邻接表表示图,且是稀疏图时,使用优先级队列实现的 Prim 算法更清晰和高效。定义的邻接表 List<List<int[]>> graph 中每个 graph.get(u) 是一个列表,里面是形如 new int[]{v, weight} 的数组,表示从 u 到 v 有一条权重为 weight 的边。执行步骤如下:
维护一个 visited[] 标记哪些节点已加入 MST
使用 PriorityQueue<int[]> pq 按照边权升序存储候选边,每个元素是 new int[]{from, to, weight}
class Edge {
int form, to, weight;
public Edge(int form, int to, int weight) {
this.form = form;
this.to = to;
this.weight = weight;
}
}
public class BellmanFord {
public static void main(String[] args) {
int V = 4;
int E = 7;
Edge[] edges = new Edge[E];
edges[0] = new Edge(0, 1, 2);
edges[1] = new Edge(0, 2, 5);
edges[2] = new Edge(3, 0, 9);
edges[3] = new Edge(1, 3, 5);
edges[4] = new Edge(3, 1, 4);
edges[5] = new Edge(2, 3, 1);
edges[6] = new Edge(1, 2, 2);
System.out.println(Arrays.toString(find(0, V, edges)));
}
public static int[] find(int source, int V, Edge[] edges) {
int[] dist = new int[V];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[source] = 0;
// 对所有边进行 V-1 次松弛
for(int i = 0; i < V; i++) {
for(Edge edge : edges) {
int u = edge.form;
int v = edge.to;
int w = edge.weight;
if(dist[u] != Integer.MAX_VALUE && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
}
}
}
// 检测负权环
for(Edge edge : edges) {
int u = edge.form;
int v = edge.to;
int w = edge.weight;
if(dist[u] != Integer.MAX_VALUE && dist[u] + w < dist[v]) {
System.out.println("存在负权环!");
return null;
}
}
return dist;
}
}
public static void main(String[] args) {
int[][] matrix = new int[][] {
{ 0, 2, 2, 9 },
{ 2, 0, 2, 4 },
{ 2, 2, 0, 1 },
{ 9, 4, 1, 0 },
};
System.out.println(Arrays.toString(prim(matrix)));
}
public static int[] prim(int[][] graph) {
int n = graph.length;
List<Integer> visited = new ArrayList<>();
// 任选一个点当根节点
visited.add(0);
int[] parent = new int[n];
parent[0] = -1; // 首节点无父节点
int start = 0, end = 0, val;
// 直到所有节点都加入
while(visited.size() < n) {
val = Integer.MAX_VALUE;
// 遍历已经加入的节点
for(Integer node : visited) {
for(int i = 0; i < n; i++) {
// 从未访问过的节点中找路径最短的
if(!visited.contains(i)) {
if(graph[node][i] < val) {
start = node;
end = i;
val = graph[node][i];
}
}
}
}
// 加入该点,并保存它的父节点
visited.add(end);
parent[end] = start;
}
return parent;
}
public static void primWithAdjList() {
int n = 4;
List<List<int[]>> graph = new ArrayList<>();
for(int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
// 添加边:双向图
addEdge(graph, 0, 1, 2);
addEdge(graph, 0, 2, 2);
addEdge(graph, 0, 3, 9);
addEdge(graph, 1, 2, 2);
addEdge(graph, 1, 3, 4);
addEdge(graph, 2, 3, 1);
System.out.println(Arrays.toString(find(graph, n)));
}
private static void addEdge(List<List<int[]>> graph, int u, int v, int weight) {
graph.get(u).add(new int[]{v, weight});
graph.get(v).add(new int[]{u, weight});
}
public static int[] find(List<List<int[]>> graph, int n) {
boolean[] visited = new boolean[n];
int[] parent = new int[n];
// {from, to, weight} 其中 weight 只用于排序
PriorityQueue<int[]> queue = new PriorityQueue<>(Comparator.comparingInt(a -> a[2]));
// 任选根节点
parent[0] = -1;
for(int[] neighbor : graph.get(0)) {
queue.offer(new int[]{0, neighbor[0], neighbor[1]});
}
visited[0] = true;
while(!queue.isEmpty()) {
int[] curr = queue.poll();
int from = curr[0], to = curr[1];
if(visited[to]) continue;
visited[to] = true;
parent[to] = from;
for(int[] neighbor : graph.get(to)) {
if(!visited[neighbor[0]])
queue.offer(new int[]{to, neighbor[0], neighbor[1]});
}
}
return parent;
}
public class Kruskal {
public static void main(String[] args) {
int n = 4;
List<int[]> graph = new ArrayList<>();
graph.add(new int[]{0, 1, 2});
graph.add(new int[]{0, 2, 2});
graph.add(new int[]{0, 3, 9});
graph.add(new int[]{1, 2, 2});
graph.add(new int[]{1, 3, 4});
graph.add(new int[]{2, 3, 1});
graph.sort(Comparator.comparingInt(a -> a[2]));
kruskal_ref(graph, n);
System.out.println(Arrays.toString(kruskal(graph, n)));
}
public static void kruskal_ref(List<int[]> graph, int n) {
int sum = 0;
int[] parent = new int[n]; // 这里的parent并代表生成树结构
Arrays.fill(parent, -1);
for (int[] curr : graph) {
int rootStart = findRoot(parent, curr[0]);
int rootEnd = findRoot(parent, curr[1]);
if (rootStart != rootEnd) {
parent[rootStart] = rootEnd; // 两个根节点合并
sum += curr[2];
System.out.println(curr[0] + " <- " + curr[1]);
}
}
System.out.println(sum);
}
// 并查集找根节点,没有路径压缩
private static int findRoot(int[] parent, int index) {
while (parent[index] >= 0)
index = parent[index];
return index;
}
public static int[] kruskal(List<int[]> graph, int n) {
int sum = 0;
int[] parents = new int[n]; // 这里的parent代表生成树结构
Arrays.fill(parents, -1);
UnionFind uf = new UnionFind(n);
for(int[] edge : graph) {
int from = edge[0], to = edge[1];
int weight = edge[2];
if(uf.find(from) != uf.find(to)) {
uf.union(from, to);
parents[to] = from;
sum += weight;
}
}
return parents;
}
}
// 并查集,带路径压缩,按秩合并
class UnionFind {
int[] parent;
int[] rank;
UnionFind(int n) {
parent = new int[n];
rank = new int[n];
for(int i = 0; i < n; i++)
parent[i] = i;
}
int find(int index) {
if(parent[index] != index)
index = find(parent[index]);
return index;
}
void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if(rootX == rootY) return;
if(rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if(rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
}
/* output
2 <- 3
0 <- 1
0 <- 2
5
[-1, 0, 0, 2] */