The problem is not exponential, it is linear but linear in the number of ancestors of the person: there is no way to guarantee that John is not an ancestor of Mary without examining all of Mary's ancestors.
It even reduces this by eliminating already examined ancestors (the same person may appear more than once as ancestral - marriage between cousins, for example), at the expense of linear space in the number of ancestors. >
The easiest thing to do is use a queue to resolve this:
import java.util.*;
public boolean isAncestor(Person p) {
if (p == null) return false;
final Queue<Person> notChecked = new LinkedList<Person>();
final Set<Person> checked = new HashSet<Person>();
if (father != null) notChecked.add(father);
if (mother != null) notChecked.add(mother);
while (notChecked.peek() != null) {
final Person nextPerson = notChecked.remove();
if (!checked.contains(nextPerson)) {
if (nextPerson == p) return true;
checked.add(nextPerson);
if (nextPerson.father != null) notChecked.add(nextPerson.father);
if (nextPerson.mother != null) notChecked.add(nextPerson.mother);
}
}
return false;
}
Another possibility is to use a stack, based on ArrayList
. Class ArrayList
is much more efficient than LinkedList
in terms of space and speed, but the implementation is slightly more complicated:
public boolean isAncestor(Person p) {
if (p == null) return false;
final List<Person> notChecked = new ArrayList<Person>();
final Set<Person> checked = new HashSet<Person>();
if (father != null) notChecked.add(father);
if (mother != null) notChecked.add(mother);
while (notChecked.size() > 0) {
final int index = notChecked.size() - 1;
final Person nextPerson = notChecked.remove(index);
if (!checked.contains(nextPerson)) {
if (nextPerson == p) return true;
checked.add(nextPerson);
if (nextPerson.father != null) notChecked.add(nextPerson.father);
if (nextPerson.mother != null) notChecked.add(nextPerson.mother);
}
}
return false;
}
Pretty much the same, but much faster.
The first version will soon find nearby ancestors - the second version, for example, will search all the ancestors of the mother before looking for the father. The two versions consume the same effort if the person is not ancestral, the second one will be much faster and consume less memory.