9 Chapter 9 Solutions¶
Solution to Challenge 1¶
A binary search tree is a tree where every left child is less than its parent, and every right child is greater than or equal to its parent.
An algorithm that verifies whether a tree is a binary search tree involves going through all the nodes and checking for this property.
Write the following in your project. You’ll need access to the BinaryNode
class that you created in Chapter 8.
import 'binary_node.dart';
extension Checker<E extends Comparable<dynamic>> on BinaryNode<E> {
bool isBinarySearchTree() {
return _isBST(this, min: null, max: null);
}
bool _isBST(BinaryNode<E>? tree, {E? min, E? max}) {
// 1
if (tree == null) return true;
// 2
if (min != null && tree.value.compareTo(min) < 0) {
return false;
} else if (max != null && tree.value.compareTo(max) >= 0) {
return false;
}
// 3
return _isBST(tree.leftChild, min: min, max: tree.value) &&
_isBST(tree.rightChild, min: tree.value, max: max);
}
}
isBinarySearchTree
is the method that you’ll expose for external use. Meanwhile, the magic happens in _isBST
, which is responsible for recursively traversing through the tree and checking that the BST rules are followed. It needs to keep track of progress via a reference to a BinaryNode
, and also keep track of the min
and max
values to verify the BST rules. Here are the details:
- This is the base case. If
tree
isnull
, then there are no nodes to inspect. Anull
node is a binary search tree, so you’ll returntrue
in that case. - This is essentially a bounds check. If the current value exceeds the bounds of
min
andmax
, the current node violates binary search tree rules. - This statement contains the recursive calls. When traversing through the left children, the current value is passed in as the
max
value. This is because any nodes on the left side cannot be greater than the parent. Conversely, when traversing to the right, themin
value is updated to the current value. Any nodes on the right side must be greater than or equal to the parent. If any of the recursive calls evaluatefalse
, thefalse
value will propagate back to the top.
The time complexity of this solution is O(n) since you need to traverse through the entire tree once. There is also an O(n) space cost since you’re making nrecursive calls.
Solution to Challenge 2¶
Testing equality is relatively straightforward. For two binary trees to be equal, both trees must have the same elements in the same order. Here’s what the solution looks like:
bool treesEqual(BinarySearchTree first, BinarySearchTree second) {
return _isEqual(first.root, second.root);
}
// 1
bool _isEqual(BinaryNode? first, BinaryNode? second) {
// 2
if (first == null || second == null) {
return first == null && second == null;
}
// 3
return first.value == second.value &&
_isEqual(first.leftChild, second.leftChild) &&
_isEqual(first.rightChild, second.rightChild);
}
The commented numbers refer to the following notes:
_isEqual
will recursively check two nodes and their descendants for equality.- This is the base case. If one or more of the nodes are
null
, then there’s no need to continue checking. If both nodes arenull
, they’re equal. Otherwise, one isnull
and one isn’tnull
, so they must not be equal. - Here, you check the value of the first and second nodes for equality. You also recursively check the left children and right children for equality.
The time complexity of this function is O(n). The space complexity of this function is also O(n).
Note
Trees are mutable and testing for equality on mutably data structures is an inherently tricky business. That’s why this solution didn’t have you override the ==
operator and hashCode
method on BinarySearchTree
.
Solution to Challenge 3¶
Your goal is to create a method that checks if the current tree contains all the elements of another tree. In other words, the values in the current tree must be a superset of the values of the other tree. Here’s what the solution looks like:
extension Subtree<E> on BinarySearchTree {
bool containsSubtree(BinarySearchTree subtree) {
// 1
Set set = <E>{};
root?.traverseInOrder((value) {
set.add(value);
});
// 2
var isEqual = true;
// 3
subtree.root?.traverseInOrder((value) {
isEqual = isEqual && set.contains(value);
});
return isEqual;
}
}
- You begin by inserting all the elements of the current tree into a set.
isEqual
is there to store the end result. You need this becausetraverseInOrder
takes a closure, and you can’t directly return from inside the closure.- For every element in the subtree, you check if the set contains the value. If at any point
set.contains(value)
evaluates asfalse
, you’ll make sureisEqual
staysfalse
even if subsequent elements evaluate astrue
.
The time and space complexity for this algorithm is O(n).