项目场景:
有一张订单表,和订单关系表,订单关系表中维护订单与订单之间的父子关系(多对多
)。
现在需要根据目标订单id去查询到目标订单关系的完整链路与链路中订单信息集合,并且判断链路是否有成环
现象。
问题描述
这里我遇到的问题是,在维护订单关系时,因为多对多的原因,不会是一个简单的单链形式,也因为维护时并没有做过多的限制,导致订单与订单之间的链路会比较混乱,无法按照层级去判断,且会衍生出很多分支,这样就导致了我无法按照传统的树形结构来看待这个链路。
比如:
如果说不用考虑是否成环的情况,我们直接查询关系表拿到链路再根据链路中的订单id去查到订单集合就ok了,但是,需要判断链路是否成环,并且将成环的订单给出标识。下图:
原因分析:
以A、C、E来看的话,C的父订单关联了C的子订单,导致链路成环,那是不是可以认为,查询订单的父订单,不能为查询过的子订单集合
。
那以这个思路去思考的话,我们是不是可以查询目标订单,将查询订单的父订单存入fatherSet
,子订单存入sonSet
,每次查询都去判断一下关联的父订单是否在sonSet
中,关联的子订单是否在fatherSet
中,如果不存在则将父订单加入fatherSet
,子订单加入sonSet
,继续递归查询。如果存在则认为这个订单链路成环了,并标记该订单。
这个思路看似可以满足上图的场景,但是如果我们将A、E之间的关系去掉的时候:
很明显并没有成环,但是按照上述逻辑去走的话,当我们查询到订单B时:
fatherSet
=[C
, D
],sonSet
=[A
]
我们继续递归查询订单C
和D
,这个时候:
fatherSet
=[C
, D
, E
],sonSet
=[A
, B
]
再次递归,查询订单E
,会有一个子订单关系指向C
,这个时候判断fatherSet
的时候就会返回ture
,很明显这个结果不是我们预期的。
解决方案:
提示:有向无环图(Directed Acyclic Graph简称DAG)、拓扑排序、判环
这是一个很明显的有向无环图
案例,并且在我们已经能拿到完整链路的情况下,我们可以使用topo排序
再根据排序结果进行是否成环的判断,有关于topo排序详细的介绍借鉴了【图论】拓扑排序&判环
主要思路还是按照拓扑排序的逻辑来,找到链路中所有度数为0的顶点,这里我理解为只有一个方向指向的节点
,然后我们把链路中这些顶点删除,再重复上面操作,就像修剪树枝编织的头环一样,最后只有两种结果,一种是树枝根本就没有被编织成头环,一剪就没了;一种是经过耐心的修剪后,的到了一个没有分叉的漂亮头环。
下面是我实践的代码:
public MxgraphData getOrdFrdCrd(String id) {
MxgraphData mxgraphData = new MxgraphData();
mxgraphData.setNodesList(new ArrayList<>());
mxgraphData.setEdgesList(new ArrayList<>());
List<String> ids = new ArrayList<>();
Set<String> idSet = new HashSet<>();
ids.add(id);
idSet.add(id);
// 得到关系链路
getEdgesList(ids, mxgraphData.getEdgesList(), idSet);
if (mxgraphData.getEdgesList().size() > 0) {
// 根据链路得到订单集合
getNodesList(mxgraphData.getNodesList(), mxgraphData.getEdgesList());
// topo排序判断链路是否成环
List<OrdRelation> ring = new ArrayList<>(mxgraphData.getEdgesList());
topoSort(ring);
if (ring.size() > 0) {
Set<String> crdSet = ring.stream().map(OrdRelation::getId).collect(Collectors.toSet());
Set<String> frdSet = ring.stream().map(OrdRelation::getDirectionId).collect(Collectors.toSet());
Set<String> ringIds = new HashSet<>(frdSet);
ringIds.addAll(crdSet);
for (ApsOrdVO apsOrdVO : mxgraphData.getNodesList()) {
if (ringIds.contains(apsOrdVO.getId())) {
apsOrdVO.setStatus(1);
}
}
}
} else {
mxgraphData.getNodesList().addAll(apsOrdMapper.queryByIds(new ArrayList<>(ids)));
}
return mxgraphData;
}
public void topoSort(List<OrdRelation> ring) {
// 遍历集合,寻找度数0的顶点
Set<String> crdSet = ring.stream().map(OrdRelation::getId).collect(Collectors.toSet());
Set<String> frdSet = ring.stream().map(OrdRelation::getDirectionId).collect(Collectors.toSet());
// 求frdSet与crdSet的差集为度数0的顶点
Collection c = new HashSet<>(crdSet);
c.retainAll(frdSet);
crdSet.addAll(frdSet);
crdSet.removeAll(c);
if (crdSet.size() > 0 && ring.size() > 0) {
// 存在度数为0的顶点,删除所有和它有关的边
ring.removeIf(o -> crdSet.contains(o.getId()) || crdSet.contains(o.getDirectionId()));
if (ring.size() > 0) {
topoSort(ring);
} else {
// 所有顶点输出,无环
return;
}
}
// 不存在顶点,成环
return;
}