图论——连通分量
连通分量介绍
对于上图很显然连通分量为1,对于下图连通分量个数为2
DFS计算连通分量
通过上一小节dfs遍历的过程我们知道依次dfs就是一个连通分量,因为dfs只有走到无路可走才会回退,所以我们只需要记录一下dfs次数即可,有如下代码
public class UndirectedGraphDFSCC {
private UndirectedGraph graph;
private boolean[] visited;
private int ccCount;//连通分量
public UndirectedGraphDFSCC(UndirectedGraph graph){
this.graph = graph;
visited = new boolean[graph.vertexNum()];
//可能有多个连通分量,所以得for
for(int v=0;v<graph.vertexNum();v++){
if(!visited[v]){
dfs(v);
ccCount++;
}
}
}
public int getCcCount(){
return ccCount;
}
private void dfs(int v){
visited[v] = true;
for(int w:graph.adj(v)) {
if(!visited[w]){
dfs(w);
}
}
}
public static void main(String[] args) {
UndirectedGraph graph = new UndirectedGraph("graph.txt");
System.out.println(graph);
UndirectedGraphDFSCC graphDFS = new UndirectedGraphDFSCC(graph);
System.out.println(graphDFS.getCcCount());
}
}
但是我们想知道的不仅仅是一个图有多少连通分量,而是每个连通分量都有哪些顶点
我们可以改变一下visited数组,改为int数组,如果数组里的值是-1,代表此下标代表的顶点未访问过,如果数组里的值是0,代表此下标代表的顶点是属于第一个连通分量的,如果数组里的值是1,代表此下标代表的顶点是属于第二个连通分量的,以此类推,有如下代码
public class UndirectedGraphCC {
private UndirectedGraph graph;
private int[] visited;
private int ccCount;//连通分量
public UndirectedGraphCC(UndirectedGraph graph){
this.graph = graph;
visited = new int[graph.vertexNum()];
for(int i=0;i<visited.length;i++){
//visited里面-1表示没有访问,0表示第一个联通分量,1表示第二个连通分量,以此类推
visited[i] = -1;
}
for(int v=0;v<graph.vertexNum();v++){
if(visited[v]==-1){
dfs(v,ccCount);
ccCount++;
}
}
}
private void dfs(int v,int ccId){
visited[v] = ccId;
for(int w:graph.adj(v)) {
if(visited[w]==-1){
dfs(w,ccId);
}
}
}
/**
* 返回所有的连通分量
* @return
*/
public List<Integer>[] components(){
List<Integer>[] res = new ArrayList[ccCount];
for(int i=0;i<ccCount;i++){
res[i] = new ArrayList<>();
}
for(int v=0;v<graph.vertexNum();v++){
res[visited[v]].add(v);
}
return res;
}
public boolean isConnected(int v,int w){
graph.validateVertex(v);
graph.validateVertex(w);
return visited[v]==visited[w];
}
public int getCcCount(){return ccCount;}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for(int v:visited){
sb.append(v+" ");
}
sb.append("\n连通分量:"+ccCount);
int index = 1;
for (List<Integer> list:components()){
sb.append("\n第"+index+++"个连通分量的顶点:");
for (int v:list) {
sb.append(v+" ");
}
}
return sb.toString();
}
public static void main(String[] args) {
UndirectedGraph graph = new UndirectedGraph("graph.txt");
System.out.println(graph);
UndirectedGraphCC graphCC = new UndirectedGraphCC(graph);
System.out.println(graphCC);
System.out.println(graphCC.isConnected(0, 6));
System.out.println(graphCC.isConnected(0, 5));
}
}
BFS计算连通分量
public class UndirectedGraphCCBFS {
private UndirectedGraph graph;
private int[] visited;
private int ccCount;
public UndirectedGraphCCBFS(UndirectedGraph graph){
this.graph = graph;
visited = new int[graph.vertexNum()];
for(int i=0;i<visited.length;i++){
//visited里面-1表示没有访问,0表示第一个联通分量,1表示第二个连通分量,以此类推
visited[i] = -1;
}
//多个联通分量必须for
for(int v=0;v<graph.vertexNum();v++){
if(visited[v]==-1){
bfs(v,ccCount);
ccCount++;
}
}
}
private void bfs(int v,int ccId){
Queue<Integer> queue = new LinkedList();
queue.offer(v);
visited[v] = ccId;
while(!queue.isEmpty()){
int w = queue.poll();
for(int u:graph.adj(w)){
if(visited[u]==-1){
visited[u] = ccId;
queue.offer(u);
}
}
}
}
/**
* 返回所有的连通分量
* @return
*/
public List<Integer>[] components(){
List<Integer>[] res = new ArrayList[ccCount];
for(int i=0;i<ccCount;i++){
res[i] = new ArrayList<>();
}
for(int v=0;v<graph.vertexNum();v++){
res[visited[v]].add(v);
}
return res;
}
public boolean isConnected(int v,int w){
graph.validateVertex(v);
graph.validateVertex(w);
return visited[v]==visited[w];
}
public int getCcCount() {
return ccCount;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for(int v:visited){
sb.append(v+" ");
}
sb.append("\n连通分量:"+ccCount);
int index = 1;
for (List<Integer> list:components()){
sb.append("\n第"+index+++"个连通分量的顶点:");
for (int v:list) {
sb.append(v+" ");
}
}
return sb.toString();
}
public static void main(String[] args) {
UndirectedGraph graph = new UndirectedGraph("graph.txt");
System.out.println(graph);
UndirectedGraphCCBFS graphBFS = new UndirectedGraphCCBFS(graph);
System.out.println(graphBFS);
}
}
图文件graph.txt
7 6
0 1
0 2
1 3
2 6
2 3
1 4
建图类
public class UndirectedGraph {
private int V;//顶点数
private int E;//边数
private TreeSet<Integer>[] adj;//邻接表,TreeSet数组存储
public UndirectedGraph(String filename){
File file = new File(filename);
try(Scanner scanner = new Scanner(file)){
V = scanner.nextInt();//顶点数
if(V<=0) throw new RuntimeException("顶点个数必须大于0");
adj = new TreeSet[V];
for(int i=0;i<V;i++){
adj[i] = new TreeSet<>();
}
E = scanner.nextInt();//边数
if(E<0) throw new RuntimeException("边数不能为负数");
for(int i=0;i<E;i++){
int a = scanner.nextInt();
validateVertex(a);
int b = scanner.nextInt();
validateVertex(b);
//自环边检测
if(a==b){
throw new RuntimeException("简单图不能包含自环边");
}
//平行边检测
if(adj[a].contains(b)){
throw new RuntimeException("简单图不能包含平行边");
}
adj[a].add(b);
adj[b].add(a);
}
}catch (IOException e){
e.printStackTrace();
}
}
public void validateVertex(int v){
if(v<0||v>=V){
throw new RuntimeException("顶点下标溢出");
}
}
public int vertexNum(){
return V;
}
public int edgeNum(){
return E;
}
public boolean hasEdge(int v,int w){
validateVertex(v);
validateVertex(w);
return adj[v].contains(w);
}
//邻接顶点
public Iterable<Integer> adj(int v){
validateVertex(v);
return adj[v];
}
//度
public int degree(int v){
validateVertex(v);
return adj[v].size();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("V = %d,E = %d\n",V,E));
for(int i=0;i<adj.length;i++){
sb.append(i+":");
for (Iterator<Integer> it = adj[i].iterator(); it.hasNext(); ) {
sb.append(it.next()+" ");
}
sb.append("\n");
}
return sb.toString();
}
public static void main(String[] args) {
UndirectedGraph graph = new UndirectedGraph("graph.txt");
System.out.println(graph);
}