topo排序&判环实操

项目场景:

有一张订单表,和订单关系表,订单关系表中维护订单与订单之间的父子关系(多对多)。
现在需要根据目标订单id去查询到目标订单关系的完整链路与链路中订单信息集合,并且判断链路是否有成环现象。


问题描述

这里我遇到的问题是,在维护订单关系时,因为多对多的原因,不会是一个简单的单链形式,也因为维护时并没有做过多的限制,导致订单与订单之间的链路会比较混乱,无法按照层级去判断,且会衍生出很多分支,这样就导致了我无法按照传统的树形结构来看待这个链路。

比如:

父订单
父订单
父订单
父订单
父订单
父订单
A
B
C
D
E

如果说不用考虑是否成环的情况,我们直接查询关系表拿到链路再根据链路中的订单id去查到订单集合就ok了,但是,需要判断链路是否成环,并且将成环的订单给出标识。下图:

父订单
父订单
父订单
父订单
父订单
父订单
父订单
A
B
C
D
E

原因分析:

以A、C、E来看的话,C的父订单关联了C的子订单,导致链路成环,那是不是可以认为,查询订单的父订单,不能为查询过的子订单集合

那以这个思路去思考的话,我们是不是可以查询目标订单,将查询订单的父订单存入fatherSet,子订单存入sonSet,每次查询都去判断一下关联的父订单是否在sonSet中,关联的子订单是否在fatherSet中,如果不存在则将父订单加入fatherSet,子订单加入sonSet,继续递归查询。如果存在则认为这个订单链路成环了,并标记该订单。

这个思路看似可以满足上图的场景,但是如果我们将A、E之间的关系去掉的时候:

父订单
父订单
父订单
父订单
父订单
父订单
A
B
C
D
E

很明显并没有成环,但是按照上述逻辑去走的话,当我们查询到订单B时:
fatherSet=[C, D],sonSet=[A]
我们继续递归查询订单CD,这个时候:
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;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值