TreeView – 如何统计所有孩子(包括折叠)

有没有一种方法来获取TreeView对象中的子项数? 我想把所有的孩子,包括孩子的孩子,都计算在内。

getExpandedItemCount()方法仅获取展开的子项的子计数。 有没有办法计算所有孩子的数量,无论他们是否扩大。

这个答案中的解决方案对于仅计算小树中的节点来说是过度的。

其他答案中的简单递归计数解决方案很好。 这个答案仅用于添加更多上下文和备用实现。

关于堆栈与递归

使用递归时,您隐式依赖Java运行时来为您维护一堆项目。 对于非常大的树,这可能是一个问题,因为运行时可能会耗尽堆栈空间(堆栈溢出)。

有关优先于递归的堆栈的更多信息,请参阅:

  • John Skeet对Java的回答:有限递归中的Stackoverflow 。
  • 顶级编码器树遍历算法,深度优先搜索 。

当然,如果你知道你正在处理的树很小,可以使用递归。 有时递归算法比非递归算法更容易理解。

基于迭代器的解决方案

 private class TreeIterator implements Iterator> { private Stack> stack = new Stack<>(); public TreeIterator(TreeItem root) { stack.push(root); } @Override public boolean hasNext() { return !stack.isEmpty(); } @Override public TreeItem next() { TreeItem nextItem = stack.pop(); nextItem.getChildren().forEach(stack::push); return nextItem; } } 

迭代器用于计算树中项目的示例用法。

 TreeIterator iterator = new TreeIterator<>(rootItem); int nItems = 0; while (iterator.hasNext()) { nItems++; iterator.next(); } 

如果需要, 可以通过创建自定义流支持类来使迭代器适应流 ,这允许您编写function代码,例如:

 TreeItemStreamSupport.stream(rootItem) .filter(TreeItem::isExpanded) .count() 

示例程序

反图像

 import javafx.application.Application; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.VBox; import javafx.stage.Stage; import java.util.*; import java.util.stream.*; public class TreeViewSample extends Application { // limits on randomly generated tree size. private static final int MAX_DEPTH = 8; private static final int MAX_CHILDREN_PER_NODE = 6; private static final double EXPANSION_PROPABILITY = 0.2; public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Label numItemsLabel = new Label(); // create a tree. TreeItem rootItem = TreeFactory.createTree( MAX_DEPTH, MAX_CHILDREN_PER_NODE, EXPANSION_PROPABILITY ); rootItem.setExpanded(true); TreeView tree = new TreeView<>(rootItem); numItemsLabel.setText( "Num Items: " + countExpandedItemsUsingStream(rootItem) ); // display the number of items and the tree. VBox layout = new VBox(10, numItemsLabel, tree); layout.setPadding(new Insets(10)); stage.setScene(new Scene(layout, 300, 250)); stage.show(); } // unused method demonstrating alternate solution. private long countItemsUsingIterator(TreeItem rootItem) { TreeItemIterator iterator = new TreeItemIterator<>(rootItem); int nItems = 0; while (iterator.hasNext()) { nItems++; iterator.next(); } return nItems; } private long countExpandedItemsUsingStream(TreeItem rootItem) { return TreeItemStreamSupport.stream(rootItem) .filter(TreeItem::isExpanded) .count(); } // unused method demonstrating alternate Jens-Peter Haack solution. private long countItemsUsingRecursion(TreeItem node) { int count = 1; for (TreeItem child : node.getChildren()) { count += countItemsUsingRecursion(child); } return count; } /** * Random Tree generation algorithm. */ private static class TreeFactory { private static final Random random = new Random(42); static TreeItem createTree( int maxDepth, int maxChildrenPerNode, double expansionProbability ) { TreeItem root = new TreeItem<>("Root 0:0"); Stack itemStack = new Stack<>(); itemStack.push(new DepthTreeItem(root, 0)); while (!itemStack.isEmpty()) { int numChildren = random.nextInt(maxChildrenPerNode + 1); DepthTreeItem nextItem = itemStack.pop(); int childDepth = nextItem.depth + 1; for (int i = 0; i < numChildren; i++) { TreeItem child = new TreeItem<>( "Item " + childDepth + ":" + i ); child.setExpanded(random.nextDouble() < expansionProbability); nextItem.treeItem.getChildren().add(child); if (childDepth < maxDepth) { itemStack.push(new DepthTreeItem(child, childDepth)); } } } return root; } static class DepthTreeItem { DepthTreeItem(TreeItem treeItem, int depth) { this.treeItem = treeItem; this.depth = depth; } TreeItem treeItem; int depth; } } } /** * Provide a stream of tree items from a root tree item. */ class TreeItemStreamSupport { public static  Stream> stream(TreeItem rootItem) { return asStream(new TreeItemIterator<>(rootItem)); } private static  Stream> asStream(TreeItemIterator iterator) { Iterable> iterable = () -> iterator; return StreamSupport.stream( iterable.spliterator(), false ); } } /** * Iterate over items in a tree. * The tree should not be modified while this iterator is being used. * * @param  the type of items stored in the tree. */ class TreeItemIterator implements Iterator> { private Stack> stack = new Stack<>(); public TreeItemIterator(TreeItem root) { stack.push(root); } @Override public boolean hasNext() { return !stack.isEmpty(); } @Override public TreeItem next() { TreeItem nextItem = stack.pop(); nextItem.getChildren().forEach(stack::push); return nextItem; } } 

有一个很好的理由不提供计算树的所有子项的方法,因为扩展的树大小可能非常大或甚至无限。

例如:可以显示“真实”数字的所有数字的树:

 static class InfiniteNumberItem extends TreeItem { boolean expanded = false; public InfiniteNumberItem(String name) { super(name); } @Override public ObservableList> getChildren() { if (!expanded) { for (int i = 0; i < 10; i++) { super.getChildren().add(new InfiniteNumberItem(""+i)); } expanded = true; } return super.getChildren(); } @Override public boolean isLeaf() { return false; } } void testTreeInfinite(VBox box) { TreeView tree = new TreeView(); tree.prefHeightProperty().bind(box.heightProperty()); tree.setRoot(new InfiniteNumberItem("3.")); box.getChildren().add(tree); } 

但是如果你知道你做了什么,并且如果树的大小有限,你必须按自己的数量计算:

 int count(TreeItem node) { int count = 1; for (TreeItem child : node.getChildren()) { count += count(child); } return count; } 

使用递归,如下所示:

 private static  long countChildren(TreeItem treeItem) { long count = 0; if (treeItem != null) { ObservableList> children = treeItem.getChildren(); if (children != null) { count += children.size(); for (TreeItem child : children) { count += countChildren(child); } } } return count; } 

要在计数器中包含根,请添加1:

 long count = countChildren(treeItem) + 1; 

然后只需调用以root为参数的方法:

 System.out.println(countChildren(treeView.getRoot()));