具有节点过滤的Java通用树遍历

时间:2018-02-14 21:16:18

标签: java algorithm tree tree-traversal

我有一个通用树结构。 我需要一个算法来遍历它,并删除一些叶子,如果它们不包含在给定的列表中。如果从子树中删除了所有叶子,则也删除整个子树。

示例树:

         0
       / |  \
      1  2   3
    /  \ |  / \
   4   5 6  7  8

要留下的叶子:{4,6}

结果树:

         0
       / | 
      1  2 
    /    | 
   4     6  

输入数据结构包含在HashMap中,其中键是节点的父ID,值是直接在父节点下的节点列表(但不是递归所有子节点)。根节点的父ID是空字符串。

Map<String, List<Node>> nodes;

class Node {
    private String id;
    //other members
    //getters, setters
}

我想,某种递归DFS遍历算法应该可行,但我找不到它,如何。

5 个答案:

答案 0 :(得分:2)

  

我想,某种递归DFS遍历算法应该可以工作

这是完全正确的。以下是构建此算法的方法:

  • 观察该任务具有递归结构:将其应用于树的任何分支对分支执行与您想要对整个树执行的操作相同的事情
  • 分支可以修剪或完全删除
  • 你的递归实现会返回一个修剪过的分支;它会通过返回null
  • 来表示删除分支
  • 递归函数会检查传入的Node
  • 如果节点代表一个叶子,则将根据我们希望保留的项目列表检查其内容
  • 如果“保持列表”中没有叶子,请返回null;否则返回叶子
  • 对于非叶子分支,请调用递归方法,并检查其结果
  • 如果结果为null,请从地图中删除相应的分支;否则,用通过调用返回的已修剪分支替换分支
  • 如果检查所有分支时子节点的映射为空,则返回null

请注意,如果没有叶节点与“keep”列表匹配,则算法可以返回null。如果这不合适,请在递归实现之上添加额外的级别,并在顶层替换null返回并返回空树。

答案 1 :(得分:2)

我建议你尝试以下方法:

方法boolean removeRecursively(String id, Set<String> leavesToKeep)将从具有给定id的节点遍历到此分支离开。

首先,我们检查一下是不是一片叶子。如果叶子不在leavesToKeep集合中,我们会将其删除并返回true,否则返回false。这是我们递归的基本情况。

如果节点不是叶子,那么我们这样做:

children.removeIf(n -> removeRecursively(n.id, leavesToKeep));

removeIf是一种方便的Java 8方法,用于删除满足给定谓词的所有元素。这意味着只有在删除了所有子项时,才会从列表中删除子项。因此,如果在removeRecursively调用后children.removeIf列表为空,我们应该children返回true:

if (children.isEmpty()) {
    tree.remove(id);
    return true;
} else return false;

完整方法可能如下所示:

public static boolean removeRecursively(Map<String, List<Node>> tree, String id, Set<String> leavesToKeep) {
    List<Node> children = tree.get(id);
    if (children == null || children.isEmpty()) {
        if (!leavesToKeep.contains(id)) {
            tree.remove(id);
            return true;
        } else return false;
    }
    children.removeIf(n -> removeRecursively(tree, n.id, leavesToKeep));
    if (children.isEmpty()) {
        tree.remove(id);
        return true;
    } else return false;
}

其中tree是您描述的地图,id是起始节点ID,leavesToKeep是要保留的一组ID。

答案 2 :(得分:0)

使用界面树:

public static interface Tree<T> {
    public T getValue();

    public List<Tree<T>> children();

    public default boolean isLeaf() {
        return children().isEmpty();
    }

    public default boolean removeDeadBranches(Predicate<T> testLiveLeaf) {
        if (isLeaf()) {
            return testLiveLeaf.test(getValue());
        }
        boolean remainLife = false;
        for (Iterator<Tree<T>> it = children().iterator(); it.hasNext();) {
            if (it.next().removeDeadBranches(testLiveLeaf)) {
                remainLife = true;
            } else {
                it.remove();
            }
        }
        return remainLife;
    }
}

答案 3 :(得分:0)

import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

public class FilterTreeNode {
    private Logger logger = LoggerFactory.getLogger(FilterTreeNode.class);
    private TreeNode node0;
    private List<String> targetNode = Lists.newArrayList("B1","D1");

    @Before
    public void init(){
        node0 = TreeNode.builder().nodeCode("0").nodeName("A").build();
        TreeNode node1 = TreeNode.builder().nodeCode("1").nodeName("B").build();
        TreeNode node2 = TreeNode.builder().nodeCode("2").nodeName("C").build();
        TreeNode node3 = TreeNode.builder().nodeCode("3").nodeName("D").build();

        TreeNode node4 = TreeNode.builder().nodeCode("4").nodeName("B1").build();
        TreeNode node5 = TreeNode.builder().nodeCode("5").nodeName("B2").build();
        TreeNode node6 = TreeNode.builder().nodeCode("6").nodeName("C1").build();
        TreeNode node7 = TreeNode.builder().nodeCode("7").nodeName("D1").build();
        TreeNode node8 = TreeNode.builder().nodeCode("8").nodeName("D2").build();

        node1.setChildren(Lists.newArrayList(node4,node5));
        node2.setChildren(Lists.newArrayList(node6));
        node3.setChildren(Lists.newArrayList(node7,node8));

        node0.setChildren(Lists.newArrayList(node1,node2,node3));
    }

    @Test
    public void filterTest(){
        logger.info("before filter node0: {}",node0);
        List<TreeNode> retNodes = filterNode(node0);
        if (retNodes.size() >0){
            node0.setChildren(retNodes);
        }
        logger.info("after filter node0: {}",node0);
    }

    private List<TreeNode> filterNode(TreeNode treeNode){

        List<TreeNode> nodes = treeNode.getChildren();
        List<TreeNode> newNodes = Lists.newArrayList();
        List<TreeNode> tagNodes = Lists.newArrayList();

        for(TreeNode node : nodes){
            if (targetNode.contains(node.getNodeName())){
                newNodes.add(node);
            }
            if (node.getChildren() != null && node.getChildren().size() >0){
                List<TreeNode> retNodes = filterNode(node);
                if (retNodes.size()>0){
                    node.setChildren(retNodes);
                }else {
                    node.setChildren(null);
                    tagNodes.add(node);
                }
            }
        }
        nodes.removeAll(tagNodes);
        return newNodes;
    }
}

答案 4 :(得分:0)

对于任何寻求与树@Kiril相似的答案而不是地图的人:

树/节点类:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Tree {

    private TreeNode node;

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public static class TreeNode {
        private int id;
        private boolean isMatch;
        private List<TreeNode> nodes;
    }
}

和过滤方法:

public Tree filter(Tree tree, List<Integer> ids) {
    if (tree.getNode() == null) {
        return tree;
    }
    filterNode(tree.getNode(), ids);

    return tree;
}

private boolean filterNode(Tree.TreeNode node, List<Integer> idsToFilter) {
    boolean isMatch = idsToFilter.contains(node.getId());
    node.setMatch(isMatch);
    if (CollectionUtils.isEmpty(node.getNodes()) && !isMatch) {
        return true;
    }

    node.getNodes().removeIf(treeNode -> filterNode(treeNode, idsToFilter));

    return node.getNodes().size() == 0 && !isMatch;
}