本文已参与「新人创作礼」活动,一起开启掘金创作之路。
参考阅读:blog.csdn.net/lhl11242810…
如何理解Kosaraju算法? - 简致的回答 - 知乎
www.zhihu.com/question/58…
Kosaraju算法步骤:
- 用DFS后序遍历原图的反转图,得到后序遍历顺序
- 用得到的后序遍历顺序对原图DFS
代码实现
栈和图的定义
const int MaxV = 100;
/*定义栈*/
typedef struct SNode{
string *Data;
int Top;
int MaxSize;
} * Stack;
/*定义边*/
typedef struct ENode{
int v1, v2;
string va, vb;
} * Edge;
/*定义邻接点*/
struct AdjVNode{
int AdjV; //顶点int型编号
AdjVNode *Next;
};
/*定义邻接表头*/
typedef struct VNode{
AdjVNode *EdgeFirst;
string Data;
bool tag;
} AdjList[MaxV];
/*定义图*/
typedef struct GNode{
int Nv, Ne;
AdjList L;
} * LGraph;
对栈需要的操作
/*创造一个新的栈*/
Stack CreateStack(int MaxSize)
{
Stack S = new SNode;
S->Data = new string[MaxSize];
S->MaxSize = MaxSize;
S->Top = -1;
return S;
}
/*将x压入栈*/
void Push(Stack S, string x)
{
S->Data[++S->Top] = x;
}
/*返回元素x是否在栈中位置, 若不在返回-1*/
int FindS(Stack S, string x)
{
for (int i = S->Top; i >= 0; i--)
if(S->Data[i] == x)
return i;
return -1;
}
/*将边由string型加入对应的int型顺序编号*/
void Edge_Code(Edge E, Stack S)
{
int k = FindS(S, E->va);
if(k < 0) //如果顶点不在栈中, 压入栈
{
Push(S, E->va);
E->v1 = S->Top; //得到新的v1的编号
}
else
E->v1 = k; //得到已有v1的编号
k = FindS(S, E->vb);
if (k < 0) //如果顶点不在栈中, 压入栈
{
Push(S, E->vb);
E->v2 = S->Top; //得到新的v2的编号
}
else
E->v2 = k; //得到已有v2的编号
}
图的操作
/*创造新的无边图*/
LGraph CreateGraph(int Nv)
{
LGraph G = new GNode();
G->Nv = Nv;
return G;
}
/*边的插入*/
void InsertEdge(LGraph G, Edge E)
{
AdjVNode *V = new AdjVNode;
V->AdjV = E->v2;
V->Next = G->L[E->v1].EdgeFirst;
G->L[E->v1].EdgeFirst = V;
}
/*键入建立图*/
LGraph BuildGraph()
{
int Nv;
cin >> Nv;
LGraph G = CreateGraph(Nv);
cin >> G->Ne;
Edge E = new ENode;
Stack S = CreateStack(G->Nv);
//使用栈来储存顶点数据,每输入一个顶点, 判断之前有没有输入过, 没有就压入栈返回栈顶下标, 有就返回在栈中的元素的位置下标
for (int i = 0; i < G->Ne; i++)
{
cin >> E->va >> E->vb;
Edge_Code(E, S); //由string得到int编号
InsertEdge(G, E); //插入边
G->L[E->v1].Data = E->va; //顶点存的数据为字符编号
G->L[E->v2].Data = E->vb;
}
delete E;
delete[] S->Data;
delete S;
return G;
}
/*删除图*/
void DeleteGraph(LGraph G)
{
AdjVNode *V;
for (int i = 0; i < G->Nv; i++)
{
while (G->L[i].EdgeFirst)
{
V = G->L[i].EdgeFirst;
G->L[i].EdgeFirst = V->Next;
delete V;
}
}
delete G;
}
输出图的物理结构
/*遍历输出图的邻接表图的物理结构*/
void ShowGraph(LGraph G)
{
AdjVNode *V;
for (int i = 0; i < G->Nv; i++)
{
V = G->L[i].EdgeFirst;
cout << G->L[i].Data << "->";
while (V)
{
cout << G->L[V->AdjV].Data << "->";
V = V->Next;
}
cout << "NULL" << endl;
}
}
得到图G的倒置图Gr
/*返回图G的倒置图Gr*/
LGraph ReverseGraph(LGraph G)
{
LGraph Gr = CreateGraph(G->Nv);
Edge E = new ENode;
AdjVNode *V;
for (int i = 0; i < G->Nv; i++)
{
Gr->L[i].Data = G->L[i].Data; //先复制顶点的string数据
V = G->L[i].EdgeFirst;
E->v2 = i;
while (V)
{
E->v1 = V->AdjV;
InsertEdge(Gr, E);
V = V->Next;
}
}
delete E;
return Gr;
}
Kosaraju算法
/*找出字符x在图G中的编号*/
int FindG(LGraph G, string x)
{
for (int i = 0; i < G->Nv; i++)
if (G->L[i].Data == x)
return i;
return -1;
}
void DFS(LGraph G, int u, Stack S)
{
G->L[u].tag = true;
AdjVNode *V = G->L[u].EdgeFirst;
while (V)
{
if(!G->L[V->AdjV].tag)
DFS(G, V->AdjV, S);
V = V->Next;
}
Push(S, G->L[u].Data);
}
void Kosaraju(LGraph G)
{
Stack S;
int *reverse = new int[G->Nv];
/*这里得到Gr图, 并输出原图和倒置图的物理结构*/
LGraph Gr = ReverseGraph(G);
cout << "原图: " << endl;
ShowGraph(G);
cout << "倒置图" << endl;
ShowGraph(Gr);
S = CreateStack(Gr->Nv); //建立新栈, 用于储存DFS后序遍历顺序
for (int i = 0; i < Gr->Nv; i++) //对Gr进行DFS得出后序遍历,标记一下
if(!Gr->L[i].tag)
DFS(Gr, i, S);
for (int i = 0; i < Gr->Nv; i++)
reverse[i] = FindG(Gr, S->Data[i]);
delete[] S->Data;
delete S;
int start = 0;
S = CreateStack(G->Nv);
cout << "该图包含以下强连通分量:" << endl;
for (int i = G->Nv - 1; i >= 0; i--)
{
if (!G->L[reverse[i]].tag) //对每个顶点DFS,都会把该顶点所在的强连通分量遍历完才退出DFS
{
DFS(G, reverse[i], S);
for (int i = start; i <= S->Top; i++)
cout << S->Data[i] << " ";
cout << endl;
//cout << S->Data[S->Top] << endl;
start = S->Top + 1;
}
}
DeleteGraph(G);
DeleteGraph(Gr);
delete[] reverse;
delete[] S->Data;
delete S;
}
程序运行
输入
13 22
0 1
0 5
2 0
2 3
3 2
3 5
4 2
4 3
5 4
6 0
6 4
6 9
7 6
7 8
8 7
8 9
9 10
9 11
10 12
11 4
11 12
12 9
运行结果