The problem is not exponential, it is linear, but it is linear in the number of people’s ancestors: there is no guarantee that John is not Mary’s ancestor without examining all of Mary’s ancestors.
You can even reduce this a little by eliminating already examined ancestors (the same person may appear more than once as an ancestor -- marriage between cousins, for example), at the expense of linear space in the number of ancestors.
The easiest is to use a queue to solve 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
. The class ArrayList
is much more efficient than the LinkedList
in terms of space and speed, but the implementation gets 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;
}
Practically the same, but much faster.
The first version will more quickly find close ancestors -- the second version, for example, will search all the ancestors of the mother before searching for the father. The two versions consume the same effort if the person is not ancestral, and the second will be much faster and consume less memory.
In the Fibonacci sequence there are many repeating elements (ex.:
X
andX-1
both useX-2
in its calculation), which allows a linear time calculation. This is not the case in a family tree, where the set of ancestors may have little or no repetition.– mgibsonbr