In computer science, a binary search tree (BST) is a binary tree which has the following properties:
The major advantage of binary search trees is that the related sorting algorithms and search algorithms such as in-order traversal can be very efficient.
Binary search trees are a fundamental data structure used to construct more abstract data structures such as sets, multisets, and associative arrays.
If a BST allows duplicate values, then it represents a multiset. This kind of tree uses non-strict inequalities, so everything in the left subtree of a node is less than or equal to the value of the node, and everything in the right subtree is greater than or equal to the value of the node.
If a BST doesn't allow duplicate values, then the tree represents a set with unique values, like the mathematical set. Trees without duplicate values use strict inequalities, meaning that the left subtree of a node only contains nodes with values that are less than the value of the node, and the right subtree only contains values that are greater.
Some definitions of BSTs use a non-strict inequality only on one side, so duplicate values are allowed. However, these definitions limit how well a tree with many duplicate values can be balanced.
Here is the search algorithm in the Python programming language:
def search_binary_tree(node, key): if node is None: return None # not found if key < node.key: return search_binary_tree(node.left, key) else if key > node.key: return search_binary_tree(node.right, key) else: return node.value
This operation requires O(log n) time in the average case, but needs O(n) time in the worst-case, when the unbalanced tree resembles a linked list.
Here's how a typical binary search tree insertion might be performed in C:
void InsertNode(struct node **node_ptr, struct node *newNode) { struct node *node = *node_ptr; if (node == NULL) *node_ptr = newNode; else if (newNode->value <= node->value) InsertNode(&node->left, newNode); else InsertNode(&node->right, newNode); }
The above "destructive" procedural variant modifies the tree in place. It uses only constant space, but the previous version of the tree is lost. Alternatively, as in the following Python example, we can reconstruct all ancestors of the inserted node; any reference to the original tree root remains valid, making the tree a persistent data structure:
def binary_tree_insert(node, key, value): if node is None: return TreeNode(None, key, value, None) if key == node.key: return TreeNode(node.left, key, value, None) if key < node.key: return TreeNode(binary_tree_insert(node.left, key, value), node.key, node.value, node.right) else: return TreeNode(node.left, node.key, node.value, binary_tree_insert(node.right, key, value))
The part that is rebuilt uses Θ(log n) space in the average case and Ω(n) in the worst case (see big-O notation).
In either version, this operation requires time proportional to the height of the tree in the worst case, which is O(log n) time in the average case over all trees, but Ω(n) time in the worst case.
Another way to explain insertion is that in order to insert a new node in the tree, its value is first compared with the value of the root. If its value is less than the root's, it is then compared with the value of the root's left child. If its value is greater, it is compared with the root's right child. This process continues, until the new node is compared with a leaf node, and then it is added as this node's right or left child, depending on its value.
Once we find either the in-order successor or predecessor, swap it with N, and then delete it. Since either of these nodes must have less than two children (otherwise it cannot be the in-order successor or predecessor), it can be deleted using the previous two cases. In a good implementation, it is generally recommended to avoid consistently using one of these nodes, because this can unbalance the tree.
Here is C++ sample code for a destructive version of deletion (we assume the node to be deleted has already been located using search):
void DeleteNode(struct node*& node) {
struct node*& temp = node;
if (node->left == NULL) {
node = node->right;
delete temp;
} else if (node->right == NULL) {
node = node->left;
delete temp;
} else {
// Node has two children - get max of left subtree
temp = node->left;
while (temp->right != NULL) {
temp = temp->right;
}
node->value = temp->value;
DeleteNode(temp);
}
}
Although this operation does not always traverse the tree down to a leaf, this is always a possibility; thus in the worst case, it requires time proportional to the height of the tree. It does not require more even when the node has two children, since it still follows a single path and visits no node twice.
def traverse_binary_tree(treenode):
if treenode is None: return
Traversal requires Ω(n) time, since it must visit every node. This algorithm is also O(n), and so asymptotically optimal.
def build_binary_tree(values): tree = None for v in values: tree = binary_tree_insert(tree, v) return tree def traverse_binary_tree(treenode): if treenode is None: return * else: left, value, right = treenode return (traverse_binary_tree(left) + * + traverse_binary_tree(right))
The worst-case time of build_binary_tree is Ω(n2) — if you feed it a sorted list of values, it chains them into a linked list with no left subtrees. For example, build_binary_tree(2, 3, 4, 5) yields the tree (None, 1, (None, 2, (None, 3, (None, 4, (None, 5, None))))).
There are a variety of schemes for overcoming this flaw with simple binary trees; the most common is the self-balancing binary search tree. If this same procedure is done using such a tree, the overall worst-case time is O(nlog n), which is asymptotically optimal for a comparison sort. In practice, the poor cache performance and added overhead in time and space for a tree-based sort (particularly for node allocation) makes it inferior to other asymptotically optimal sorts such as quicksort and heapsort for static list sorting. On the other hand, it is one of the most efficient methods of incremental sorting, adding items to a list over time while keeping the list sorted at all times.
Assume that we know the elements and that for each element, we know the proportion of future lookups which will be looking for that element. We can then use a dynamic programming solution, detailed in section 15.5 of Introduction to Algorithms, to construct the tree with the least possible expected search cost.
Even if we only have estimates of the search costs, such a system can considerably speed up lookups on average. For example, if you have a BST of English words used in a spell checker, you might balance the tree based on word frequency in text corpuses, placing words like "the" near the root and words like "agerasia" near the leaves. Such a tree might be compared with Huffman trees, which similarly seek to place frequently-used items near the root in order to produce a dense information encoding; however, Huffman trees only store data elements in leaves and these elements need not be ordered.
Alphabetic trees are Huffman trees with the additional constraint on order, or, equivalently, search trees with the modification that all elements are stored in the leaves. Faster algorithms exist for optimal alphabetic binary trees (OABTs).
Trees (structure) | Sort algorithms | Comparison sorts | Stable sorts
Binært søgetræ | Binärer Suchbaum | Árbol binario de búsqueda | Arbre binaire de recherche | Albero binario di ricerca | עץ חיפוש | Zoekboom | 2分探索木 | Drzewo poszukiwań binarnych | Árvore de busca binária | Двоичное дерево поиска | Binäärinen hakupuu | Бінарне дерево пошуку | 二元搜尋樹
This article is licensed under the GNU Free Documentation License.
It uses material from the
"Binary search tree".
Home Page • arts • business • computers • games • health • hospitals • home • kids & teens • news • physicians • recreation• reference • regional • science • shopping • society • sports • world