将arraylist递归地排成树

时间:2014-02-05 06:29:39

标签: java recursion tree treemap treeset

我有一个Object的arraylist。但我需要一个具有以下参数的树:

  1. 某些对象不应该有子(非类别对象)
  2. 类别对象应该有子级(可以是其他类别对象,或非类别对象)
  3. 应跳过/删除没有孩子的类别对象
  4. 所有对象都有一个父变量的变量。但我没有一个好的算法可以递归地确定每个类别对象有多少个孩子。同一级别的所有孩子都是ArrayList<Object>

    我目前有一个只能深入一级的迭代方法,并且无法区分方案2的某些版本。我不认为它的代码是相关的,因为它不是递归的。

    洞察力

3 个答案:

答案 0 :(得分:3)

由于我经常使用你所描述的树结构,我前段时间创建了一个虚拟实现。我稍微更改了代码以使其更加通用并根据您的需要进行更新,随意在您认为合适的情况下充分利用它。

树元素的“id”是通用的,因此您可以将任何数据用作元素的ID。 在main方法中记录了一个示例用法,它创建了一些随机树结构。生成树后,您可以使用Visitor pattern验证并导航树。

以下是代码:

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;

public class TestStructure {

public interface ElementVisitor<E>{
    boolean startVisit(Element<E> e);
    void endVisit(Element<E> e);
}

public interface Element<E>{
    E getId();
    public boolean isParent();
    Parent<E> getParent();
    void visit(ElementVisitor<E> visitor);
}

public interface Parent<E> extends Element<E>{
    List<Element<E>> getChildren();
    int size();
}

public static abstract class ElementImpl<E> implements Element<E>{
    private final E id;
    private ParentImpl<E> parent;

    public ElementImpl(E id) {
        super();
        if (id == null) {
            throw new IllegalArgumentException("id must not be null");
        }
        this.id = id;
    }

    void setParent(ParentImpl<E> parent) {
        this.parent = parent;
    }
    @Override
    public final ParentImpl<E> getParent() {
        return parent;
    }
    protected final void addToParent(){
        parent.addChild(this);
    }
    protected final void removeFromParent(){
        parent.removeChild(this);
    }
    public final boolean isTreeRoot(){
        return parent == null;
    }

    @Override
    public String toString() {
        return getId().toString();
    }

    public int getTreeLevel() {
        int level = 0;
        ParentImpl<E> parent = this.parent;
        while (parent != null) {
            parent = parent.getParent();
            level++;
        }
        return level;
    }

    public ParentImpl<E> getRoot(){
        ElementImpl<E> e = this;
        while (e.parent != null) {
            e = e.parent;
        }
        return (ParentImpl<E>) e;
    }

    public E getId() {
        return id;
    }
}

public static class ChildImpl<E> extends ElementImpl<E> implements Element<E>{

    public ChildImpl(E id) {
        super(id);
    }

    @Override
    public void visit(ElementVisitor<E> visitor) {
        visitor.startVisit(this);
        visitor.endVisit(this);
    }

    @Override
    void setParent(ParentImpl<E> parent) {
        super.setParent(parent);
        // add childs to parent automatically
        addToParent();
    }
    @Override
    public boolean isParent() {
        return false;
    }
}

public static class ParentImpl<E> extends ElementImpl<E> implements Parent<E>{
    private List<ElementImpl<E>> children;
    boolean added = false;

    public ParentImpl(E id) {
        super(id);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<Element<E>> getChildren() {
        return children == null ? Collections.EMPTY_LIST : children;
    }

    public void addChild(ElementImpl<E> child){
        if (children == null) {
            children = new ArrayList<>();
        }
        if (getParent() != null && children.size() == 0) {
            // only include parents in the tree if they have children
            addToParent();
            added = true;
        }
        children.add(child);
    }

    public boolean removeChild(ElementImpl<E> e) {
        if (children != null && children.remove(e)) {
            if (children.size() == 0) {
                children = null;
                removeFromParent();
                added = false;
            }
        }
        return false;
    }

    @Override
    void setParent(ParentImpl<E> parent) {
        super.setParent(parent);
        if (children != null && !added) {
            addToParent();
            added = true;
        }
    }

    @Override
    public int size() {
        return children == null ? 0 : children.size();
    }

    @Override
    public void visit(ElementVisitor<E> visitor) {
        if (visitor.startVisit(this)) {
            for (Element<E> e : getChildren()) {
                e.visit(visitor);
            }
        }
        visitor.endVisit(this);
    }

    @Override
    public boolean isParent() {
        return true;
    }

}


public static void main(String[] args) {

    // elements below parentRange are considered parents
    int parentRange = 80;

    // The number of all elements in the tree
    int numberOfElements = 150;

    Map<Integer, Integer> relationMap = new HashMap<>(); // map from child id to parent id
    Random random = new Random();

    // id 0 == root id
    // create an initial parent element at the root
    relationMap.put(1, 0);
    // create the parent elements
    for (int id = 2; id < parentRange; id++) {
        int parentId = random.nextInt(id-1);
        relationMap.put(id, parentId);
    }

    // create the child elements
    for (int id = parentRange; id < numberOfElements; id++) {
        int parentId = random.nextInt(parentRange);
        relationMap.put(id, parentId);
    }

    HashMap<Integer, ParentImpl<Integer>> map = new HashMap<>();

    final ParentImpl<Integer> treeRoot = new ParentImpl<>(0);
    map.put(0, treeRoot);
    // create the tree structure
    for (Entry<Integer, Integer> entry : relationMap.entrySet()) {
        Integer childId = entry.getKey();
        Integer parentId = entry.getValue();

        ParentImpl<Integer> parentImpl = map.get(parentId);
        if (parentImpl == null) {
            parentImpl = new ParentImpl<>(parentId);
            map.put(parentId, parentImpl);
        }

        ElementImpl<Integer> child;
        if (childId < parentRange) {
            // this child is a parent
            child = map.get(childId);
            if (child == null) {
                ParentImpl<Integer> p = new ParentImpl<>(childId);
                child = p;
                map.put(childId, p);
            }
        } else{
            child = new ChildImpl<>(childId);
        }
        child.setParent(parentImpl);

    }

    // count the number of elements in the tree
    class Counter  implements ElementVisitor<Integer> {
        int count = 0;
        @Override
        public boolean startVisit(Element<Integer> e) {
            count++;
            return true;
        }
        @Override
        public void endVisit(Element<Integer> e) {}
    };

    Counter counter = new Counter();
    treeRoot.visit(counter);
    System.out.println("Number of all elements in the tree: " + counter.count);

    // validate the tree structure
    ElementVisitor<Integer> treeValidator = new ElementVisitor<Integer>() {
        @Override
        public boolean startVisit(Element<Integer> e) {
            if (!e.getId().equals(0) && e.getParent() == null) {
                throw new IllegalStateException("child with no parent...");
            }
            if (e.isParent()) {
                Parent<Integer> parent = (Parent<Integer>)e;
                if (parent.size() == 0) {
                    throw new IllegalStateException("parent with no children...");
                }
            }
            return true;
        }

        @Override
        public void endVisit(Element<Integer> e) {}
    };
    treeRoot.visit(treeValidator);

    final StringBuilder b = new StringBuilder();
    // print the tree structure
    ElementVisitor<Integer> treePrinter = new ElementVisitor<Integer>() {
        int level = 0;
        @Override
        public boolean startVisit(Element<Integer> e) {
            for (int i = 0; i < level; i++) {
                b.append("  ");
            }
            if (e.isParent()) {
                b.append("Parent ");
                level++;
            } else{
                b.append("Child ");
            }
            b.append(e.getId());
            b.append('\n');
            return true;
        }

        @Override
        public void endVisit(Element<Integer> e) {
            if (e.isParent()) {
                level--;
            }
        }
    };
    treeRoot.visit(treePrinter);
    System.out.println("The generated tree structure: ");
    System.out.println(b.toString());
}
}

答案 1 :(得分:3)

使用Composite Pattern为所有对象提供通用界面。

接口或抽象超类 Component 表示可能具有关系的所有对象。一个子类 Leaf 没有任何子节点。 (可能还有其他非复合的Leaf类子类。)另一个子类 Composite 包含许多对象,通常是任何类型的Component。这允许Composite包含其他复合材料,或其他非复合材料,如Leaf。

我在这里创建的Component超类是ComponentObject。在此,所有ComponentObjects都有一个父CategoryObject,在下面介绍。

<强> ComponentObject

public abstract class ComponentObject
{
   private CategoryObject myParent;

   protected ComponentObject(CategoryObject parent)
   {
      myParent = parent;
   }

   public CategoryObject getParent()
   {
      return myParent;
   }

   public abstract int getNumChildren();
}

它有两个具体的子类NonCategoryObject,Leaf不能包含子节点,CategoryObject,Composite,它封装了一个子对象列表,可以是其他CategoryObject个或NonCategoryObject s。

<强> NonCategoryObject

public class NonCategoryObject extends ComponentObject
{
   public NonCategoryObject(CategoryObject parent)
   {
      super(parent);
   }

   @Override
   public int getNumChildren()
   {
      return 0;
   }
}

<强> CategoryObject

public class CategoryObject extends ComponentObject
{
   private ArrayList<ComponentObject> myChildren;

   public CategoryObject(CategoryObject parent)
   {
      super(parent);
      myChildren = new ArrayList<ComponentObject>();
   }

   public void addChild(ComponentObject child)
   {
      myChildren.add(child);
   }

   public ComponentObject getChild(int index)
   {
      return myChildren.get(index);
   }

   public int getNumDirectChildren()
   {
      return myChildren.size();
   }

   @Override
   public int getNumChildren()
   {
      int numChildren = 0;
      for (ComponentObject child : myChildren)
      {
         // One for the child itself, plus add any of the child's children.
         numChildren += 1 + child.getNumChildren();
      }
      return numChildren;
   }
}

“getNumChildren”方法对于通过复合模式查找子项数非常重要。

设置数据

你有ArrayList个组件,每个组件都知道他们的父母,但不知道他们的孩子是谁:

public static void main(String[] args)
{
   // Create a tree structure, parent information only.
   CategoryObject root = new CategoryObject(null);
   CategoryObject categoryOne = new CategoryObject(root);
   CategoryObject categoryTwo = new CategoryObject(root);
   CategoryObject categoryThree = new CategoryObject(root);
   CategoryObject categoryOneOne = new CategoryObject(categoryOne);
   CategoryObject categoryOneTwo = new CategoryObject(categoryOne);
   CategoryObject categoryOneThree = new CategoryObject(categoryOne);
   NonCategoryObject leafOneFour = new NonCategoryObject(categoryOne);
   NonCategoryObject leafOneOneOne = new NonCategoryObject(categoryOneOne);
   NonCategoryObject leafOneOneTwo = new NonCategoryObject(categoryOneTwo);
   NonCategoryObject leafOneTwoOne  = new NonCategoryObject(categoryOneTwo);
   NonCategoryObject leafOneThreeOne  = new NonCategoryObject(categoryOneThree);
   NonCategoryObject leafTwoOne = new NonCategoryObject(categoryTwo);
   NonCategoryObject leafThreeOne = new NonCategoryObject(categoryThree);

   // We're given the ArrayList
   ArrayList<ComponentObject> components = new ArrayList<ComponentObject>();
   // The order doesn't matter.
   components.addAll(Arrays.asList(leafOneFour, leafOneOneTwo, leafOneThreeOne,
      categoryOne, categoryOneOne, categoryOneThree, root, leafThreeOne,
      leafOneOneOne, categoryThree, leafOneTwoOne, leafTwoOne,
      categoryTwo, categoryOneTwo));

通过循环遍历列表来确定子信息。我们也会找到根(假设只有一个无父组件)。

   ComponentObject foundRoot = null;
   for (ComponentObject c : components)
   {
      CategoryObject parent = c.getParent();
      if (parent == null)
      {
         foundRoot = c;
      }
      else
      {
         parent.addChild(c);
      }
   }

现在所有的父母都知道他们的孩子是谁。接下来,我们将调用两种不同的方法,每种方法都有自己确定子项数的方法:

   // 2 methods to determine the number of children.
   compositeMethod(foundRoot);
   recursiveMethod(foundRoot);
}

方法

以下是方法。首先,“复合”方法利用复合模式来确定子项数。它只是调用root的getNumChildren()方法。

private static void compositeMethod(ComponentObject root)
{
   int numChildren = root.getNumChildren();
   System.out.println("Composite method: " + numChildren);
}

输出:

Composite method: 13

复合方法不是很递归,因为即使它调用自身,它调用自身的对象也是不同的。它把自己称为对象的孩子。

您感兴趣的递归方法在层次结构中的每个级别调用自身:

private static void recursiveMethod(ComponentObject root)
{
   int numChildren = findNumChildren(root);
   System.out.println("Recursive method: " + numChildren);
}

// The actual recursive method.
private static int findNumChildren(ComponentObject root)
{
   if (root instanceof CategoryObject)
   {
      CategoryObject parent = (CategoryObject) root;
      int numChildren = 0;
      for (int i = 0; i < parent.getNumDirectChildren(); i++)
      {
         ComponentObject child = parent.getChild(i);
         // One for the child itself, plus its children.
         numChildren += 1 + findNumChildren(child);
      }
      return numChildren;
   }

   // Base case: NonCategoryObjects have no children.
   return 0;
}

输出:

Recursive method: 13

答案 2 :(得分:1)

我遇到了类似的问题但是在Javascript中。有人用递归算法为我回答:

Convert parent-child array to tree

它可能并不完美,但希望它会给你一个起点。