Introduction
Binary Tree is a fascinating data structure with lots of interesting properties. Binary Search Tree (BST for short below; also known as binary search tree, search binary tree, etc.) is one of the commonly used variants, and it has many interesting properties:
- Left children are all smaller, right children are all larger.
- In-order traversal yields a sorted sequence.
- Projection is in ascending order.
Of course, adding balance introduces even more properties, but let’s set that aside for now. Today, let’s start with a small problem.
Author: Woodpecker Notes https://www.qtmuniao.com, please indicate the source when reposting
Problem Statement
Given a binary search tree t (no duplicate values) and a node value val in it, split t into two new binary trees s and l using val as the boundary, with the following requirements:
- Simply discard val.
- Trees s and l must still be binary search trees.
- All values in tree s are less than val, and all values in tree l are greater than val.
- s and l must be split in-place; do not reconstruct the trees.
Thinking
Usually, our first thought goes like this:
First use the BST property to find the node val --> discard this node, its left subtree definitely goes into s, its right subtree definitely goes into l --> then consider its parent node, if the parent is the root it’s easy, balala
But what if the parent is not the root? What if it’s deep in the tree? Without a parent pointer, how do you backtrack?
Most people are completely stumped by these three questions.
A Simple Solution
The solution is actually quite simple: just reverse your thinking. That is, we still need to find the node, but instead of thinking about splitting the tree only at the end, we split while searching for the node. Specifically:
- Binary search for val from the root, which forms a search path.
- For each node a on this path:
- If a > val, then a along with its right subtree are all greater than val.
- If a < val, then a along with its right subtree are all less than val.
- If a == val (i.e., we found the node), then its left subtree is less than val, and its right subtree is greater than val.
- From the root downward, at each step cut off a node on the path along with the corresponding branch. When the node is found, cut off its left and right branches separately.
This gives us all the branches greater than val and all the branches less than val, with val discarded.
The figure below illustrates searching for val = 11 in the tree.
bst-split
The next question is, how do we merge the cut branches to get the result?
It is easy to prove (think about it, dear readers) that branches cut from the same side can all be merged together through the “cut point”. For example, in the figure above, the branch rooted at 10 can be merged into the right branch of 8.
Code
I’ve been writing a lot of Python lately, and Python is quite concise:
1 | import typing as T |
To verify the correctness of this code, here are functions to construct a BST, validate a BST, and print a binary tree:
1 | # Given upper and lower bounds, construct by halving. |
The binary tree printing implementation prints the tree sideways: each line outputs one value, and each column belongs to the same level—it’s a tricky but simple printing method (think about why). You can tilt your head to view it :).
1 | ## Excerpt of two typical results: |
