本算法通过遍历整个有向图来获取已选节点的关系,通过本算法,可以轻松的获得已选节点在其有向图中的流向关系,如下图,通过本算法我们可以和容易的发现A与C的流向关系是A -> C 而不是 A <- C
\
本算法的实现步骤是:
1.遍历整个有向图
2.每次遍历,查看当前节点是否为已选节点之一,
3.若是,建立集合存储已选节点(这里暂且称为临时集合),实现中用的是集合是HashMap
4.若果临时集合元素个数不为0,则将刚加入临时集合的节点名称放入临时集合已有元素的containNode字段中,containNode字段的值是一个类型为String的HashSet
5.若临时集合已有的某个节点的所以子孙节点已遍历完毕,则将该节点所对应的元素从临时集合中删除。
由于本算法需要遍历整个有向图,因此时间复杂度是遍历整个有向图的复杂度;另,关于有向图的遍历方法,在文章《有向图闭环检测值java代码实现》中有论述,这里就不再说明了,当然本算法实现也是有文章《有向图闭环检测值java代码实现》的实现修改得来,对于遍历的修改点如下:
1.修改了traverseInDigraph()方法,取消了对"isFrozen"属性值得检测,因为本算法需要深度的遍历整个有向图。
2.修改了getNextNodeIndexInDigraph()方法,增加了对"isFrozen"的属性值检测,确保可以深度遍历整个有向图。
3.修改了traverseInDigraph(),在前进时将下一节点放入临时集合,后退时将临时集合里对应的元素放入返回之中,同时删除临时集合中对应的元素
具体实现代码如下:
/**
*
* 功能:在有向图中寻找 已选节点的关系
* 作者:EugeneJao
* @param nodeCollections
* @param nodes
* @return
* @throws Exception
*/
public Map<String,Map<String,Object>> seekRelation(Map<String,Map<String,Object>> nodeCollections , List<String> nodes) throws Exception{
Map<String,Map<String,Object>> ret = new HashMap<String, Map<String,Object>>();
/**
* 如果前进至某已选节点则将该已选节点放入inn,
* 若已从相应已选节点中后退,则从inn移出该节点
* ,感觉就像是供游客暂歇的旅馆
*/
Map <String,Map<String,Object>> inn = new HashMap<String, Map<String,Object>>();
/**
* 初始化"起点
*/
String startNode = getOriginInDigraph(nodeCollections);
/**
* 初始化"视野"
*/
Map<String,String> nodeName = new HashMap<String, String>() ;
nodeName.put("nextNode", startNode);
nodeName.put("curNode", startNode);
nodeName.put("beforeNode", startNode);
while(!(nodeName.get("beforeNode").equals(nodeName.get("curNode"))
&& nodeName.get("nextNode") == null )){
/**
* 遍历每个节点
*/
nodeName = traverseInDigraphForSeek(nodeCollections, nodeName, inn, nodes,ret);
}
return ret;
}
/**
*
* 功能:在有向图中访问某个网络节点
* 作者:EugeneJao
* @param nodeCollections
* @param nodeName
* @param inn
* @param ret
* @return
* @throws Exception
*/
private Map<String,String> traverseInDigraphForSeek(Map<String,Map<String,Object>> nodeCollections , Map<String,String> nodeName ,
Map<String, Map<String, Object>> inn , List<String> nodes, Map<String, Map<String, Object>> ret) throws Exception{
/**
* 判断前进节点在网络中存不存在
*/
if(nodeName.get("nextNode") != null){
if(!(nodeCollections.containsKey(nodeName.get("nextNode")))){
throw new Exception("节点网络构建异常,网络中不存在节点:" + nodeName);
}
}
if(nodeName.get("curNode") == null || nodeName.get("beforeNode") == null){
throw new Exception("traverse()方法入参异常:curNode和beforeNode不能为空" + nodeName);
}
if(nodeName.get("nextNode") != null
&& nodeCollections.get(nodeName.get("nextNode"))== null ){
throw new Exception("节点"+ nodeName.get("nextNode") +"在节点集合中不存在");
}
String nextNodeName = nodeName.get("nextNode") ;
String curNodeName = nodeName.get("curNode") ;
if( nextNodeName == null){
if(isSelectedNode(curNodeName, nodes)){
record(inn, ret, curNodeName);
this.checkOut(curNodeName, inn);
}
nodeName = backInDigraphForSeek(nodeCollections, nodeName);
}else{
if(isSelectedNode(nextNodeName, nodes)){
this.checkIn(nextNodeName, inn);
}
nodeName = headInDigraphForSeek(nodeCollections, nodeName);
}
/**
* 如果不是起始"视野",但是nextNode和curNode相同的,
* 说明在一个节点上可能存在包含自身的关系,这样的数据是异常数据
* 顺序不可调整至调用head()和back()的语句前面
*/
if(nodeName.get("curNode").equals(nodeName.get("nextNode")) &&
!nodeName.get("curNode").equals(nodeName.get("beforeNode"))){
int curRelIndex = (Integer) nodeCollections.get(nodeName.get("curNode")).get("curRelIndex") ;
throw new Exception("节点参数异常:节点" + nodeName.get("curNode")+
"的关系节点不能包含自身,在relList的第"+
(curRelIndex + 1)+"元素上");
}
return nodeName ;
}
/**
*
* 功能:判断节点是否为已选节点
* 作者:EugeneJao
* @param curNodeName
* @param nodes
* @return
*/
private boolean isSelectedNode(String curNodeName,List<String> nodes){
boolean ret = false ;
Iterator it = nodes.iterator() ;
while(it.hasNext()){
String tmpNodeName = (String) it.next() ;
if(tmpNodeName.equals(curNodeName)){
ret = true ;
break ;
}
}
return ret;
}
/**
*
* 功能:将节点放入inn中,并进行相关操作
* 作者:EugeneJao
* @param curNodeName
* @param inn
*/
private void checkIn(String curNodeName , Map<String,Map<String,Object>> inn){
/**
* 将已在inn中的节点的relList增加当前节点
*/
Set keySet = inn.entrySet();
Iterator it = keySet.iterator() ;
while(it.hasNext()){
Entry tmpSet = (Entry) it.next() ;
String tmpKey = (String) tmpSet.getKey();
Map<String, Object> tmpNode = inn.get(tmpKey);
Set<String> tmpList = (Set<String>) tmpNode.get("containNode");
tmpList.add(curNodeName);
tmpNode.put("containNode", tmpList);
inn.put(tmpKey, tmpNode);
}
/**
* 往inn中新增节点
*/
Map<String,Object> traverNode = new HashMap<String, Object>();
Set<String> relList= new HashSet<String>();
traverNode.put("nodeName",curNodeName);
traverNode.put("containNode", relList);
inn.put(curNodeName, traverNode) ;
}
/**
*
* 功能:将已选节点从inn中删除
* 作者:EugeneJao
* @param curNodeName
* @param inn
*/
private void checkOut(String curNodeName , Map<String,Map<String,Object>> inn){
inn.remove(curNodeName);
}
/** 功能:在有向图中前进至下一个节点
* 作者:EugeneJao
* @param nodeCollections
* @param nodeName
* @param inn
* @return
* @throws Exception
**/
@SuppressWarnings("unchecked")
private Map<String,String> headInDigraphForSeek(Map<String,Map<String,Object>> nodeCollections , Map<String,String> nodeName) throws Exception{
String curNodeName = nodeName.get("nextNode") ; //传入参数的nextNode在本方法中即是当前节点
String beforeNodeName = nodeName.get("curNode") ;//传入参数的curNode在本方法中即是前一节点
Map<String,Object> curNode = nodeCollections.get(curNodeName);
List<String> relList = (List<String>) curNode.get("relList") ;
curNode.put("beforeNode",beforeNodeName) ;//传入参数的beforeNode在本方法中即是当前节点
int curRelIndex = getNextNodeIndexInDigraphForSeek(curNode);
/**
* 获取下一个前进的目标节点名称,当curRelIndex等于relList的长度时,
* 说明当前节点的所有关系节点都遍历完了,已无节点可以前进,只能后退
*/
String nextNodeName = null ;
if(curRelIndex < relList.size()){
nextNodeName = relList.get(curRelIndex) ;
}
/**
* 更新当前节点信息
*/
curNode.put("curRelIndex", curRelIndex);//不能调整顺序值调用getNextNodeIndex()方法后面
nodeCollections.put(curNodeName, curNode);
/**
* 更新nodeName信息
*/
nodeName.put("nextNode", nextNodeName);
nodeName.put("curNode", curNodeName);
nodeName.put("beforeNode", beforeNodeName);
return nodeName;
}
/**
*
* 功能:在无向图中后退至之前的节点
* 作者:EugeneJao
* @param nodeCollections
* @param nodeName
* @return
* @throws Exception
*/
private Map<String,String> backInDigraphForSeek(Map<String,Map<String,Object>> nodeCollections , Map<String, String> nodeName) throws Exception{
/**
* 节点后退,说明遍历完当前节点的所有子孙节点后,依然没有闭环
* 所以先更新当前节点信息的isFroze字段为true
* 在多个父节点的情况下,通过isFrozen字段来区分之前通过另一父节点对改节点的遍历
* 同时也通过这个属性介绍算法运算的复杂度
*/
String curNodeName = nodeName.get("curNode");
Map<String,Object> curNode = nodeCollections.get(curNodeName);
curNode.put("isFrozen", true);
/**
* 更新完Frozen属性后,做后退操作
*/
curNodeName = nodeName.get("beforeNode") ; //传入参数的beforeNode在后退操作中即是当前节点
curNode = nodeCollections.get(curNodeName);
List<String> relList = (List<String>) curNode.get("relList") ;
String beforeNodeName = (String) curNode.get("beforeNode");
/**
* 先更新当前节点beforeNode信息,因为后面要用
*/
curNode.put("beforeNode", beforeNodeName);//不能调整顺序值调用getNextNodeIndex()方法后面
int curRelIndex = getNextNodeIndexInDigraphForSeek(curNode);
/**
* 获取下一个前进的目标节点名称,当curRelIndex等于relList的长度时,
* 说明当前节点的所有关系节点都遍历完了,已无节点可以前进,只能后退
*/
String nextNodeName = null ;
if(curRelIndex < relList.size()){
nextNodeName = relList.get(curRelIndex) ;
}
/**
* 更新当前节点信息
*/
curNode.put("curRelIndex", curRelIndex);//不能调整顺序值调用getNextNodeIndex()方法前面
nodeCollections.put(curNodeName, curNode);
/**
* 更新nodeName信息
*/
nodeName.put("nextNode", nextNodeName);
nodeName.put("curNode", curNodeName);
nodeName.put("beforeNode",beforeNodeName) ;
return nodeName;
}
/**
*
* 功能:将即将从inn中请出的node放入ret中
* 作者:EugeneJao
* @param inn
* @param ret
* @param curNodeName
*/
private void record(Map<String,Map<String,Object>> inn , Map<String,Map<String,Object>> ret , String curNodeName){
Map<String,Object> curNode = inn.get(curNodeName);
ret.put(curNodeName, curNode);
}
/**
*
* 功能:在有向图中获取下一节点名称在relList里的索引值
* 作者:EugeneJao
* @param curNode
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
private int getNextNodeIndexInDigraphForSeek(Map<String,Object> curNode) throws Exception{
if(!(curNode.get("isFrozen") instanceof Boolean)){
throw new Exception("节点"+ curNode.get("nodeName") +
"参数类型异常:isFrozen合法类型为布尔类型,isFrozen:"+ curNode.get("isFrozen"));
}
/**
* 如果当前节点状态为冻结,怎么说明它与它的子节点之前都被遍历过了,
* 为了让这次能够再次遍历,设置状态为非冻结状态,同时返回索引值0
*/
if((Boolean) curNode.get("isFrozen")){
curNode.put("isFrozen", false);
return 0 ;
}
List<String> relList = (List<String>) curNode.get("relList") ;
int curRelIndex = (Integer) curNode.get("curRelIndex") ;
String beforeNodeName = (String) curNode.get("beforeNode");
curRelIndex ++ ;
/**
* 如果curRelIndex大于relList的大小
* 则说明当前节点的关系节点全都遍历过了,curRelIndex赋值为relList的大小,
* 即说明当前节点的关系节点全都遍历过了,不能前进了,只能后退
*/
if(curRelIndex > relList.size()){
curRelIndex = relList.size() ;
}
return curRelIndex ;
}
测试代码如下:
public static void main(String [] args) throws Exception{
Map<String,Map<String,Object>> nodeCollections = new HashMap<String, Map<String,Object>>() ;
Map<String,Object> node_1 = new HashMap<String, Object>() ;
String nodeName_1 = "1" ;
List<String> relList_1 = new ArrayList<String>() ;
relList_1.add("2");
relList_1.add("3");
int curRelIndex_1 = -1 ;
String beforeNode_1 = null ;
node_1.put("nodeName", nodeName_1) ;
node_1.put("curRelIndex", curRelIndex_1) ;
node_1.put("beforeNode", beforeNode_1) ;
node_1.put("relList", relList_1) ;
node_1.put("isFrozen", false);
node_1.put("isRoot", false);
Map<String,Object> node_2 = new HashMap<String, Object>() ;
String nodeName_2 = "2" ;
List<String> relList_2 = new ArrayList<String>() ;
relList_2.add("4");
int curRelIndex_2 = -1 ;
String beforeNode_2 = null ;
node_2.put("nodeName", nodeName_2) ;
node_2.put("curRelIndex", curRelIndex_2) ;
node_2.put("beforeNode", beforeNode_2) ;
node_2.put("relList", relList_2) ;
node_2.put("isFrozen", false);
node_2.put("isRoot", false);
Map<String,Object> node_3 = new HashMap<String, Object>() ;
String nodeName_3 = "3" ;
List<String> relList_3 = new ArrayList<String>() ;
relList_3.add("4");
int curRelIndex_3 = -1 ;
String beforeNode_3 = null ;
node_3.put("nodeName", nodeName_3) ;
node_3.put("curRelIndex", curRelIndex_3) ;
node_3.put("beforeNode", beforeNode_3) ;
node_3.put("relList", relList_3) ;
node_3.put("isFrozen", false);
node_3.put("isRoot", false);
Map<String,Object> node_4 = new HashMap<String, Object>() ;
String nodeName_4 = "4" ;
List<String> relList_4 = new ArrayList<String>() ;
relList_4.add("5");
relList_4.add("6");
int curRelIndex_4 = -1 ;
String beforeNode_4 = null ;
node_4.put("nodeName", nodeName_4) ;
node_4.put("curRelIndex", curRelIndex_4) ;
node_4.put("beforeNode", beforeNode_4) ;
node_4.put("relList", relList_4) ;
node_4.put("isFrozen", false);
node_4.put("isRoot", false);
Map<String,Object> node_5 = new HashMap<String, Object>() ;
String nodeName_5 = "5" ;
List<String> relList_5 = new ArrayList<String>() ;
int curRelIndex_5 = -1 ;
String beforeNode_5 = null ;
node_5.put("nodeName", nodeName_5) ;
node_5.put("curRelIndex", curRelIndex_5) ;
node_5.put("beforeNode", beforeNode_5) ;
node_5.put("relList", relList_5) ;
node_5.put("isFrozen", false);
node_5.put("isRoot", false);
Map<String,Object> node_6 = new HashMap<String, Object>() ;
String nodeName_6 = "6" ;
List<String> relList_6 = new ArrayList<String>() ;
relList_6.add("7");
int curRelIndex_6 = -1 ;
String beforeNode_6 = null ;
node_6.put("nodeName", nodeName_6) ;
node_6.put("curRelIndex", curRelIndex_6) ;
node_6.put("beforeNode", beforeNode_6) ;
node_6.put("relList", relList_6) ;
node_6.put("isFrozen", false);
node_6.put("isRoot", false);
Map<String,Object> node_7 = new HashMap<String, Object>() ;
String nodeName_7 = "7" ;
List<String> relList_7 = new ArrayList<String>() ;
relList_7.add("5");
int curRelIndex_7 = -1 ;
String beforeNode_7 = null ;
node_7.put("nodeName", nodeName_7) ;
node_7.put("curRelIndex", curRelIndex_7) ;
node_7.put("beforeNode", beforeNode_7) ;
node_7.put("relList", relList_7) ;
node_7.put("isFrozen", false);
node_7.put("isRoot", false);
nodeCollections.put("1",node_1 );
nodeCollections.put("2",node_2 );
nodeCollections.put("3",node_3 );
nodeCollections.put("4",node_4 );
nodeCollections.put("5",node_5 );
nodeCollections.put("6",node_6 );
nodeCollections.put("7",node_7 );
GraphAnalysis analysis = new GraphAnalysis() ;
long start = System.currentTimeMillis();
/*boolean result = analysis.isContainLoop(nodeCollections);*/
List<String> nodes = new ArrayList<String>();
nodes.add("1");
nodes.add("4");
nodes.add("7");
nodes.add("5");
Map<String,Map<String,Object>> ret = analysis.seekRelation(nodeCollections, nodes);
System.out.println(ret);
/*boolean result = analysis.isContainLoopInDigraph(nodeCollections);
System.out.println(result);*/
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) );
}
如有BUG,欢迎大家提出,谢谢!