在调用dfs的过程中,几种添加顶点到集合的顺序。一共有四种顺序:
- Pre-Order,在递归调用dfs之前将当前顶点添加到queue中
- Reverse Pre-Order,在递归调用dfs之前将当前顶点添加到stack中
- Post-Order,在递归调用dfs之后将当前顶点添加到queue中
- Reverse Post-Order,在递归调用dfs之后将当前顶点添加到stack中
最后一种的用途最广,至少目前看来是这样,比如步骤2-a以及拓扑排序中,都是利用的Reverse Post-Order来获取顶点集合。
Kosaraju的主要步骤:
- 对G求解Reverse Post-Order,即上文中的”伪拓扑排序“
- 对G进行转置得到GR
- 按照第一步得到的集合中顶点出现的顺序,对GR调用DFS得到若干颗搜索树
- 每一颗搜索树就代表了一个强连通分量
#include <stdlib.h>
#include <iostream>
#include <stack>
#include <queue>
using namespace std;
struct node /* 图顶点结构定义 */
{
int vertex; /* 顶点数据信息 */
node *nextnode; /* 指下一顶点的指标 */
};
const int vertexnum = 4; /*顶点的数目*/
const int instance = 4;
typedef struct node *graph; /* 图形的结构新型态 */
node head[vertexnum+1]; /* 图形顶点数组 */
node reversehead[vertexnum+1]; /* 图形顶点数组 */
int visited[vertexnum+1]; /* 遍历标记数组 */
int id[vertexnum+1]; /*标记连通分量的编号*/
//int edgeto[vertexnum+1]; /*标记节点前继*/
//int onstack[vertexnum+1]; /*标记栈中节点*/
int count = 0; /*连通分量编号*/
int edgenum = 0; /*边的数目*/
queue<int> pre;
queue<int> post;
stack<int> reversepost;
/********************根据已有的信息建立邻接表********************/
void creategraph(int node[instance][2],int num)/*num指的是图的边数*/
{
graph newnode; /*指向新节点的指针定义*/
graph ptr;
int from; /* 边的起点 */
int to; /* 边的终点 */
int i;
for ( i = 0; i < num; i++ ) /* 读取边线信息,插入邻接表*/
{
from = node[i][0]; /* 边线的起点 */
to = node[i][1]; /* 边线的终点 */
/* 建立新顶点 */
newnode = ( graph ) malloc(sizeof(struct node));
newnode->vertex = to; /* 建立顶点内容 */
newnode->nextnode = NULL; /* 设定指标初值 */
ptr = &(head[from]); /* 顶点位置 */
while ( ptr->nextnode != NULL ) /* 遍历至链表尾 */
ptr = ptr->nextnode; /* 下一个顶点 */
ptr->nextnode = newnode; /* 插入节点 */
}
}
void createreversegraph(int node[instance][2],int num)/*num指的是图的边数*/
{
graph newnode; /*指向新节点的指针定义*/
graph ptr;
int from; /* 边的起点 */
int to; /* 边的终点 */
int i;
for ( i = 0; i < num; i++ ) /* 读取边线信息,插入邻接表*/
{
to = node[i][0]; /* 边线的起点 */
from = node[i][1]; /* 边线的终点 */
/* 建立新顶点 */
newnode = ( graph ) malloc(sizeof(struct node));
newnode->vertex = to; /* 建立顶点内容 */
newnode->nextnode = NULL; /* 设定指标初值 */
ptr = &(reversehead[from]); /* 顶点位置 */
while ( ptr->nextnode != NULL ) /* 遍历至链表尾 */
ptr = ptr->nextnode; /* 下一个顶点 */
ptr->nextnode = newnode; /* 插入节点 */
}
}
void dfs(int current){
visited[current] = 1;
pre.push(current);
cout<<current<<endl;
node *p;
p = head[current].nextnode;
while(p != NULL){
if(visited[p->vertex] == 0){
visited[p->vertex] = 1;
dfs(p->vertex);
}
p = p->nextnode;
}
post.push(current);
reversepost.push(current);
}
void kosarajuSCCdfs(int current){
visited[current] = 1;
id[current] = count;
node *p;
p = reversehead[current].nextnode;
cout<<current<<" ";
while(p != NULL){
if(visited[p->vertex] == 0){
visited[p->vertex] = 1;
kosarajuSCCdfs(p->vertex);
}
p = p->nextnode;
}
}
void kosarajuSCC(){
int current;
for(int i=1;i <= vertexnum;i++){
visited[i] = 0;
}
count = 0;
while(!reversepost.empty()){
current = reversepost.top();
reversepost.pop();
if(visited[current] == 0){
kosarajuSCCdfs(current);
cout<<endl;
count++;
}
}
}
/****************************** 主程序******************************/
int main()
{
graph ptr;
/* int node[instance][2] = { {1, 2}, {2, 1},
{1, 3}, {3, 1},
{1, 4}, {4, 1},
{2, 5}, {5, 2},
{2, 6}, {6, 2},
{3, 7}, {7, 3},
{4, 7}, {4, 4},
{5, 8}, {8, 5},
{6, 7}, {7, 6},
{7, 8}, {8, 7} };
*/
int node[instance][2] = {
{1, 2}, {2, 3},{3,4},{4,2}
};
int i;
edgenum = 4;
//clrscr();
for ( i = 1; i <= vertexnum; i++ ) /* 顶点数组初始化 */
{
head[i].vertex = i; /* 设定顶点值 */
head[i].nextnode = NULL; /* 指针为空 */
visited[i] = 0; /* 设定遍历初始标志 */
//onstack[i] = 0;
}
creategraph(node,instance); /* 建立邻接表 */
createreversegraph(node,instance); /* 建立邻接表 */
cout<<"Content of the gragh's ADlist is:\n";
for ( i = 1; i <= vertexnum; i++ )
{
cout<<"vertex"<<head[i].vertex<<" ->";/* 顶点值 */
ptr = head[i].nextnode; /* 顶点位置 */
while ( ptr != NULL ) /* 遍历至链表尾 */
{
cout<<" "<<ptr->vertex<<" "; /* 印出顶点内容 */
ptr = ptr->nextnode; /* 下一个顶点 */
}
cout<<endl; /* 换行 */
}
cout<<"\nThe end of the dfs are:\n";
for(i=1;i<=vertexnum;i++){
if(visited[i] == 0){
dfs(i); /* 打印输出遍历过程 */
count++;
}
}
cout<<endl;
kosarajuSCC();
}
对kosaraju的证明,这个版本比较清晰,来自http://blog.csdn.net/dm_vincent/article/details/8554244
证明的目标,就是最后一步 --- 每一颗搜索树代表的就是一个强连通分量
证明:设在图GR中,调用DFS(s)能够到达顶点v,那么顶点s和v是强连通的。
两个顶点如果是强连通的,那么彼此之间都有一条路径可达,因为DFS(s)能够达到顶点v,因此从s到v的路径必然存在。现在关键就是需要证明在GR中从v到s也是存在一条路径的,也就是要证明在G中存在s到v的一条路径。
而之所以DFS(s)能够在DFS(v)之前被调用,是因为在对G获取ReversePost-Order序列时,s出现在v之前,这也就意味着,v是在s之前加入该序列的(因为该序列使用栈作为数据结构,先加入的反而会在序列的后面)。因此根据DFS调用的递归性质,DFS(v)应该在DFS(s)之前返回,而有两种情形满足该条件:
- DFS(v) START -> DFS(v) END -> DFS(s) START -> DFS(s) END
- DFS(s) START -> DFS(v) START -> DFS(v) END -> DFS(s) END
是因为而根据目前的已知条件,GR中存在一条s到v的路径,即意味着G中存在一条v到s的路径,而在第一种情形下,调用DFS(v)却没能在它返回前递归调用DFS(s),这是和G中存在v到s的路径相矛盾的,因此不可取。故情形二为唯一符合逻辑的调用过程。而根据DFS(s) START -> DFS(v) START可以推导出从s到v存在一条路径。
所以从s到v以及v到s都有路径可达,证明完毕。
复杂度分析:
根据上面总结的Kosaraju算法关键步骤,不难得出,该算法需要对图进行两次DFS,以及一次图的转置。所以复杂度为O(V+E)。
参考:
http://blog.csdn.net/dm_vincent/article/details/8554244
http://blog.csdn.net/michealtx/article/details/8233814
http://www.cnblogs.com/luweiseu/archive/2012/07/14/2591370.html
http://www.cnblogs.com/suoloveyou/archive/2012/05/06/2486589.html