所谓节点网络指的是任一节点与其他若干节点之间存在直接的联系,从而形成一种网络结果,在数学和计算机科学领域中也被称为图。其中闭环又称为回路,指的的是某一节点顺着关系链既是关系链的起点又是关系链的终点。即A->B->C->D->A 如图所示
为了判断节点网络中是否存在闭环,本文采用的基本思想是逐步遍历整个网络,每次访问某个节点都将改节点相关信息存储在一个哈希结构的集合里(这里使用HashMap),并最终检测该集合中是否有重复的来达到检查网络中是否含有闭环的情况。
本次实现的网络节点数据结构如下表所示:
nodeName: | String |
relList: | List |
curRelIndex: | int |
beforeNode: | String |
代码实现如下,本人测试了节点为4的情况,由于本人时间有限,不能测试更多的情况,如果以下程序有bug,欢迎各位在评论里提出,谢谢。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class GraphAnalysis {
/**
*
* 功能:判断网络中是否存在闭环
* 作者:EugeneJao
* @param nodeCollections
* @return
* @throws Exception
*/
public boolean isContainLoop(Map<String,Map<String,Object>> nodeCollections) throws Exception{
//用map的hash码计算,速度更快
Map<String,String> visitedList = new HashMap<String, String>();
/**
* 初始化"起点
*/
String startNode = getOrigin(nodeCollections);
boolean containLoop = false ;
/**
* 初始化"视野"
*/
Map<String,String> nodeName = new HashMap<String, String>() ;
nodeName.put("nextNode", startNode);
nodeName.put("curNode", startNode);
nodeName.put("beforeNode", startNode);
int count = 0 ;
/**
* 如果当前不含有闭环,并且没有遍历完起点节点的所有分支则进入循环
*/
while(!containLoop
&& !(nodeName.get("beforeNode").equals(nodeName.get("curNode"))
&& nodeName.get("nextNode") == null )){
nodeName = traverse(nodeCollections, nodeName, visitedList);
if(count > 1){
containLoop = containSameNode(visitedList,nodeName.get("nextNode"));
}
count ++ ;
}
return containLoop;
}
/**
*
* 功能:随机获取起始节点
* 作者:EugeneJao
* @param nodeCollections
* @return
*/
@SuppressWarnings("rawtypes")
private String getOrigin(Map<String,Map<String,Object>> nodeCollections){
Set keySet = nodeCollections.entrySet();
Iterator it = keySet.iterator() ;
List<String> keyArr = new ArrayList<String>();
while(it.hasNext()){
Entry tmpSet = (Entry) it.next() ;
keyArr.add((String) tmpSet.getKey()) ;
}
int randomInteger = (int) (Math.random() * keyArr.size()) ;
return keyArr.get(randomInteger) ;
}
/**
*
* 功能:访问某个网络节点
* 作者:EugeneJao
* @param nodeCollections
* @param nodeName
* @param visitedList
* @return
* @throws Exception
*/
private Map<String,String> traverse(Map<String,Map<String,Object>> nodeCollections , Map<String,String> nodeName ,
Map<String, String> visitedList) 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);
}
String nextNodeName = nodeName.get("nextNode") ;
if( nextNodeName == null){
nodeName = back(nodeCollections, nodeName);
}else{
nodeName = head(nodeCollections, nodeName, visitedList);
}
return nodeName ;
}
/**
*
* 功能:前进至下一个节点
* 作者:EugeneJao
* @param nodeCollections
* @param nodeName
* @param visitedList
* @return
*/
@SuppressWarnings("unchecked")
private Map<String,String> head(Map<String,Map<String,Object>> nodeCollections , Map<String,String> nodeName ,
Map<String, String> visitedList){
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 = getNextNodeIndex(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);
/**
* 更新visitedList信息
*/
visitedList.put(curNodeName,null) ;
return nodeName;
}
/**
*
* 功能:后退至之前的节点
* 作者:EugeneJao
* @param nodeCollections
* @param nodeName
* @return
*/
@SuppressWarnings("unchecked")
private Map<String,String> back(Map<String,Map<String,Object>> nodeCollections , Map<String, String> nodeName){
String curNodeName = nodeName.get("beforeNode") ; //传入参数的beforeNode在本方法中即是当前节点
Map<String,Object> 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 = getNextNodeIndex(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;
}
<span style="white-space:pre"> </span>/**
<span style="white-space:pre"> </span>*
<span style="white-space:pre"> </span>* 功能:检测是否存在环路
<span style="white-space:pre"> </span>* 作者:EugeneJao
<span style="white-space:pre"> </span>* @param visitedList
* @param curNodeName
* @return
*/
private boolean containSameNode(Map<String, String> visitedList,String curNodeName){
if(visitedList.containsKey(curNodeName)){
return true ;
}
return false;
}
/**
*
* 功能:获取下一节点名称在relList里的索引值
* 作者:EugeneJao
* @param curNode
* @return
*/
@SuppressWarnings("unchecked")
private int getNextNodeIndex(Map<String,Object> curNode){
List<String> relList = (List<String>) curNode.get("relList") ;
int curRelIndex = (Integer) curNode.get("curRelIndex") ;
String beforeNodeName = (String) curNode.get("beforeNode");
String nextNodeName = beforeNodeName ;
//判断是否可以进入while循环,条件一:上一节点名称是否可下一节点名称相同,条件二:数组是否越界
boolean canAdd = beforeNodeName.equals(nextNodeName) && curRelIndex < relList.size()-1 ;
while(canAdd){
curRelIndex ++ ;
nextNodeName = relList.get(curRelIndex) ;
canAdd = beforeNodeName.equals(nextNodeName) && curRelIndex < relList.size()-1;
}
/**
* 若果经过前面运算后,前一节点名称和下一节点名称依然相同,
* 则说明当前节点的关系节点全都遍历过了,curRelIndex赋值为relList的大小,
* 即说明当前节点的关系节点全都遍历过了,不能前进了,只能后退
*/
if(beforeNodeName.equals(nextNodeName) || relList.size() == 0){
curRelIndex = relList.size() ;
}
return curRelIndex ;
}
}