/******************************************************************************* * Copyright (c) 2011 Dipanjan Das * Language Technologies Institute, * Carnegie Mellon University, * All Rights Reserved. * * TreeNode.java is part of SEMAFOR 2.0. * * SEMAFOR 2.0 is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SEMAFOR 2.0 is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with SEMAFOR 2.0. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package edu.cmu.cs.lti.ark.util.ds.graph; import java.util.ArrayList; import java.util.List; import java.util.Set; import edu.cmu.cs.lti.ark.util.ds.Pair; import gnu.trove.TIntArrayList; import gnu.trove.TIntHashSet; /** * Node in a tree structure. * The root node should have index 0 and no parent; all other nodes have index > 0 and one parent. * * @param T Node type; same as the name of the subclass being defined (proxy for SELF_TYPE) * @author Nathan Schneider (nschneid) * @since 2009-04-04 */ public abstract class TreeNode<T extends TreeNode<T>> extends RootedDAGNode<T> { private static final long serialVersionUID = -5923423841213967909L; public TreeNode() { } public void setParentIndex(int i) { List<Integer> pIndices = new ArrayList<Integer>(1); pIndices.add(i); try { setParentIndices(pIndices); } catch (GraphException gex) { ; } } public int getParentIndex() { if (parentIndices==null || parentIndices.size()==0) return -1; return getParentIndices().get(0); } public T getParent() { if (parents==null || parents.size()==0) return null; return parents.get(0); } public void setParent(T p) { List<T> plist = new ArrayList<T>(1); if (p!=null) plist.add(p); try { setParents(plist); } catch (GraphException gex) { ; } } /** * @throws GraphException if attempting to add multiple parents */ public void setParents(List<T> p) throws GraphException { if (p!=null && p.size()>1) throw new GraphException("Multiple parents are not allowed in TreeNode"); super.setParents(p); } /** * @throws GraphException if attempting to add indices for multiple parents */ public void setParentIndices(List<Integer> p) throws GraphException { if (p!=null && p.size()>1) throw new GraphException("Multiple parents are not allowed in TreeNode"); super.setParentIndices(p); } public void setChildren(List<T> c) { try { super.setChildren(c); } catch (GraphException gex) { ; } } /** * @param includeSelf If true, the current node will be included in the list, along with its descendants * @return A list of all descendants, in no particular order */ @SuppressWarnings("unchecked") public List<T> getDescendants(boolean includeSelf) { List<T> nodeList = new ArrayList<T>(); if (includeSelf) nodeList.add((T)this); if (!this.hasChildren()) return nodeList; List<T> plist = this.getChildren(); for (T node : plist) nodeList.addAll(node.getDescendants(true)); return nodeList; } /** * @return List of indices of ancestor nodes, starting with the current node and ending with the shallowest ancestor (root of the tree). * @author Nathan Schneider (nschneid) */ public TIntArrayList getAncestry() { TIntArrayList ancestry = new TIntArrayList(); T eldest = getAncestorAtLevel(0, ancestry); ancestry.add(eldest.index); return ancestry; } public T getAncestorAtLevel(int alevel) { return getAncestorAtLevel(alevel, null); } public T getAncestorAtLevel(int alevel, TIntArrayList ancPath) { return getAncestorAtLevel(alevel, ancPath, null); } /** * * @param alevel Level (depth) of the desired ancestor * @param ancPath A list to be populated with indices of nodes searched (starting with the current node), provided that an ancestor node at the specified level exists; or null. * @param alreadySearched Indices of nodes which have already been searched. If non-null, 'this' will be returned if (and only if) a member of this set is encountered in the ancestor path. * @return Ancestor node of the current node whose level is 'alevel', or null if no such ancestor exists. A node is not considered to be its own ancestor. * @author Nathan Schneider (nschneid) */ @SuppressWarnings("unchecked") public T getAncestorAtLevel(int alevel, TIntArrayList ancPath, TIntHashSet alreadySearched) { if (alevel < 0 || alevel >= this.depth) // A node at this level is not strictly an ancestor return null; TreeNode<T> node = this; for (int d=this.depth; d>alevel; d--) { if (ancPath!=null) ancPath.add(node.index); if (alreadySearched!=null && alreadySearched.contains(node.index)) return (T)this; node = node.getParent(); } return (T)node; } public boolean isAncestor(T anc) { return isAncestor(anc, null); } /** * * @param anc Alleged ancestor node * @param ancPath A list in which to store the indices of nodes that have been searched, or null * @return true if 'anc' is an ancestor of this node, otherwise false. A node is not considered its own ancestor. * @author Nathan Schneider (nschneid) */ public boolean isAncestor(T anc, TIntArrayList ancPath) { T trueAncestor = getAncestorAtLevel(anc.depth, ancPath); return (trueAncestor==anc); } public T nearestCommonAncestorWith(T that, TIntHashSet searched) { return nearestCommonAncestorWith(that, searched, null); } /** * * @param that Second node * @param searched If non-null, a list that will be populated with indices of nodes that have been searched * @param alreadySearched If non-null, 'this' will be returned as soon as any ancestors of 'that' are encountered whose indices belong to this set * @return The nearest (deepest) common ancestor node between 'this' and 'that': either 'this', 'that', some third node, or null (if they have * no common ancestor, though this should not occur if it is a tree) * @author Nathan Schneider (nschneid) */ @SuppressWarnings("unchecked") public T nearestCommonAncestorWith(T that, TIntHashSet searched, TIntHashSet alreadySearched) { if (alreadySearched!=null && alreadySearched.contains(that.index)) return (T)this; TreeNode<T> n1 = this; T n2 = that; if (this.depth < that.depth) { TIntArrayList ancPath = new TIntArrayList(); n1 = this; n2 = that.getAncestorAtLevel(this.depth, ancPath, alreadySearched); if (searched!=null) searched.addAll(ancPath.toNativeArray()); if (alreadySearched!=null && (n2==that || alreadySearched.contains(n2.index))) return (T)this; } else if (that.depth < this.depth) { TIntArrayList ancPath = new TIntArrayList(); n1 = that; n2 = this.getAncestorAtLevel(that.depth, ancPath); if (searched!=null) searched.addAll(ancPath.toNativeArray()); } while (n1 != n2) { if (searched!=null) { searched.add(n1.index); searched.add(n2.index); } n1 = n1.getParent(); n2 = n2.getParent(); } return (T)n1; } public static <T extends TreeNode<T>> T nearestCommonAncestor( Set<T> nodes) { return nearestCommonAncestor(nodes, null); } /** * * @param nodes A set of nodes in a tree * @param searched If non-null, a list which will be populated with indices of the nodes that have been searched * @return The nearest (deepest) common ancestor among a set of nodes, which may belong to that set; or null if there is no common ancestor (should not occur if a tree) * @author Nathan Schneider (nschneid) */ public static <T extends TreeNode<T>> T nearestCommonAncestor(Set<T> nodes, TIntHashSet searched) { TIntHashSet alreadySearched = searched; if (searched==null) alreadySearched = new TIntHashSet(); T best = null; int i = 0; for (T next : nodes) { if (i++==0) { best = next; continue; } TIntHashSet srched = new TIntHashSet(); best = best.nearestCommonAncestorWith(next, srched, alreadySearched); if (best==null) break; alreadySearched.addAll(srched.toArray()); } return best; } /** * @param <T> TreeNode subtype * @param from Origin node in the tree * @param to Destination node in the tree * @return Path from the origin to the destination, represented as a list of pairs with the node in the path * and the direction of traversal: "^" for moving upwards (to the parent) and "!" for moving downwards (to a child). * The first node in this list is 'from', marked with "="; the last node in the list is 'to'. */ public static <T extends TreeNode<T>> List<Pair<String,T>> getPath(T from, T to) { List<Pair<String,T>> result = new ArrayList<Pair<String,T>>(); result.add(new Pair<String,T>("=",from)); if (to==from) return result; T nca = from.nearestCommonAncestorWith(to, null); { T node = from; while (node.getDepth()>nca.getDepth()) { node = node.getParent(); result.add(new Pair<String,T>("^",node)); } if (node!=nca) { System.err.println("Error finding path between tree nodes (1)"); System.exit(1); } } { List<Pair<String,T>> tail = new ArrayList<Pair<String,T>>(); T node2 = to; while (node2.getDepth()>nca.getDepth()) { tail.add(0, new Pair<String,T>("!",node2)); node2 = node2.getParent(); } if (node2!=nca) { System.err.println("Error finding path between tree nodes (2)"); System.exit(1); } result.addAll(tail); } return result; } }