强连通分量(Strongly Connected Components, SCCs)是图论中的一个概念,主要用于有向图。在有向图中,如果从图中的某个顶点 A 能够到达另一个顶点 B,并且从顶点 B 也能到达顶点 A,则称这两个顶点是强连通的。一个强连通分量是这样一个子图,其内部任意两个顶点都是强连通的。
强连通分量的算法:
-
Kosaraju 算法:这是一个经典的算法,它首先对原图进行深度优先搜索(DFS),记录顶点的完成顺序,并建立一个逆序的顶点栈。然后,对原图的逆图(即边的方向全部反转)再次进行 DFS,每次 DFS 会找到原图中的一个强连通分量。
-
Tarjan 算法:Tarjan 算法也是一种用于寻找强连通分量的算法,它在同一个图中完成寻找过程,不需要构建逆图。Tarjan 算法在 DFS 的过程中为每个顶点分配三个值:发现时间戳(disc)、最低可达时间戳(low),以及一个标记用来判断顶点是否在栈上。
强连通分量的 Java 实现(Kosaraju 算法):
import java.util.*;
public class StronglyConnectedComponents {
private int time;
private List<List<Integer>> adjList;
private Stack<Integer> postOrder;
private List<List<Integer>> sccList;
public List<List<Integer>> getSCCs(int n, List<List<Integer>> edges) {
adjList = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
adjList.add(new ArrayList<>());
}
postOrder = new Stack<>();
sccList = new ArrayList<>();
time = 0;
// Build the adjacency list and perform DFS on the original graph
for (List<Integer> edge : edges) {
adjList.get(edge.get(0)).add(edge.get(1));
}
for (int i = 0; i < n; i++) {
if (!visited[i]) {
dfs(i);
}
}
// Create the transpose of the graph
List<List<Integer>> transpose = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
transpose.add(new ArrayList<>());
}
for (List<Integer> edge : edges) {
transpose.get(edge.get(1)).add(edge.get(0));
}
// Perform DFS on the transpose graph
for (int i = 0; i < n; i++) {
if (!visited[i]) {
dfsOnTranspose(transpose, i);
}
}
return sccList;
}
private void dfs(int v) {
visited[v] = true;
for (int neighbor : adjList.get(v)) {
if (!visited[neighbor]) {
dfs(neighbor);
}
}
postOrder.push(v);
}
private void dfsOnTranspose(List<List<Integer>> transpose, int v) {
visited[v] = true;
sccList.add(v);
for (int neighbor : transpose.get(v)) {
if (!visited[neighbor]) {
dfsOnTranspose(transpose, neighbor);
}
}
}
// Helper method to initialize visited array and call getSCCs
public List<List<Integer>> scc(int n, List<List<Integer>> edges) {
boolean[] visited = new boolean[n];
getSCCs(n, edges);
return sccList;
}
public static void main(String[] args) {
StronglyConnectedComponents scc = new StronglyConnectedComponents();
int n = 5;
List<List<Integer>> edges = Arrays.asList(
Arrays.asList(1, 0),
Arrays.asList(0, 2),
Arrays.asList(2, 1),
Arrays.asList(0, 3),
Arrays.asList(3, 4)
);
List<List<Integer>> result = scc.scc(n, edges);
System.out.println("Strongly Connected Components: " + result);
}
}
在面试中,强连通分量是一个重要的图算法问题,它考察应聘者对图算法的理解和算法实现能力。通过实现强连通分量的算法,可以展示你对深度优先搜索和图算法的掌握程度。希望这些知识点和示例代码能够帮助你更好地准备面试!强连通分量(SCC)是图算法中的一个高级话题,经常出现在大厂的面试中。以下是三道与强连通分量相关的面试题目,以及相应的Java源码实现。
题目 1:社交网络中的社交圈子
描述:
在社交网络中,如果两个人是朋友,那么他们之间是双向关注的。给定社交网络中的关注关系,找出所有的社交圈子。
示例:
输入:社交网络关系图的邻接表表示,例如:
{
1: [2],
2: [1, 3],
3: [2],
4: [5],
5: [4]
}
输出:[[1, 2, 3], [4, 5]]
Java 源码(使用Kosaraju算法):
import java.util.*;
public class SocialCircles {
List<List<Integer>> sccList;
int time;
Stack<Integer> postOrder;
Map<Integer, List<Integer>> graph;
public List<List<Integer>> findCircles(int n, Map<Integer, List<Integer>> graph) {
this.graph = new HashMap<>(graph);
sccList = new ArrayList<>();
time = 0;
postOrder = new Stack<>();
boolean[] visited = new boolean[n];
// DFS on original graph
for (int i = 0; i < n; i++) {
if (!visited[i]) {
dfs(i, visited);
}
}
// Create transpose of the graph
Map<Integer, List<Integer>> transpose = new HashMap<>();
for (Map.Entry<Integer, List<Integer>> entry : graph.entrySet()) {
for (int neighbor : entry.getValue()) {
if (!transpose.containsKey(neighbor)) {
transpose.put(neighbor, new ArrayList<>());
}
transpose.get(neighbor).add(entry.getKey());
}
}
// DFS on transpose graph
visited = new boolean[n];
while (!postOrder.isEmpty()) {
int node = postOrder.pop();
if (!visited[node]) {
dfsOnTranspose(transpose, node, visited, new ArrayList<>());
sccList.add(visitedSubgraph);
}
}
return sccList;
}
private void dfs(int node, boolean[] visited) {
visited[node] = true;
for (int neighbor : graph.getOrDefault(node, Collections.emptyList())) {
if (!visited[neighbor]) {
dfs(neighbor, visited);
}
}
postOrder.push(node);
}
private void dfsOnTranspose(Map<Integer, List<Integer>> transpose, int node, boolean[] visited, List<Integer> visitedSubgraph) {
visited[node] = true;
visitedSubgraph.add(node);
for (int neighbor : transpose.getOrDefault(node, Collections.emptyList())) {
if (!visited[neighbor]) {
dfsOnTranspose(transpose, neighbor, visited, visitedSubgraph);
}
}
}
public static void main(String[] args) {
SocialCircles solution = new SocialCircles();
int n = 5;
Map<Integer, List<Integer>> graph = new HashMap<>();
graph.put(1, Collections.singletonList(2));
graph.put(2, Arrays.asList(1, 3));
graph.put(3, Collections.singletonList(2));
graph.put(4, Collections.singletonList(5));
graph.put(5, Collections.singletonList(4));
List<List<Integer>> result = solution.findCircles(n, graph);
System.out.println("Social circles: " + result);
}
}
题目 2:课程的先决条件
描述:
给定 n 门课程和一个先决条件列表,请你检查是否存在课程之间的循环依赖,如果存在则返回 true;否则,返回 false。
示例:
输入:n = 4, prerequisites = [[1, 0], [2, 0], [3, 1], [3, 2]]
输出:false
Java 源码:
import java.util.*;
public class CoursePrerequisites {
public boolean hasCycle(int n, int[][] prerequisites) {
Map<Integer, List<Integer>> graph = new HashMap<>();
int[] inDegree = new int[n];
// Build the graph and calculate in-degrees
for (int[] edge : prerequisites) {
graph.computeIfAbsent(edge[1], k -> new ArrayList<>()).add(edge[0]);
inDegree[edge[0]]++;
}
// Use a queue to perform BFS on the graph
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
}
}
int visitedCount = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
int course = queue.poll();
visitedCount++;
for (int prereq : graph.get(course)) {
if (--inDegree[prereq] == 0) {
queue.offer(prereq);
}
}
}
}
return visitedCount != n;
}
public static void main(String[] args) {
CoursePrerequisites solution = new CoursePrerequisites();
int n = 4;
int[][] prerequisites = {{1, 0}, [2, 0], [3, 1], [3, 2]};
boolean result = solution.hasCycle(n, prerequisites);
System.out.println("Does the course have a cycle of prerequisites? " + result);
}
}
题目 3:构建离线图
描述:
给定一个包含 n 个顶点和 m 个边的有向图,边具有权重。设计一个算法,根据给定的边权重,构建一个离线图,使得在离线图中,任意两个强连通分量之间的边的权重之和最小。
示例:
输入:n = 4, edges = [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 1, 6]]
输出:最小权重和
Java 源码:
import java.util.*;
public class OfflineGraph {
public int buildOfflineGraph(int n, int[][] edges) {
Map<Integer, List<int[]>> graph = new HashMap<>();
int[] inDegree = new int[n];
int totalWeight = 0;
// Build the graph and calculate in-degrees
for (int[] edge : edges) {
graph.computeIfAbsent(edge[1], k -> new ArrayList<>()).add(new int[]{edge[0], edge[2]});
inDegree[edge[0]]++;
totalWeight += edge[2];
}
// Use a queue to perform BFS on the graph
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
}
}
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
int vertex = queue.poll();
for (int[] edge : graph.get(vertex)) {
if (--inDegree[edge[0]] == 0) {
queue.offer(edge[0]);
}
}
}
}
// Calculate the remaining weight considering SCCs
// (This part requires a more complex algorithm and is not fully implemented here)
// ...
return totalWeight; // Placeholder for the minimum weight sum
}
public static void main(String[] args) {
OfflineGraph solution = new OfflineGraph();
int n = 4;
int[][] edges = {{1, 2, 3}, [2, 3, 4}, [3, 4, 5}, [4, 1, 6]];
int result = solution.buildOfflineGraph(n, edges);
System.out.println("Minimum weight sum of the offline graph: " + result);
}
}
这些题目和源码展示了强连通分量在图算法中的应用。在面试中,能够根据问题的特点选择合适的算法并实现其解决方案是非常重要的。希望这些示例能够帮助你更好地准备面试!