如何检测集合中的周期

时间:2019-06-18 22:13:11

标签: java graph-theory

类似于How to find if a graph has a cycle?,但更多适用于标准Java Set

我有一个具有必备技能的班级技能。

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

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

}

我想确保前提条件中没有循环。

这是我刚开始时使用的,因为我只真正处理了自拍,所以这是行不通的。

@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;
    }
}

2 个答案:

答案 0 :(得分:1)

如下所示的情况很好,没有对它进行彻底的测试。仅使用一个保留ID的列表,我们可能会遇到以下情况:多个单独的技能具有相同的先决条件,并且被错误地检测为一个周期。例如,在这里发生这种情况:keycloak aggregated policies.,因此使用第二个递归列表。

您用以下名称调用:hasCycle(yourFirstSkill, new ArrayList<>(), new ArrayList<>());

public static boolean hasCycle(Skill entry, List<UUID> visited, List<UUID> recursion) {
  UUID currentId = entry.getId();

  if (recursion.contains(currentId))
    return true;

  if (visited.contains(currentId))
    return false;

  visited.add(currentId);
  recursion.add(currentId);

  for (final Skill prerequisite : entry.getPrerequisites()) {
    if (hasCycle(prerequisite, visited, recursion)) {
      return true;
    }
  }

  recursion.remove(currentId);

  return false;
}

答案 1 :(得分:0)

基于@Shadov的答案,我的最终解决方案。做了一些调整使其通用,我将Setvisited的{​​{1}}而不是列表中使用。

recursion

通过非科学方法消除了递归,将速度提高了5ms。

@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;
    }

}