对于无向图G: 如果两个顶点之间有一条路径连着, 我们就说这两个顶点是连通的(无向图的边无方向性, 只要有边连着就是连通的)。 如果满足图中的任意两个顶点都是连通的, 我们就说图是连通图。 所谓的连通分量, 就是无向图中的极大连通子图。 对于连通图, 只有一个连通分量, 就是它本身。 非联通的无向图有多个连通分量。
如下图, 该无向图的连通分量分别是: (A L M J B F C), (D E), (G, K, H, I)。
当对无向图进行遍历的时候, 如果该无向图是连通图, 只需从图的任一个顶点出发, 进行DFS(或者BFS), 便可以遍历图中所有的顶点。 对于非连通的无向图, 则需要从多个顶点出发进行搜索。 而每一次从一个新的起始点出发进行搜索得到的顶点访问序列恰为各个连通分量的顶点集合。 不难看出,对无向图进行的DFS调用的次数(指的是选择新的起始点进行DFS)就是给定无向图的连通分量的个数。
对于有向图, 对应的概念强连通分量。
若对于V中任意两个不同的顶点u和v,都存在从u到v以及从v到u的路径(注意有向图的边是有方向的),则称G是强连通图(Strongly Connected Graph)。相应地有强连通分量(Strongly Connected Component)的概念。强连通图只有一个强连通分量,即是其自身;非强连通的有向图有多个强连通分量。
寻找有向图的强连通分量的算法用到了图G = (V, E)的转置。 所谓的转置就是把有向图的边进行均做反向处理。
假如图G使用的是邻接链表表示的, 那么求取G的转置图像transpose(G)所需的时间就是线性时间O(V + E)。
下面伪代码, 通过使用两次深度优先搜索计算有向图G的强连通分量(一次对原始图进行DFS, 记录下结束访问的时间。 最后再对 原始图的转置 按照结束访问节点的时间的递减顺序进行一次DFS):
即包含如下三步:
(1)调用DFS(G)计算每一个节点的finishing time。
(2)计算图G的转置图
(3)按照所有节点的结束时间递减的顺序访问节点。 执行DFS.
(4)输出深度优先遍历获得的每一个树的节点, 这就是我们的强连通分量的节点集合。
下面, 我们举一个例子:
<span style="font-size:14px;">#include <iostream>
#include <list>
#include <stack>
using namespace std;
class Graph{
int V; // number of vertices
list<int> *adj; // array of adjacency list
// fills stack with vertices(in increasing oder of finishing time)
// the top element of the stack has the maximum finishing time
void fillOder(int v, bool visited[], stack<int>& Stack);
// a recursive function to print DFS starting from v
void DFSUtil(int v, bool visited[]);
public:
Graph(int V);
void addEdge(int v, int w);
// the main function that finds and prints strongly connected component
void printSCCs();
// function that returns the transpose of the graph
Graph getTranspose();
};
Graph::Graph(int V) {
this -> V = V; // number of edges
adj = new list<int>[V];
}
void Graph::DFSUtil(int v, bool visited[]) {
visited[v] = true; // doing depth first search from v
cout << v << " ";
// recursion for all the vertices adjacent to v
list<int>::iterator i;
for(i = adj[v].begin(); i != adj[v].end(); ++i) {
if(!visited[*i]) {
DFSUtil(*i, visited);
}
}
}
Graph Graph::getTranspose() {
Graph g(V);
for(int v = 0; v < V; ++v) {
// recursion for all the vertices adjacent to this vertex
list<int>::iterator i;
for(i = adj[v].begin(); i != adj[v].end(); ++i) {
g.adj[*i].push_back(v);
}
}
return g;
}
void Graph::addEdge(int v, int w) {
adj[v].push_back(w); // add w to v's list
}
void Graph::fillOder(int v, bool visited[], stack<int>& Stack) {
// mark current node as visited and print it
visited[v] = true;
// recursions for all the vertices adjacent to the vertex v
list<int>::iterator i;
for(i = adj[v].begin(); i != adj[v].end(); ++i) {
if(!visited[*i])
fillOder(*i, visited, Stack);
}
// all vertices reachable from v are now processed and push it to the stack
Stack.push(v);
}
void Graph::printSCCs() {
stack<int> Stack;
// mark all the vertices as not visited(for first DFS)
bool *visited = new bool[V];
for(int i = 0; i < V; ++i) {
visited[i] = false;
}
// fill vertices in stack according to their finishing time
for(int i = 0; i < V; ++i) {
if(visited[i] == false) {
fillOder(i, visited, Stack);
}
}
// create a reversed graph
Graph gr = getTranspose();
// mark all the vertices not visited(for second DFS)
// Mark all the vertices as not visited (For second DFS)
for(int i = 0; i < V; i++)
visited[i] = false;
// Now process all vertices in order defined by Stack
while (Stack.empty() == false)
{
// Pop a vertex from stack
int v = Stack.top();
Stack.pop();
// Print Strongly connected component of the popped vertex
if (visited[v] == false)
{
gr.DFSUtil(v, visited);
cout << endl;
}
}
}
// Driver program to test above functions
int main()
{
// Create a graph given in the above diagram
Graph g(5);
g.addEdge(1, 0);
g.addEdge(0, 2);
g.addEdge(2, 1);
g.addEdge(0, 3);
g.addEdge(3, 4);
cout << "Following are strongly connected components in given graph \n";
g.printSCCs();
return 0;
}
</span>
运行结果如下所示:
介绍完上述问题后, 我们看一个强连通分量的经典题目King's Queen:
Description
So the king asked his wizard to find for each of his sons the girl he liked, so that he could marry her. And the king's wizard did it -- for each son the girl that he could marry was chosen, so that he liked this girl and, of course, each beautiful girl had to marry only one of the king's sons.
However, the king looked at the list and said: "I like the list you have made, but I am not completely satisfied. For each son I would like to know all the girls that he can marry. Of course, after he marries any of those girls, for each other son you must still be able to choose the girl he likes to marry."
The problem the king wanted the wizard to solve had become too hard for him. You must save wizard's head by solving this problem.
Input
The last line of the case contains the original list the wizard had made -- N different integer numbers: for each son the number of the girl he would marry in compliance with this list. It is guaranteed that the list is correct, that is, each son likes the girl he must marry according to this list.
Output
Sample Input
4
2 1 2
2 1 2
2 2 3
2 3 4
1 2 3 4
Sample Output
2 1 2
2 1 2
1 3
1 4
大致意思是N个男的(编号是1, ... N), N个女的。 男的比较花心, 可以喜欢多个女的。 但是最后结婚的时候, 必须是只能从他喜欢的女子中选一个, 并且还有保证自己选择不会使得其他的男的无喜欢的女子可选, 这样就皆大欢喜了。 一旦N个男的都有了自己喜欢的女的去结婚, 那么就固定下来了。 但是我们不是给出一个配对方案, 而是给出所有让人皆大欢喜的配对方案。 够乱的。
其实这是一道考察了二部图 + 最强连通分量的混合题。
关键是求最强连通分量。
首先第一步就是建图。
N个男的, N个女的, 就相当于大小2N个顶点的顶点集合。 组成L, R的二部图。 接下来就是建造边了。
我们假设:
如果A(男的)喜欢B(女的), 就连一条从A到B的有向边(A, B)。
根据巫师给出的list, 如果B女嫁给了A, 我们就建一条从B到A的边。
对于上述的sampe input建的图如下:
然后求出所建图的最强连通分量。 位于同一个最强连通分量的顶点可以自由混搭。 例如, 王子1, 王子2 在一个最强连通分量中, 表示这个强连通分量的女的都是两个人喜欢的女的。 当然王子1随便选一个女的结婚, 也不会造成与其处于同一个强连通分量的王子无别的女的可选。
相关程序如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=20000 + 100; // 200000边的最大可能数
const int N=4200; // 2000 * 2 + 200 表示最多2000个男的, 2000个女的
struct node {
int v;
int next;
}edge[M];
// head[N]记录的是N个女子
int head[N], num; // num 记录的是边的个数
bool vis[N],instack[N];
int stack[10*N],belong[N],top,bcnt,dindex,dep[N],low[N];
int n,m;
void init() {
for(int i=0; i <= 2*n+5; i++) // 因为总共有2n个节点
head[i]=-1;
num=0; // 记录边的个数, 给边进行编号
}
void addege(int u,int v) { // 添加边(u, v)
edge[num].v=v; // 边num 的终点
edge[num].next=head[u]; // 起点为u, -1表示没有下一条边, 1 表示有下一条边
head[u]=num++; //head[u]记录下起点为u上的边数目
}
// 从节点i开始进行DFS
void dfs(int i) {
int j, v;
dep[i]=low[i]=++dindex; // 表示搜索时第i个顶点的访问时间
instack[i]=1; // 把第i个顶点入栈
stack[++top]=i; //
for(j=head[i];j!=-1;j=edge[j].next) {
v=edge[j].v;
if(!dep[v]) {
dfs(v);
if(low[v]<low[i])
low[i]=low[v];
}
else if(instack[v] && dep[v]<low[i]) // 只有强连通分量的根节点的dep才能与low
low[i]=dep[v];
}
if(dep[i]==low[i]) { // 连通分量的根节点
bcnt++;
do
{
j=stack[top--];
instack[j]=0;
belong[j]=bcnt;
}while(j!=i);
}
}
void tarjan() {
int i;
top=bcnt=dindex=0;
memset(dep,0,sizeof(dep));
memset(vis,0,sizeof(vis));
for(i=1;i<=n;i++)
if(!dep[i]) {
dfs(i);
}
}
int rec[N];
void solve() {
int i,j,cnt;
for(i=1;i<=n;i++) {
cnt=0;
for(j=head[i];j!=-1;j=edge[j].next)
if(belong[edge[j].v]==belong[i])
rec[cnt++]=edge[j].v-n;
sort(rec,rec+cnt);
printf("%d",cnt);
for(j=0;j<cnt;j++)
printf(" %d",rec[j]);
printf("\n");
}
}
int main()
{
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
while((scanf("%d",&n))==1) {// 输入有n个王子
init();
int a,j; // a对应的是王子喜欢的姑娘数, j 对应王子a喜欢的女子
for(int i=1;i<=n;i++) { // 下标从1开始
scanf("%d",&a);
while(a--) { // 一直执行到a == 1即退出
scanf("%d",&j);
addege(i,j+n); // 连接王子i喜欢的姑娘之间的边
}
}
for(int i=1; i<=n; i++) { // 根据最后一行, 连接巫师给的完备匹配的边
scanf("%d",&j);
addege(j+n,i);
}
tarjan();
solve();
}
return 0;
}
运行结果: