Java 如何检测集合中的循环

Java 如何检测集合中的循环,java,graph-theory,Java,Graph Theory,与标准JavaSet类似,但更多 我有一门课技能,它有必备技能 @Data @Entity public class Skill { @Id @GeneratedValue private UUID id; @OneToMany private Set<Skill> prerequisites = new HashSet<>(); private String name; } @数据 @实体 公开课技能{ @身份证 @

与标准Java
Set类似,但更多

我有一门课技能,它有必备技能

@Data
@Entity
public class Skill {
    @Id
    @GeneratedValue
    private UUID id;

    @OneToMany
    private Set<Skill> prerequisites = new HashSet<>();
    private String name;

}
@数据
@实体
公开课技能{
@身份证
@生成值
私有UUID;
@独身癖
私有集先决条件=新HashSet();
私有字符串名称;
}
我想确保先决条件中没有循环

这是我开始的,当然它不起作用,因为我只处理自我循环

@UtilityClass
public class CycleChecks {
    /**
     * Take a root object and a function to get its edges to see if there are any cycles.
     *
     * @param root  root object
     * @param edges a function that would take an object and get its edges.
     * @param <T>   an object that has edges
     * @return has a cycle.
     */
    public <T> boolean isCyclic(T root, Function<T, Iterable<T>> edges) {
        final Set<T> visited = new HashSet<>();
        return doIsCyclic(root, edges, visited);
    }

    private <T> boolean doIsCyclic(T vertex, Function<T, Iterable<T>> edges, Set<T> visited) {
        if (visited.contains(vertex)) {
            return true;
        }
        visited.add(vertex);
        for (T edgeTarget : edges.apply(vertex)) {
            if (doIsCyclic(edgeTarget, edges, visited)) {
                return true;
            }
        }
        return false;
    }
}
@UtilityClass
公共类自行车{
/**
*取一个根对象和一个函数来获取它的边,看看是否有循环。
*
*@param根对象
*@param edges一个函数,它将获取一个对象并获取其边。
*@param有边的对象
*@return有一个循环。
*/
公共布尔是循环的(T根,函数边){
访问的最终集=新HashSet();
返回doiscycle(根、边、访问);
}
私有布尔DoisCycle(T顶点、函数边、集合){
如果(已访问。包含(顶点)){
返回true;
}
添加(顶点);
对于(T edgeTarget:边。应用(顶点)){
if(doIsCyclic(edgeTarget、edges、visitored)){
返回true;
}
}
返回false;
}
}

下面这样的测试很好,但没有彻底测试。只有一个列表保存ID,我们可能会遇到这样的情况:多个单独的技能具有相同的先决条件,并且被错误地检测为一个循环。这在这里发生,例如:,所以使用第二个递归列表

您可以使用以下命令调用它:
hasCycle(yourFirstSkill,new ArrayList(),new ArrayList())

publicstaticbooleanhascycle(技能输入、访问列表、列表递归){
UUID currentId=entry.getId();
if(recursion.contains(currentId))
返回true;
if(已访问。包含(当前ID))
返回false;
已访问。添加(当前ID);
recursion.add(currentId);
对于(最终技能先决条件:entry.getpremissions()){
if(hasCycle(先决条件、访问、递归)){
返回true;
}
}
递归.remove(currentId);
返回false;
}

我的最终解决方案基于@Shadov的答案。只是做了一些调整,使其通用,我使用了
Set
for
visitored
recursion
而不是列表

@UtilityClass
public class CycleChecks {
    /**
     * Take a root object and a function to get its edges to see if there are any cycles.
     *
     * @param root             root object
     * @param adjacentFunction a function that would take an object and return an iterable of objects that are adjacent to it.
     * @param <T>              an object that has edges
     * @return has a cycle.
     */
    public <T> boolean isCyclic(T root, Function<T, Iterable<T>> adjacentFunction) {
        final Set<T> visited = new HashSet<>();
        final Set<T> recursion = new HashSet<>();
        return doIsCyclic(root, adjacentFunction, visited, recursion);
    }

    private <T> boolean doIsCyclic(T current, Function<T, Iterable<T>> adjacentFunction, Set<T> visited, Set<T> recursion) {
        if (recursion.contains(current)) {
            return true;
        }
        if (visited.contains(current)) {
            return false;
        }
        visited.add(current);
        recursion.add(current);
        for (T adjacent : adjacentFunction.apply(current)) {
            if (doIsCyclic(adjacent, adjacentFunction, visited, recursion)) {
                return true;
            }
        }
        recursion.remove(current);
        return false;
    }

}
@UtilityClass
公共类自行车{
/**
*取一个根对象和一个函数来获取它的边,看看是否有循环。
*
*@param根对象
*@param adjacentFunction获取对象并返回与其相邻的对象的iterable的函数。
*@param有边的对象
*@return有一个循环。
*/
公共布尔是循环的(T根,函数邻接ntf函数){
访问的最终集=新HashSet();
最终集递归=新HashSet();
返回doIsCyclic(根、邻接函数、已访问、递归);
}
私有布尔doIsCyclic(T当前、函数邻接ntfunction、访问集、递归集){
if(递归包含(当前)){
返回true;
}
if(已访问。包含(当前)){
返回false;
}
已访问。添加(当前);
递归。添加(当前);
对于(T相邻:邻接函数。应用(当前)){
if(doIsCyclic(相邻、邻接函数、访问、递归)){
返回true;
}
}
递归。删除(当前);
返回false;
}
}
通过消除递归的非科学方法,速度稍微快了5毫秒

@UtilityClass
public class CycleChecks {
    /**
     * Take a root object and a function to get its edges to see if there are any cycles.
     *
     * @param root             root object
     * @param adjacentFunction a function that would take an object and return an iterable of objects that are adjacent to it.
     * @param <T>              an object that has edges
     * @return has a cycle.
     */
    public <T> boolean isCyclic(T root, Function<T, Iterable<T>> adjacentFunction) {
        final Set<T> visited = new HashSet<>();
        final Deque<T> recursion = new LinkedList<>();
        final Deque<Node<T>> nodesToVisit = new LinkedList<>();
        final Node<T> popRecursionNode = new Node<>();
        nodesToVisit.add(new Node<>(root));

        while (!nodesToVisit.isEmpty()) {
            final Node<T> frontOfQueue = nodesToVisit.pop();
            if (frontOfQueue.isPopRecursion()) {
                recursion.pop();
                continue;
            }
            T current = frontOfQueue.getObj();
            if (recursion.contains(current)) {
                return true;
            }
            if (visited.contains(current)) {
                continue;
            }
            visited.add(current);
            recursion.push(current);
            for (T adjacent : adjacentFunction.apply(current)) {
                nodesToVisit.add(new Node<>(adjacent));
            }
            nodesToVisit.add(popRecursionNode);

        }
        return false;
    }

    @Data
    private static class Node<T> {
        private final T obj;
        private final boolean popRecursion;

        public Node(T obj) {
            this.obj = obj;
            popRecursion = false;
        }

        public Node() {
            obj = null;
            popRecursion = true;
        }
    }
@UtilityClass
公共类自行车{
/**
*取一个根对象和一个函数来获取它的边,看看是否有循环。
*
*@param根对象
*@param adjacentFunction获取对象并返回与其相邻的对象的iterable的函数。
*@param有边的对象
*@return有一个循环。
*/
公共布尔是循环的(T根,函数邻接ntf函数){
访问的最终集=新HashSet();
final Deque recursion=new LinkedList();
final Deque nodesToVisit=新建LinkedList();
最终节点popRecursionNode=新节点();
添加(新节点(根));
而(!nodesToVisit.isEmpty()){
最终节点frontOfQueue=nodesToVisit.pop();
if(frontOfQueue.isPopRecursion()){
recursion.pop();
持续
}
T current=frontOfQueue.getObj();
if(递归包含(当前)){
返回true;
}
if(已访问。包含(当前)){
持续
}
已访问。添加(当前);
递归推(当前);
对于(T相邻:邻接函数。应用(当前)){
添加(新节点(相邻));
}
添加(popRecursionNode);
}
返回false;
}
@资料
私有静态类节点{
私人终审法院;
私有最终布尔递归;
公共节点(T obj){
this.obj=obj;
popRecursion=false;
}
公共节点(){
obj=null;
popRecursion=true;
}
}

乍一看,这个链接问题似乎解决了您在这里要做的事情。您能详细说明一下在将它转换为您的环境时遇到的困难吗?基本上,链接问题使用数组,我只是使用集合。没有任何区别。您需要查找“拓扑排序”。只需使用tes即可ted你的方法。我在这个测试技能中失败了skill1=新技能();skill2=新技能