强连通分量(Strongly Connected Components,简称SCCs)是图论中的一个概念,它指的是图中的一组顶点,这些顶点满足从组内任意顶点都可达组内其他顶点的条件。在有向图中,一个顶点的强连通分量是该顶点自身以及所有它可以到达和可以到达它的顶点的集合。
强连通分量的算法:
-
Kosaraju算法:这是一种基于深度优先搜索(DFS)的算法,用于找到有向图中的所有强连通分量。算法分为两个主要步骤:
- 首先,对原始图执行一次DFS,记录下每个顶点的完成顺序,并建立一个栈,将完成顺序逆序的顶点压入栈中。
- 然后,对原始图的逆图(即所有边的方向都反转的图)执行DFS,每次从栈顶取出一个顶点作为起始点,这样每次DFS会找到一个强连通分量。
-
Tarjan算法:另一种经典的算法,它同样使用DFS遍历图,但与Kosaraju算法不同,Tarjan算法在同一个图中完成强连通分量的寻找。算法为每个顶点分配三个数值:
- disc[]:发现时间,即DFS遍历时第一次遇到顶点的时间戳。
- low[]:通过当前顶点可达的最早发现时间的顶点的时间戳。
- onStack[]:标记当前顶点是否在DFS栈上。
强连通分量的Java实现(Kosaraju算法):
import java.util.*;
import java.io.*;
class Graph {
private List<List<Integer>> adj;
private int time;
private Stack<Integer> postOrder;
public Graph(int v) {
adj = new ArrayList<>(v);
for (int i = 0; i < v; i++) adj.add(new ArrayList<>());
time = 0;
postOrder = new Stack<>();
}
public void addEdge(int v, int w) {
adj.get(v).add(w);
}
public void dfs(int v, boolean visited[]) {
visited[v] = true;
for (int i : adj.get(v)) {
if (!visited[i]) {
dfs(i, visited);
}
}
postOrder.push(v);
}
public List<List<Integer>> getSCCs() {
List<List<Integer>> scc = new ArrayList<>();
boolean[] visited = new boolean[adj.size()];
// DFS on original graph
for (int i = 0; i < adj.size(); i++) {
if (!visited[i]) {
dfs(i, visited);
}
}
Graph transpose = new Graph(adj.size());
for (int i = 0; i < adj.size(); i++) {
for (int j : adj.get(i)) {
transpose.adj.get(j).add(i);
}
}
// DFS on transpose graph
while (!postOrder.isEmpty()) {
int v = postOrder.pop();
if (!visited[v]) {
List<Integer> component = new ArrayList<>();
dfsOnTranspose(transpose, v, visited, component);
scc.add(component);
}
}
return scc;
}
private void dfsOnTranspose(Graph transpose, int v, boolean[] visited, List<Integer> component) {
visited[v] = true;
component.add(v);
for (int i : transpose.adj.get(v)) {
if (!visited[i]) {
dfsOnTranspose(transpose, i, visited, component);
}
}
}
}
public class Main {
public static void main(String[] args) {
Graph g = new Graph(5);
g.addEdge(1, 0);
g.addEdge(0, 2);
g.addEdge(2, 1);
g.addEdge(0, 3);
g.addEdge(3, 4);
List<List<Integer>> scc = g.getSCCs();
for (List<Integer> component : scc) {
System.out.println(component);
}
}
}
在面试中,强连通分量是一个重要的图算法问题,它考察应聘者对图算法的理解和算法实现能力。通过实现强连通分量的算法,可以展示你对深度优先搜索和图算法的掌握程度。希望这些知识点和示例代码能够帮助你更好地准备面试!强连通分量(SCC)是图算法中的一个高级话题,经常在大厂的面试中出现。以下是三道可能出现在大厂面试中的与强连通分量相关的编程题目,以及相应的Java源码实现。
题目 1:找出图中的所有强连通分量
描述:
给定一个有向图,编写一个程序找出图中所有的强连通分量。
示例:
输入:图的邻接表表示,例如:
{
1: [2, 3],
2: [4],
3: [1, 4],
4: []
}
输出:[[4], [0, 2, 3], [1]]
Java 源码(使用Kosaraju算法):
import java.util.*;
public class AllSCC {
List<List<Integer>> sccList;
Stack<Integer> postOrder;
int time;
public List<List<Integer>> scc(int n, List<List<Integer>[]> graph) {
sccList = new ArrayList<>();
postOrder = new Stack<>();
time = 0;
// DFS on original graph
boolean[] visited = new boolean[n];
for (int i = 0; i < n; i++) {
if (!visited[i]) {
dfs(graph, i, visited);
}
}
// Create transpose of the graph
List<List<Integer>[]> transpose = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
transpose.add(new ArrayList<>());
}
for (int i = 0; i < n; i++) {
for (int j : graph.get(i)[0]) {
transpose.get(j).add(new Integer[]{i, graph.get(i)[1].get(j)});
}
}
// DFS on transposed graph to find SCCs
visited = new boolean[n];
for (int i = 0; i < n; i++) {
if (!visited[i]) {
dfsOnTranspose(transpose, i, visited, new ArrayList<>());
}
}
return sccList;
}
private void dfs(List<List<Integer>[]> graph, int v, boolean[] visited) {
visited[v] = true;
for (int i : graph.get(v)[0]) {
if (!visited[i]) {
dfs(graph, i, visited);
}
}
postOrder.push(v);
}
private void dfsOnTranspose(List<List<Integer>[]> transpose, int v, boolean[] visited, List<Integer> component) {
visited[v] = true;
component.add(v);
for (Integer[] edge : transpose.get(v)) {
int to = edge[0];
if (!visited[to]) {
dfsOnTranspose(transpose, to, visited, component);
}
}
if (!component.isEmpty()) {
sccList.add(component);
component.clear();
}
}
public static void main(String[] args) {
AllSCC scc = new AllSCC();
int n = 4;
List<List<Integer>[]> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new List[]{new ArrayList<>(), new HashMap<>()});
}
graph.get(0)[0].add(1);
graph.get(0)[0].add(2);
graph.get(1)[0].add(3);
graph.get(2)[0].add(0);
graph.get(2)[0].add(3);
graph.get(3)[0].add(0);
List<List<Integer>> result = scc.scc(n, graph);
for (List<Integer> component : result) {
System.out.println(component);
}
}
}
题目 2:检查图中是否存在环
描述:
给定一个有向图,检查图中是否存在环。
示例:
输入:图的邻接表表示,例如:
{
1: [2],
2: [3],
3: [1]
}
输出:true
Java 源码:
import java.util.*;
public class GraphCycle {
public boolean hasCycle(int n, List<List<Integer>[]> graph) {
boolean[] visited = new boolean[n];
boolean[] recStack = new boolean[n];
for (int i = 0; i < n; i++) {
if (hasCycleUtil(i, visited, recStack, graph)) {
return true;
}
}
return false;
}
private boolean hasCycleUtil(int v, boolean[] visited, boolean[] recStack, List<List<Integer>[]> graph) {
if (recStack[v]) {
return true;
}
if (visited[v]) {
return false;
}
visited[v] = true;
recStack[v] = true;
for (int i : graph.get(v)[0]) {
if (hasCycleUtil(i, visited, recStack, graph)) {
return true;
}
}
recStack[v] = false;
return false;
}
public static void main(String[] args) {
GraphCycle cycle = new GraphCycle();
int n = 3;
List<List<Integer>[]> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new List[]{new ArrayList<>(), new HashMap<>()});
}
graph.get(0)[0].add(1);
graph.get(1)[0].add(2);
graph.get(2)[0].add(0);
boolean result = cycle.hasCycle(n, graph);
System.out.println("Graph contains cycle: " + result);
}
}
题目 3:计算图中的三角形数量
描述:
给定一个无向图,计算图中三角形的数量。
示例:
输入:图的邻接表表示,例如:
{
1: [2, 3],
2: [1, 3],
3: [1, 2]
}
输出:1
Java 源码:
import java.util.*;
public class CountTriangles {
public int countTriangles(int n, List<List<Integer>[]> graph) {
int count = 0;
int[] disc = new int[n];
int[] low = new int[n];
for (int i = 0; i < n; i++) {
if (!visited[i]) {
DFS(i, -1, disc, low, graph, visited, count);
}
}
return count;
}
private void DFS(int v, int parent, int[] disc, int[] low, List<List<Integer>[]> graph, boolean[] visited, int count) {
visited[v] = true;
disc[v] = low[v] = ++time;
for (int i : graph.get(v)[0]) {
if (!visited[i]) {
DFS(i, v, disc, low, graph, visited, count);
low[v] = Math.min(low[v], low[i]);
if (disc[v] < low[i]) {
count++;
}
} else if (i != parent) {
low[v] = Math.min(low[v], disc[i]);
}
}
}
// Additional fields and methods for the algorithm
private boolean[] visited;
private int time = 0;
public static void main(String[] args) {
CountTriangles triangles = new CountTriangles();
int n = 3;
List<List<Integer>[]> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new List[]{new ArrayList<>(), new HashMap<>()});
}
graph.get(0)[0].add(1);
graph.get(1)[0].add(0);
graph.get(0)[0].add(2);
graph.get(2)[0].add(0);
graph.get(1)[0].add(2);
graph.get(2)[0].add(1);
int result = triangles.countTriangles(n, graph);
System.out.println("Number of triangles in the graph: " + result);
}
}
这些题目和源码展示了强连通分量在图算法中的应用。在面试中,能够根据问题的特点选择合适的算法并实现其解决方案是非常重要的。希望这些示例能够帮助你更好地准备面试!