2023/5/30---个人总结---Tarjan算法

Tarjan算法

Tarjan 算法是基于深度优先搜索的算法,用于求解图的连通性问题。
用途:Tarjan 算法可以在线性时间内求出无向图的割点与桥,进一步地可以求解无向图的双连通分量;同时,也可以求解有向图的强连通分量、必经点与必经边。

其中需要两个重要的数组low,dfn。

dfn:作为这个点搜索的次序编号(时间戳),简单来说就是 第几个被搜索到的。

low:追溯值---(用来表示从当前节点 x 作为搜索树的根节点出发,能够访问到的所有节点中,时间戳最小的值)。

大致模板如下:

void Tarjan(int u)  //顶点
{
	++cnt;    //访问时间值
	dfn[u] = low[u] = cnt;
	for (int i = 0; i < edge[u].size(); ++i)
	{
		int v = edge[u][i];
		if (!dfn[v])
		{
			Tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else 
			low[u] = min(low[u], dfn[v]);
	}
}


割点:

若从图中删除节点 x 以及所有与 x 关联的边之后,图将被分成两个或两个以上的不相连的子图,那么称 x 为图的割点。
根结点:如果是割点条件是,当且仅当其子节点数大于等于 2。
非根节点 :u 如果是割点,当且仅当 u 至少存在一个子树v,v中没有连向 u 的祖先的边(返祖边)。
 即v访问结束时满足low[v]>=dfn[u]
 

桥(割边):


若从图中删除边 e 之后,图将分裂成两个不相连的子图,那么称 e 为图的桥或割边。
边(u, v)在dfs 树中。如果u 为v 的父亲,v 的子树中没有向u 或其祖先连的边。如果桥(u,v)的两个端点都不是叶子节点,则节点u和v为割点
判断条件low[v]>dfn[u]

双连通分量:


点双连通分量:点双连通的极大子图称为点双连通分量(简称双连通分量)
特点
该连通分量的点在同一简单环;该连通分量没有桥;一个割点可以多个点连通分量。

边连通分量:边双连通的极大子图称为边双连通分量。
特点
任意一条边至少包含在一个简单环;连通分量里没有桥;割点只属于一个边双连通分量;
两个边双连通分量最多只有一条边,且必为桥;进一步地,所有边双与桥可抽象为一棵树结构。

P2863 [USACO06JAN]The Cow Prom S

求强连通分量个数。代码如下:

#include
#include
#include
using namespace std;

const int MAX = 1e4 + 5;
int n, m, cnt, cntb;
vector edge[MAX];  //存边
vector belong[MAX];
bool instack[MAX];   //表示某结点是否在栈中
int dfn[MAX];    //记录搜索顺序
int low[MAX];    //记录所属强连通
stack s;   //一个栈存储搜索路径
 
void Tarjan(int u)
{
	++cnt;    //访问时间值
	dfn[u] = low[u] = cnt;
	s.push(u);
	instack[u] = true;
	for (int i = 0; i < edge[u].size(); ++i)
	{
		int v = edge[u][i];
		if (!dfn[v])
		{
			Tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (instack[v])
			low[u] = min(low[u], dfn[v]);
	}
	if (dfn[u] == low[u]) 如果一个点的dfn和low值相同,这个点就是根(找到一个强连通分量)
	{
		++cntb;
		int node;
		do
		{           把该强连通分量的所有节点序号存入belong数组中
			node = s.top();
			s.pop();
			instack[node] = false;
			belong[cntb].push_back(node);
		} while (node != u);
	}
}
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; ++i)
	{
		int u, v;
		cin >> u >> v;
		edge[u].push_back(v);
	}

	Tarjan(1);

    for (int i = 1; i <= n; i++) {
		if (!dfn[i])Tarjan(i);
	}
	int cont = 0;
	for (int i = 1; i <= cntb; ++i)
	{

		if (belong[i].size() > 1)cont++;
	}
	cout << cont << '\n';

	return 0;
}

P1656 炸铁路

无向图求桥(割边),并按给定规则输出。代码如下:

#include
#include
#include 
#include
#include
using namespace std;


const int MAX = 5005;
int n, m, cnt;  
int cont;
vector edge[2 * MAX];  
int dfn[MAX];    
int low[MAX];  
struct node{
	int u, v;
}node[MAX*2];

bool cmp(struct node a, struct node b) {
	if (a.u == b.u) {
		return a.v < b.v;
	}
	else {
		return a.u < b.u;
	}
}

void tarjan(int u,int in_edge) {
	++cnt;
	dfn[u] = low[u] = cnt;
	for (int i = 0; i < edge[u].size(); i++) {
		int v = edge[u][i];
		if (!dfn[v]) {
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u]) {  桥的判定条件
				node[cont].u = u;
				node[cont++].v = v;
			}
		}
		else if(v != in_edge){  
			low[u] = min(low[u], dfn[v]);
		}
	}

}

int main() {
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for (int i = 0; i < m; i++) {
		int u, v;
		cin >> u >> v;
		edge[u].push_back(v);
		edge[v].push_back(u);
	}

	for (int i = 1; i <= n; i++) {  
		if (!dfn[i])tarjan(i, 0);
	}
	sort(node, node + cont, cmp);
	for (int i = 0; i < cont; i++) {
		cout << node[i].u << ' ' << node[i].v << '\n';
	}
	return 0;
}

P3388 【模板】割点(割顶)

无向图求割点。代码如下:

#include
#include
#include 
#include
#include
using namespace std;

const int MAX = 1e5 + 5;
int n, m, cnt; 
int cont;
vector g[2*MAX]; 
int dfn[MAX];    
int low[MAX];
int flag[MAX];

// v:当前点 r:本次搜索树的root
void tarjan(int u, int r) {
	dfn[u] = low[u] = ++cnt;
	int child = 0;
	for (unsigned i = 0; i < g[u].size(); i++) {
		int v = g[u][i];
		if (!dfn[v]) {
			tarjan(v, r);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u] && u != r)cont += !flag[u], flag[u] = 1;//不是根而且他的孩子无法跨越他回到祖先
			if (r == u)child++; //如果是搜索树的根,统计孩子数目
		}
		else low[u] = min(low[u], dfn[v]);//已经搜索过了
	}
	if (child >= 2 && u == r)cont += !flag[r], flag[r] = 1;
}

int main() {
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for (int i = 0; i < m; i++) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}

	for (int i = 2; i <= n; i++) {  
		if (!dfn[i])tarjan(i, i);
	}
	cout << cont << '\n';
	for (int i = 1; i <= n; i++) {
		if (flag[i])cout << i << ' ';
	}
	return 0;
}

你可能感兴趣的:(算法)