强连通分量Tarjan算法

参考大佬的分析:
全网最!详!细!Tarjan算法讲解 https://blog.csdn.net/hurmishine/article/details/75248876
代码实现(代码里有详细的注释!)
结构定义

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
请按任意键继续. . .

完整代码

#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;
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;

/*栈的初始化*/
Stack CreateStack(int MaxSize)
{
	Stack S = new SNode;
	S->Data = new string[MaxSize];
	S->Top = -1;
	S->MaxSize = MaxSize;
	return S;
}

/*入栈*/
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算法,核心代码*/
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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值