【算法】【Undone】Tarjan算法 - 查找图的强连通分量、(割点)

【算法】Tarjan算法 - 查找图的强连通分量、(割点)

[by_041]

Tarjan算法是基于由图建树加上DFS序(时间戳DFN)处理的一种算法
适用于both有向图和无向图

  • 其理念图:
    在这里插入图片描述

数据结构

  • 对一个节点,其有树结点的数据结构,此外还需包含以下两个成员值;
  • DFN序:对图DFS建树时产生的时间戳
  • LOW值:对于每个点,能回溯到达的点中的最小DFN序(有向)/最多经过一条后向边能到达的点中的最小DFN序(无向)

理论基础

  • 树枝边:DFS时经历的边(即是DFS搜索树上的边)

  • 前向边:与DFS方向一致,从某结点指向其子孙结点的边(eg.(u,v):dfn(u)<dfn(v))

  • 后向边:与DFS方向相反,从某结点指向其祖先结点的边(eg.(u,v):dfn(u)>dfn(v))

  • 横叉边:从某结点指向搜索树上另一棵子树中某点的边

  • 对于一条边:(u,v)

    • 其为树枝边(v还未产生DFN序),u为v的父结点,则LOW(u)=Min{LOW(u),LOW(v)}
    • 其为后向边或横向边(v已有DFN序),则LOW(u)=Min{LOW(u),DFN(v)}
  • 当对一个结点u的搜索过程结束后,若DFN(u)=LOW(u),则以u为根结点的搜索子树上所有的结点(即u和在u之后进栈的结点)是一个强连通分量,可退栈。u为该强连通分量根,那么它子孙的最高祖先是他本身

实践过程

  • 构造数据结构,申请变量空间
  • Tarjan函数:
    • 初始化:当首次搜索到u时,DFN(u)为结点u的搜索次序编号(时间戳)
    • 堆栈:将u压入栈顶
    • 更新:更新Low(u),遍历从u出发的点,对于一条边(u,v)参考上面的操作
    • 优化:如果结点u的子树全部遍历后Low(u)=DFN(u),则将u和在u之后的所有结点弹出
    • 继续搜索未被遍历的点
#include<iostream>
#include<vector>
#include<stack>

using namespace std;

struct Tnode
{
    
    
	int num;
	int dfn;
	vector<int>to;
};
int n,m;
vector<Tnode>tn;//tree nodes
int dfs=0;//use '++now_dfn' when need
stack<int>sta;

void init()
{
    
    
	cin>>n;
	tn.resize(n+1);
	for(int i=1;i<=n;i++)
		tn[i].num=i;
	cin>>m;
	//输入边构建图
	for(int i=0,u,v;i<m;i++)
	{
    
    
		scanf("%d%d",&u,&v);
		tn[u].to.push_back(v);
		tn[v].to.push_back(u);
	}
}

void tarjan(int u)
{
    
    
//	- 初始化:当首次搜索到u时,DFN(u)为结点u的搜索次序编号(时间戳)
	if(tn[u].dfn==0)
		tn[u].dfn=++dfs;
//	- 堆栈:将u压入栈顶
	sta.push(u);
//	- 更新:更新Low(u),遍历从u出发的点,对于一条边(u,v)参考上面的操作
	for(int i=0,ii=tn[u].to.size();i<ii;i++)
	{
    
    
		;
	}
//	- 优化:如果结点u的子树全部遍历后Low(u)=DFN(u),则将u和在u之后的所有结点弹出
	;
}

int main(void)
{
    
    
	init();
	//tarjan(rand()%n+1);	//有时候防卡数据可以rand一下
	for(int i=1;i<=n;i++)
		if(tn[i].dfn==0)
			tarjan(i);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42710619/article/details/115934211