热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

tarjan算法_SCC算法Kosaraju算法amp;Tarjan算法

正道的光,照在了大地上!图论中,强连通图指每一个顶点皆可以经由该图上的边抵达其他的每一个点的有向图。意即对于此图上每一个点对(Va,Vb)
正道的光,照在了大地上!

图论中,强连通图指每一个顶点皆可以经由该图上的边抵达其他的每一个点的有向图。意即对于此图上每一个点对(Va,Vb),皆存在路径Va→Vb以及Vb→Va。「强连通分量」则是指一张有向图G的极大强连通子图G'。如果将每一个强连通分量缩成一个点,则原图G将会变成一张有向无环图。一张图被称为有向无环图当且仅当此图不具有点集合数量大于一的强连通分量,因为有向环即是一个强连通分量,而且任何的强连通分量皆具有至少一个有向环。如图所示,图中有三个强连通分量:

3d29640a9d0e8c340e1a06b0f296d95a.png

  • {1,2,3}
  • {4,5,6,7}
  • {8,9}

第一节 寻找强连通分量的有效算法

1.1Kosaraju算法

1.1.1 算法步骤

  • step1:对原图进行DFS,记录顶点的后序遍历序列(入栈)
  • step2:每次选择栈顶的顶点(出栈),对反向图进行DFS,标记能够遍历到的顶点,这些顶点构成一个强连通分量。
  • 如果还有顶点没有标记,继续step2,否则算法结束

1.1.2 算法原理

  1. 反图与原图的强连通分量是相同的
  2. 若原图能从分量1走到分量2,则反图不能从分量1走到分量2

1.1.3 代码

下面的代码中,默认每个节点是可以自联通的。存储图的数据结构为邻接表。目的是有的小伙伴的图的id可能是不连续的,这样剩下了节点映射的操作。为了方便,在代码中手动建图,如果读者需要,可以在txt中编辑好图,读入文件的时候建图。

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

public class Kosaraju {

   public List> getScc(HashMap> map,HashMap> revmap){
       List> result &#61; new LinkedList<>();
       Stack stack &#61; new Stack<>();
       //声明一个节点遍历状态数组&#xff0c;记录节点是否被遍历过&#xff0c;由于节点id是大于0的&#xff0c;故visited中的0进行空置(就是不使用)
       boolean[] visited &#61; new boolean[map.size()&#43;1];
       //对原图进行DFSfor (int id:map.keySet()) {if(!visited[id]) {
               visited[id] &#61; true;
               dfs1(map,id,visited,stack);
          }
      }
       //将节点遍历状态重置
       visited &#61; new boolean[map.size()&#43;1];while(!stack.isEmpty()){
           //每次选择栈的顶点出栈&#xff0c;对反向图进行DFS
           int num &#61; stack.pop();if(!visited[num]){
               visited[num] &#61; true;
               List trace &#61; dfs2(revmap,num,visited,new LinkedList(){{add(num);}});
               result.add(trace);
          }
      }return result;
  }
   public void dfs1(HashMap> map, int id ,boolean[] visited,Stack stack){if(map.get(id) &#61;&#61; null) return ;
       //记录后续遍历的序列(入栈操作)for(int num : map.get(id)){if(!visited[num]){
               visited[num] &#61; true;
               dfs1(map,num,visited,stack);
          }
      }
       stack.add(id);
  }
   public List dfs2(HashMap> revmap, int id ,boolean[] visited, LinkedList trace){if(revmap.get(id) &#61;&#61; null) return trace;for(int num : revmap.get(id)){if(!visited[num]){
               visited[num] &#61; true;
               trace.add(num);
               dfs2(revmap,num,visited,trace);
          }
      }return trace;
  }
   public static void main(String[] args) {
       //建立例子中的正向图
       HashMap> map &#61; new HashMap<>();
       map.put(1,new LinkedList(){{add(2);add(4);add(1);}});
       map.put(2,new LinkedList(){{add(3);}});
       map.put(3,new LinkedList(){{add(1);}});
       map.put(4,new LinkedList(){{add(5);}});
       map.put(5,new LinkedList(){{add(6);add(7);add(8);}});
       map.put(6,new LinkedList(){{add(4);}});
       map.put(7,new LinkedList(){{add(6);}});
       map.put(8,new LinkedList(){{add(9);}});
       map.put(9,new LinkedList(){{add(8);}});
       //建立例子中的反向图
       HashMap> revmap &#61; new HashMap<>();
       revmap.put(1,new LinkedList(){{add(3);}});
       revmap.put(2,new LinkedList(){{add(1);}});
       revmap.put(3,new LinkedList(){{add(2);}});
       revmap.put(4,new LinkedList(){{add(1);add(6);}});
       revmap.put(5,new LinkedList(){{add(4);}});
       revmap.put(6,new LinkedList(){{add(5);add(7);}});
       revmap.put(7,new LinkedList(){{add(5);}});
       revmap.put(8,new LinkedList(){{add(5);add(9);}});
       revmap.put(9,new LinkedList(){{add(8);}});
       Kosaraju demo &#61; new Kosaraju();
       List> result &#61; demo.getScc(map,revmap);
  }
}

1.2 Tarjan算法

Tarjan算法是基于对图的深度优先搜索的算法&#xff0c;每个强连通分量为搜索树种的一颗子树。搜索时&#xff0c;把的当前搜索树中未处理的节点加入一个堆栈&#xff0c;回溯时可以判断栈顶到栈中的节点是否是一个强连通分量。

Tarjan算法可以通过一次深度遍历&#xff0c;找到强连通分量。Tarjan包含以下几种数据结构&#xff1a;

  • 标记数组Dfn[]:记录点的访问次序
  • 标记数组Low[]:动态改小点的访问次序
  • 栈数组Stack[]:顶点入栈&#xff0c;分量出栈

1.2.1 Tarjan算法的伪代码描述

DFS(u){
   1.记录:Dfn[u] &#61; Low[u] &#61; &#43;&#43;num;
     入栈:Stack[&#43;&#43;t] &#61; u;
   for(对 u 的邻接点v){
       //如果v没有被访问过
       if(Dfn[v] &#61;&#61; 0){//如果v没有被访问过
           2.调用:DFS(v);
           3.回溯时改小;
           Low[u] &#61; min(Low[u],Low[v]);
      }
       else if(v在栈中){//说明u&#xff0c;v已经构成了环
           4.有环时改小&#xff1a;
           Low[u] &#61; Low[v];
      }
       if(Dfn[u] &#61;&#61; Low[u]){//如果u是分量的根
           5.输出分量
           u与u之后访问的点出栈
      }            
  }
}

下面是原版论文中Tarjan算法的伪代码流程&#xff1a;

4ee64f79a02a38469c405cb57a46901a.png

(原版论文中Tarjan算法伪代码流程)

1.2.2 代码

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

public class Tarjan {

   public List> getScc(HashMap> map){
       List> result &#61; new LinkedList<>();
       HashMap dfn &#61; new HashMap<>();
       HashMap low &#61; new HashMap<>();
       Stack stack &#61; new Stack<>();for (int num: map.keySet()) {if(!dfn.containsKey(num)){
               tarjanDfs(map,num,dfn,low,stack,result);
          }
      }return result;
  }
   static int time &#61; 1;
   public void tarjanDfs(HashMap> map, int num,
                         HashMap dfn, HashMap low,
                         Stack stack,List> result){
       time&#43;&#43;;
       dfn.put(num,time);
       low.put(num,time);
       stack.add(num);for(int id:map.get(num)){if(!dfn.containsKey(id)){
               tarjanDfs(map,id,dfn,low,stack,result);
               //回溯的时候需要改小
               low.put(num, Math.min(low.get(num),low.get(id)));
          }else if(stack.contains(id)) low.put(num,low.get(id));if(dfn.get(num) &#61;&#61; low.get(num) && stack.contains(id)){
               //输出分量
               LinkedList trace &#61; new LinkedList<>();
               int temp;do {
                   temp &#61; stack.pop();
                   trace.add(temp);
              }while(temp !&#61; num);
               result.add(trace);
          }
      }
  }
   public static void main(String[] args) {
       //建立例子中的正向图
       HashMap> map &#61; new HashMap<>();
       map.put(1,new LinkedList(){{add(2);add(4);}});
       map.put(2,new LinkedList(){{add(3);}});
       map.put(3,new LinkedList(){{add(1);}});
       map.put(4,new LinkedList(){{add(5);}});
       map.put(5,new LinkedList(){{add(6);add(7);add(8);}});
       map.put(6,new LinkedList(){{add(4);}});
       map.put(7,new LinkedList(){{add(6);}});
       map.put(8,new LinkedList(){{add(9);}});
       map.put(9,new LinkedList(){{add(8);}});
       Tarjan demo &#61; new Tarjan();
       List> result &#61; demo.getScc(map);
       System.out.println();
  }
}

往期回顾

机器学习学习笔记CTR预估——贝叶斯平滑XGBoost学习笔记LightGBM学习笔记

欢迎大家关注微信公众号&#xff1a;哦呦明知山有虎

19806adf5e86802e7159c474e087266b.png




推荐阅读
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • Java程序设计第4周学习总结及注释应用的开发笔记
    本文由编程笔记#小编为大家整理,主要介绍了201521123087《Java程序设计》第4周学习总结相关的知识,包括注释的应用和使用类的注释与方法的注释进行注释的方法,并在Eclipse中查看。摘要内容大约为150字,提供了一定的参考价值。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
author-avatar
童T-Aurora
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有