zoukankan      html  css  js  c++  java
  • 【算法日记】Tarjan

    图论

    Tarjan

    Tarjan 求强连通分量

    在图中找到一个最大的图,使这个图中的每个两点能够互相到达。
    用DFS搜,将每一个强连通分量作为搜索树上的子树。

    模板

    输出图中的每个强连通分量

    //DFN[]作为这个点搜索的次序编号(时间戳
    //LOW[]作为每个点在这颗树中的,最小的子树的根,每次保证最小,like它的父亲结点的时间戳这种感觉。如果它自己的LOW[]最小,那这个点就应该从新分配,变成这个强连通分量子树的根节点。
    //每次找到一个新点,这个点LOW[]=DFN[]。
    struct node {
        int v,next;
    }edge[1001];
    int DFN[1001],LOW[1001];
    int sstack[1001],heads[1001],visit[1001],cnt,tot,index;
    void add(int x,int y)
    {
        edge[++cnt].next=heads[x];
        edge[cnt].v = y;
        heads[x]=cnt;
        return ;
    }
    void tarjan(int x)//代表第几个点在处理。递归的是点。
    {
        DFN[x]=LOW[x]=++tot;// 新进点的初始化。
        sstack[++index]=x;//进站
        visit[x]=1;//表示在栈里
        for(int i=heads[x];i!=-1;i=edge[i].next)
        {
            if(!DFN[edge[i].v]) {//如果没访问过
                tarjan(edge[i].v);//往下进行延伸,开始递归
                LOW[x]=min(LOW[x],LOW[edge[i].v]);//递归出来,比较谁是谁的儿子/父亲,就是树的对应关系,涉及到强连通分量子树最小根的事情。
            }
            else if(visit[edge[i].v ]){  //如果访问过,并且还在栈里。
                LOW[x]=min(LOW[x],DFN[edge[i].v]);//比较谁是谁的儿子/父亲。就是链接对应关系
            }
        }
        if(LOW[x]==DFN[x]) //发现是整个强连通分量子树里的最小根。
        {
            do{
                printf("%d ",sstack[index]);
                visit[sstack[index]]=0;
                index--;
            }while(x!=sstack[index+1]);//出栈,并且输出。
            printf("
    ");
        }
        return ;
    }
    int main()
    {
        memset(heads,-1,sizeof(heads));
        int n,m;
        scanf("%d%d",&n,&m);
        int x,y;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&x,&y);
            add(x,y);
        }
        for(int i=1;i<=n;i++)
             if(!DFN[i])  tarjan(1);//当这个点没有访问过,就从此点开始。防止图没走完
        return 0;
    }
    
    //vector 实现邻接表
    const int N=100005;
    vector <ll> g[N]; 
    stack <ll> s;
    ll color[N],vis[N],dfn[N],low[N],cnt[N];// dfn[i] i出现的次序 low[i] 点i在这棵树中最小子树的根 
    ll deep,top,sum,res=0;//deep 节点编号  top 栈中元素数量 sum 强连通分量的数目
    void tarjan(int v){
    	dfn[v] = low[v] = ++deep;
    	vis[v]=1;
    	s.push(v);
    	for(unsigned i=0;i<g[v].size();i++){
    		ll id = g[v][i];
    		if(!dfn[id]){
    			tarjan(id);
    			low[v]=min(low[v],dfn[id]);
    		} 
    		else{
    			if(vis[id]){
    				low[v]=min(low[v],dfn[id]);
    			}
    		}
    	}
    	if(low[v] == dfn[v]){//自己和子节点形成了强连通分量或者自己只有一个人 
    		color[v]= ++sum;
    		vis[v]=0;
    		while(s.top()!=v){
    			color[s.top()]=sum;
    			vis[s.top()]=1;
    			s.pop();
    		} 
    		cout<<endl;
    	}	
    }
    int main(){
    	int n,m;
    	n=read();m=read();
    	for(int i=1;i<=m;i++){
    		g[read()].push_back(read());
    	}
    	return 0;
    }
    
    

    Tarjan缩点

    原理:强连通分量对于一些贡献有传导性
    对于一个有向环,可以缩成一个点。

    引入问题

    一个有向图,有n个点以及m条边,我们至少应该添加几条边才能使整个图变成强连通图。或者是一个无向图至少添加几条边变成连通图。

    思路

    我们计算入度为零的点数为a,出度为零的点数为b,那么我们至少需要添加的边数为max(a,b),如果只有一个点的话,我们不需要添加任何边。
    那么我们怎么把一个图转换为DAG呢,因为上面给出的图可能存在环,那么我们就会想到把已经组成全连通的子图转换成一个点来看,那么我们最终的图就不会包含环了。
    我们使用Tarjan算法求解出强连通分量之后,我们上面使用了一个color数组将同一个连通分量的点分配相同的数值,然后我们再次遍历一遍所有的边,如果边的两侧u->v不属于同一颜色,那么u对应颜色将会有一条边连向v对应的颜色。在此基础上我们可以计算缩点之后的出入度,得到max(a,b)或者其他一些信息。

    实现

    #define MAX 10005
    #define ll long long
    
    vector<ll> g[MAX];
    ll color[MAX], vis[MAX], stack[MAX], dfn[MAX], low[MAX], cnt[MAX], num[MAX];
    ll ind[MAX], outd[MAX];//每个点的出度入度
    //deep:节点编号 top:栈顶  sum:强连通分量数目
    ll deep, top, sum, res = 0;
    
    void tanjan(ll v) {
    	dfn[v] = ++deep;
    	low[v] = deep;   //(1)初始化dfn数组,同时将low设置为相同值
    	vis[v] = 1;
    	stack[++top] = v;//(2)入栈,作为栈顶元素,同时更新vis数组
    
    	for (unsigned i = 0; i < g[v].size(); i++) {//(3)遍历所有可能到达的点
    		ll id = g[v][i];
    		if (!dfn[id]) {//如果这个点从没访问过,则先放问它,再用它更新low[v]的值
    			tanjan(id);
    			low[v] = min(low[v], low[id]); //他的儿子如果能连到更小的祖先节点,显然他也可以
    		}
    		else {
    			if (vis[id]) {//不在栈中的点,要么没有访问,要么不能到达id,所以只需要判断栈中的
    				low[v] = min(low[v], low[id]);
    			}
    		}
    	}
    
    	if (low[v] == dfn[v]) {//(4)自己和子节点形成了强连通分量,或者只有自己孤身一人
    		color[v] = ++sum; num[sum]++; //num统计该颜色有多少点
    		vis[v] = 0;
    		while (stack[top] != v) {//将从v开始所有的点取出
    			color[stack[top]] = sum;//给予同一颜色
    			vis[stack[top--]] = 0;//出栈要顺便修改vis
    			num[sum]++;
    		}
    		top--;
    	}
    }
    int main(){
    	for (int i = 1; i <= N; i++) {
    		for (unsigned k = 0; k < g[i].size(); k++) {
    			ll v = g[i][k];
    			if (color[v] != color[i]) {//二者分属于不同的联通集
    				outd[color[i]] += 1; //以颜色作为点,更新相应点的出度
    			}
    		}
    	}
    }
    

    Tarjan割点

    割点定义:对于一个无向图,如果这个顶点集合以及集合中相关联的边,图的联通分量增多,这个点为割点。
    对于根节点,如果子树数量>=2 就是割点(子树互不相连,而且不连向祖先)
    对于非根借点 维护数组dfn和low,对于边(u,v),如果low[v]>=dfn[u] u就是割点

    解释

    low[v]>=dfn[u] 意味着v不能回到u前面 u的儿子可以不通过u访问到u的祖先,那么去掉u后,不影响连通性,那么u就不是割点。反之如果u如果存在儿子节点,必须要通过u链接到u的祖先,那么去掉u,连通性增加,u就是割点。

    注意事项!!!

    对于tarjan 可以将更新语句改为 low[u]=min(low[u],dfn[v]);
    对于割点 这一句就不可以
    在求强连通分量中,如果v已经在栈里,那么u,v一定在同一个强连通给分量中,所有Low[u]=low[v]是必然,提前更新也不会有问题。
    但是,在求割点过程中,low不能追溯到最早祖先,因为这是一个无向图。应该是最早能绕到的割点。如果把dfn[v]改掉就会翻过头。

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

    学习博客来源:https://blog.csdn.net/csyifanZhang/article/details/105370924?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160488705219724836759948%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=160488705219724836759948&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-1-105370924.pc_first_rank_v2_rank_v28p&utm_term=Tarjan&spm=1018.2118.3001.4449

  • 相关阅读:
    字符串匹配的三种算法
    字符串匹配
    多少个矩形被覆盖
    指针和地址的区别
    股票开盘的最大成交额-----一道不错的贪心算法题目
    Z字形扫描矩阵
    第几次出现
    节日(CCF试题)
    数字排序
    JS对象添加新的字段
  • 原文地址:https://www.cnblogs.com/Shayndel/p/13951305.html
Copyright © 2011-2022 走看看