Trees
Trees
1 | class BinaryTreeNode: |
Binary Search Tree
A binary search tree node consists of:
- \(key: \;\) value of the node
- \(left: \;\) left children
- \(right: \;\) right children
- \(p: \;\) parent
If a child or the parent is missing, the appropriate attribute contains the value None
. The root node is the only node in the tree whose parent is None
.
The keys in a binary search tree are always stored in such a way as to satisfy the binary search tree property
:
For any node \(x\), the keys in the left subtree of \(x\) are at most \(x.key\), and the keys in the right subtree of \(x\) are at least \(x.key\).
The binary search tree property allows us to print out all the keys in a binary search tree in sorted order by inorder tree walk
(Inorder traversal). This algorithm prints the key of the root of a subtree betweeen printing the values in its left subtree and printing those in its right subtree. Similarly, a preorder tree walk
prints the root before the values in either subtree and a postorder tree walk
prints the root after the values in its subtrees.
Inorder Traversal
1 | def inorder_tree_walk(tree): |
Preorder Traversal
1 | def preorder_tree_walk(tree): |
Postorder Traversal
1 | def postorder_tree_walk(tree): |
Search
We use the following procedure to search for a node with a given key in a binary search tree. Given a pointer to the root of the tree and a key \(k\), it returns a pointer to a node with key \(k\) if one exists, otherwise it returns None
.
1 | def tree_search(tree, k): |
Minimum
We can always find an element in a binary search tree whose key is a minimum by following \(left\) child pointers from the root until we encounter a None
. The following procedure returns a pointer to the minimum element in the subtree rooted at a given node \(x\):
1 | def tree_minimum(tree): |
Maximum
The maximum is symmetric as minimum:
1 | def tree_maximum(tree): |
Successor and Predecessor
Given a node in a binary search tree, sometimes we need to find its successor in the sorted order determined by an inorder tree walk. If all keys are distinct, the successor of a node \(x\) is the node with the smallest key greater than \(x.key\). The procedure breaks into several cases:
- If node \(x\) has right subtree, then the successor must be the smallest element in the right subtree.
- If node \(x\) does not have right subtree and itself is a leftsubtree of its parent, then the successor must be its parent.
- If node \(x\) does not have right subtree and itself is a rightsubtree of its parent, then the successor must be the lowest ancestor of \(x\) whose left child is also an ancestor of \(x\).
1 | def tree_successor(node): |
Predecessor is symmetric
Insertion
To insert a new node \(z\) into a binary search tree \(T\) we use the following procedure:
- Begin at root of the tree, we maintain two pointers \(y, x\) representing parent and current node respectively.
- Go down the tree and compare every node encounter with \(z\), if it is greater than \(z\) we go to the left, if smaller we go to the right, until we reach
None
. - We replace \(y.left\) or \(y.right\) with \(x\) and fill \(z.p = y\).
1 | def tree_insert(T, z): |
Deletion
The overall strategy for deleting a node \(z\) from a binary search tree \(T\) has three basic cases:
- If \(z\) has no children, then we can simply remove it by modifying its parent to replace \(z\) with
None
as its child. - If \(z\) has just on child, then we elevate that child to take \(z\)'s position in the tree by modifying \(z\)'s parent to replace \(z\) by \(z\)'s child.
- If \(z\) has two children, then we find \(z\)'s successor \(y\) which must be the minimum item in \(z\)'s right subtree (with no left child). Now have two cases:
- If \(y\) is \(z\)'s right child: We replace \(z\) by \(y\) and \(z\)'s left child becomes \(y\)'s left child.
- If \(y\) is not \(z\)'s right child: We first replace \(y\) by its right child, then we replace \(z\) by \(y\)
In order to move subtrees around, we define a helper function transplant
that replaces the subtree rooted at node \(u\) with the subtree rooted at node \(v\).
1 | def transplant(T, u, v): |
Complexcity
- Tree walks: \(\; \Theta(n)\)
- Search, Minimum, Maximum, Successor, Predecessor, Insert, Delete: \(\; O(h)\)