本文已参与「新人创作礼」活动,一起开启掘金创作之路。
参考大佬的分析:
全网最!详!细!Tarjan算法讲解 blog.csdn.net/hurmishine/…
代码实现(代码里有详细的注释!)
结构定义
const int MaxV = 100;
/*定义栈*/
typedef struct SNode{
string *Data; //栈数据类型
int Top; //栈顶位置
int MaxSize; //栈容量
} * Stack;
/*需要的辅助数组*/
typedef struct ANode
{
Stack S;
//bool *Vis; //标记数组,标记顶点是否访问, 或表示(目前)栈中有没有该元素, 这里不用这个Vis
int *LOW; //追溯值,根据DFS树判断,向回追溯的最小时间戳(DFS)
int *DFN; //时间戳,访问顶点的顺序
int Index;
int Nv; //树顶点数为NV
} * AssArrary; //辅助数组集合
/*定义边*/
typedef struct ENode{
int v1, v2;
string va, vb;
//int Weight;
} * Edge;
/*定义邻接点*/
struct AdjVNode{
int AdjV;
//int Weight;
AdjVNode *Next;
};
/*定义邻接表头*/
typedef struct VNode{
string Data;
AdjVNode *EdgeFirst;
} AdjList[MaxV];
/*定义图*/
typedef struct GNode{
int Nv, Ne;
AdjList L;
} * LGraph;
栈的操作
/*入栈*/
void Push(Stack S, string x)
{
if(S->Top + 1 == S->MaxSize)//栈满,入栈失败
return;
else
S->Data[++S->Top] = x;
}
/*出栈*/
string Pop(Stack S)
{
if(S->Top < 0)
return "error";
else
return S->Data[S->Top--];
}
/*返回x在栈S中的位置,若不在返回-1*/
int FindS(Stack S, string x) //查找
{
int i;
for (i = S->Top; i >= 0; i--)
if (S->Data[i] == x)
break;
return i;
}
/*由边的字符串信息得到对应的数字编号*/
void EdgeCode(Edge E, Stack S)
{
int t = FindS(S, E->va);
if (t != -1) //如果能在栈中找到
E->v1 = t;
else
{
Push(S, E->va);
E->v1 = S->Top;
}
t = FindS(S, E->vb); //另一顶点同样的操作
if (t != -1)
E->v2 = t;
else
{
Push(S, E->vb);
E->v2 = S->Top;
}
}
图的操作
/*图的初始化*/
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;
Stack S = CreateStack(G->Nv);
Edge E = new ENode;
for (int i = 0; i < G->Ne; i++)
{
cin >> E->va >> E->vb;
EdgeCode(E, S);
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 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;
}
}
/*删除图*/
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;
}
辅助数组的初始化与删除
/*初始化需要用到的辅助数组*/
AssArrary CreateAss(int Nv)
{
AssArrary A = new ANode;
A->Nv = Nv; A->Index = 0;
A->DFN = new int[Nv]();
A->LOW = new int[Nv]();
//A->Vis = new bool[Nv]();
A->S = CreateStack(Nv);
return A;
}
/*删除辅助数组*/
void DeleteAss(AssArrary A)
{
//delete[] A->Vis;
delete[] A->LOW;
delete[] A->DFN;
delete[] A->S->Data;
delete A->S;
}
Tarjan算法,核心代码
/*Tarjan算法,核心代码*/
void Tarjan(LGraph G, int u, AssArrary A)
{
A->DFN[u] = A->LOW[u] = ++A->Index;
//A->Vis[u] = true; //Vis[]表示的是是否在栈中,而不是是否访问!!!
Push(A->S, G->L[u].Data);
AdjVNode *V = G->L[u].EdgeFirst;
while (V)
{
if(!A->DFN[V->AdjV]) //如果v点没有被访问过, DFN是时间戳,没访问过就全是0
{
Tarjan(G, V->AdjV, A);
//当上面访问结束后,(重新回溯到u顶点时)
A->LOW[u] = min(A->LOW[u], A->LOW[V->AdjV]); //因为有向边<u, v>联系,若v以下没有回路,LOW[v]一定是小于LOW[u]的
} //就是该条语句只在找到回路是才发生
//FinsS()为线性查找函数
else if (FindS(A->S, G->L[V->AdjV].Data) >= 0) //访问过,但栈中!! 这是就有回路了 (即DFS树上的反祖边出现了)
A->LOW[u] = min(A->LOW[u], A->DFN[V->AdjV]); //有回路就更新u顶点的LOW,新找到的被访问过得顶点的DFN一定小于当前顶点u的DFN!!!
V = V->Next;
}
if(A->LOW[u] == A->DFN[u]) //由DFS树的特点,当前DFN=LOW说明形成了一个回路(因为顶点u经过上面的变化,两部LOW减少语句都没有成功)
{ //上面可能变化LOW两句可知,都在有环路时才发生变化; 下面输出u及u以后的点
do
{
//A->Vis[A->S->Top] = false; //表示已经出栈, 这里由于顶点编号是字符串,不用该Vis表示是否在栈中
cout << Pop(A->S) << " ";
} while (A->S->Data[A->S->Top + 1] != G->L[u].Data); //如果刚才输出的是顶点u的字符, 则退出循环
cout << endl;
}
}
int main()
{
LGraph G = BuildGraph();
AssArrary A = CreateAss(G->Nv);
cout << "图的邻接表物理结构为:" << endl;
ShowGraph(G);
cout << "有下面几个强连通分量:" << endl;
for (int i = 0; i < G->Nv; i++)
if(!A->DFN[i])
Tarjan(G, i, A);
DeleteAss(A);
system("pause");
return 0;
}
输入样例
input:
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
output:
图的邻接表物理结构为:
0->5->1->NULL
1->NULL
5->4->NULL
2->3->0->NULL
3->5->2->NULL
4->3->2->NULL
6->9->4->0->NULL
9->11->10->NULL
7->8->6->NULL
8->9->7->NULL
10->12->NULL
11->12->4->NULL
12->9->NULL
有下面几个强连通分量:
1
2 3 4 5 0
10 12 11 9
6
8 7
请按任意键继续. . .