简体   繁体   中英

Non-binary tree implementation with cyclic checks

I've been looking for a good non-binary tree implementation that matches my needs but haven't found one.

I need a non-binary tree, in which the number of children is arbitrary, and that can be traversed. This tree is built by user input so I need to have a cyclic check. Other functionality includes removing nodes (and their children), traversal to get the children of a node (and the children of children) and adding children (and other trees).

Examples of implementations I found online include using the firstchild-nextsibling method and parent-child links to a node. The problem with the firstchild-nextsibling method is adding trees and nodes to a tree. The parent-child method seems reasonable but I haven't found any implementation that adds whole trees as children and has cyclic checks.

An example of such implementation:

     A
   /   \
  U     W

The user then chooses to create another tree:

  B
 /  \
X    R

and then adds B as a child of W. The full tree would be:

    A
  /   \
 U      W
         \
           B
          /  \
         X     R

If there's a better way to implement this data structure then I would be glad to hear it, because I can't think of anything else. Any help would be very appreciated. Thank you!

EDIT: Some code I wrote.

public class TreeNode<T> {

private T data;
private TreeNode<T> firstChild;
private TreeNode<T> nextSibling;
private TreeNode<T> parent;
public TreeNode(T data) {
    this.data = data;
}

public boolean isRoot() {
    return this.parent == null;
}
public boolean isaLeaf() {
    return this.firstChild == null;
}

public TreeNode<T> getFirstChild(){
    return firstChild;
}
public void addChild(TreeNode<T> child) {
    child.parent = this;
    if (this.firstChild == null) {
        this.firstChild = child;
    } else {
        child.nextSibling = firstChild;
        firstChild = child;
    }
}

public TreeNode<T> getParent(){
    return parent;
}

public TreeNode<T> getNextSibling() {
    return nextSibling;
}

public T getData() {
    return data;
}

}

EDIT 2: My tree allows for the addition of similar nodes, but what it doesn't allow for is creating an infinite cycle. An example of such a cycle would be adding W as a child of R. I was thinking of having each level as a linked list for easier sorting, but I'm not sure if that makes any sense. Any thoughts?

You can add cyclic check to the addChild method:

public void addChild(TreeNode<T> child) {
    if (!child.isRoot()) {
        throw new IllagalArgumentException();
    }
    TreeNode myRoot = this;
    while (!myRoot.isRoot()) {
        myRoot = myRoot.parent();
    }
    if (child == myRoot) {
        throw new IllagalArgumentException();
    }
    // now we can safely set parent
    child.parent = this;

I assume that your intention is for the data structure to be a tree, and not a DAG (directed acyclic graph). Here are some properties that distinguish trees from graphs.

  • In a tree:

    • no node has more than one parent,
    • (exactly) one node is the root of the tree: the root node has no parent, and
    • for each child c of a node n, n == c.parent()
  • In a graph or DAG, some nodes may have more than one parent and there may be multiple root nodes, or no root nodes.

Clearly in pure data structure terms, the same representational types can represent a tree, a graph, or a DAG, depending on how you put them together. Therefore, to ensure that your data structure is a tree, you need to maintain the properties above. This can be done by restricting the transformation you can perform, and placing some preconditions on them. The following should be enough:

  • Add node n1 as a child of node n2:

    • pre: n1 must be a root node; ie n1.parent == null
    • pre: n2 must part of a different tree to n1; ie root(n2) != n1
    • action: set n1.parent = n2
    • action: add n1 to n2.children
    • post: parent(n1) = n2 which means that root(n1) == root(n2)
  • Remove node n1 from parent node n2:

    • pre: n1.parent == n2; ie n1 is not a root
    • pre: n2.children contains n1
    • action: set n1.parent to null
    • action: remove n1 from p2.children
    • post: n1 is a root node.

As you can see, the only slightly tricky thing that you need to do is to check that n1 and n2 don't (already) have the same root before adding one to the other; this can be done using a simple recursive helper function:

  TreeNode root(TreeNode n) {
      return (n.parent == null) ? n : root(n.parent);
  }

then

  void add(TreeNode n1, Tree node n2) {
      if (root(n1) != n1 || n1 == root(n2)) {
          throw new BadTreeTransformation(...);
      }

If you restrict the user interface so that all tree transformations are composed of insert and remove operations as above, then the tee invariants will be maintained.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM