Scapegoat tree | |
Type: | tree |
Invented Year: | 1989 |
Invented By: | Arne Andersson, Igal Galperin, Ronald L. Rivest |
Space Avg: | O(n) |
Search Avg: | O(logn) |
Search Worst: | O(logn) |
Insert Avg: | O(logn) |
Insert Worst: | O(n) |
Delete Avg: | O(logn) |
Delete Worst: | O(n) |
In computer science, a scapegoat tree is a self-balancing binary search tree, invented by Arne Andersson[1] in 1989 and again by Igal Galperin and Ronald L. Rivest in 1993.[2] It provides worst-case {\color{Blue}O(logn)}
n
O(logn)
Unlike most other self-balancing binary search trees which also provide worst case
O(logn)
Instead of the small incremental rebalancing operations used by most balanced tree algorithms, scapegoat trees rarely but expensively choose a "scapegoat" and completely rebuild the subtree rooted at the scapegoat into a complete binary tree. Thus, scapegoat trees have
O(n)
A binary search tree is said to be weight-balanced if half the nodes are on the left of the root, and half on the right.An α-weight-balanced node is defined as meeting a relaxed weight balance criterion: size(left) ≤ α*size(node) size(right) ≤ α*size(node)Where size can be defined recursively as: function size(node) is if node = nil then return 0 else return size(node->left) + size(node->right) + 1 end if end function
Even a degenerate tree (linked list) satisfies this condition if α=1, whereas an α=0.5 would only match almost complete binary trees.
A binary search tree that is α-weight-balanced must also be α-height-balanced, that is height(tree) ≤ floor(log1/α(size(tree)))
By contraposition, a tree that is not α-height-balanced is not α-weight-balanced.
Scapegoat trees are not guaranteed to keep α-weight-balance at all times, but are always loosely α-height-balanced in that height(scapegoat tree) ≤ floor(log1/α(size(tree))) + 1.Violations of this height balance condition can be detected at insertion time, and imply that a violation of the weight balance condition must exist.
This makes scapegoat trees similar to red–black trees in that they both have restrictions on their height. They differ greatly though in their implementations of determining where the rotations (or in the case of scapegoat trees, rebalances) take place. Whereas red–black trees store additional 'color' information in each node to determine the location, scapegoat trees find a scapegoat which isn't α-weight-balanced to perform the rebalance operation on. This is loosely similar to AVL trees, in that the actual rotations depend on 'balances' of nodes, but the means of determining the balance differs greatly. Since AVL trees check the balance value on every insertion/deletion, it is typically stored in each node; scapegoat trees are able to calculate it only as needed, which is only when a scapegoat needs to be found.
Unlike most other self-balancing search trees, scapegoat trees are entirely flexible as to their balancing. They support any α such that 0.5 < α < 1. A high α value results in fewer balances, making insertion quicker but lookups and deletions slower, and vice versa for a low α. Therefore in practical applications, an α can be chosen depending on how frequently these actions should be performed.
Lookup is not modified from a standard binary search tree, and has a worst-case time of
O(logn)
O(n)
Insertion is implemented with the same basic ideas as an unbalanced binary search tree, however with a few significant changes.
When finding the insertion point, the depth of the new node must also be recorded. This is implemented via a simple counter that gets incremented during each iteration of the lookup, effectively counting the number of edges between the root and the inserted node. If this node violates the α-height-balance property (defined above), a rebalance is required.
To rebalance, an entire subtree rooted at a scapegoat undergoes a balancing operation. The scapegoat is defined as being an ancestor of the inserted node which isn't α-weight-balanced. There will always be at least one such ancestor. Rebalancing any of them will restore the α-height-balanced property.
One way of finding a scapegoat, is to climb from the new node back up to the root and select the first node that isn't α-weight-balanced.
Climbing back up to the root requires
O(logn)
To determine whether a potential node is a viable scapegoat, we need to check its α-weight-balanced property. To do this we can go back to the definition: size(left) ≤ α*size(node) size(right) ≤ α*size(node)However a large optimisation can be made by realising that we already know two of the three sizes, leaving only the third to be calculated.
Consider the following example to demonstrate this. Assuming that we're climbing back up to the root: size(parent) = size(node) + size(sibling) + 1But as: size(inserted node) = 1.The case is trivialized down to: size[x+1] = size[x] + size(sibling) + 1Where x = this node, x + 1 = parent and size(sibling) is the only function call actually required.
Once the scapegoat is found, the subtree rooted at the scapegoat is completely rebuilt to be perfectly balanced.[2] This can be done in
O(n)
As rebalance operations take
O(n)
O(n)
O(logn)
Define the Imbalance of a node v to be the absolute value of the difference in size between its left node and right node minus 1, or 0, whichever is greater. In other words:
I(v)=\operatorname{max}(|\operatorname{left}(v)-\operatorname{right}(v)|-1,0)
Immediately after rebuilding a subtree rooted at v, I(v) = 0.
Lemma: Immediately before rebuilding the subtree rooted at v,
I(v)\in\Omega(|v|)
\Omega
Proof of lemma:
Let
v0
h(v0)=log(|v0|+1)
\Omega(|v0|)
I(v)\in\Omega(|v0|)
h(v)=h(v0)+\Omega(|v0|)
log(|v|)\lelog(|v0|+1)+1
Since
I(v)\in\Omega(|v|)
\Omega(|v|)
v
O(logn)
O(|v|)
O(logn)
{\Omega(|v|)O(logn)+O(|v|)\over\Omega(|v|)}=O(logn)
Scapegoat trees are unusual in that deletion is easier than insertion. To enable deletion, scapegoat trees need to store an additional value with the tree data structure. This property, which we will call MaxNodeCount simply represents the highest achieved NodeCount. It is set to NodeCount whenever the entire tree is rebalanced, and after insertion is set to max(MaxNodeCount, NodeCount).
To perform a deletion, we simply remove the node as you would in a simple binary search tree, but if NodeCount ≤ α*MaxNodeCountthen we rebalance the entire tree about the root, remembering to set MaxNodeCount to NodeCount.
This gives deletion a worst-case performance of
O(n)
O(logn)
Suppose the scapegoat tree has
n
n/2-1
O(logn)
n/2
O(logn)+O(n)
O(n)
O(logn)
n/2 | |
{\sum | |
1 |
O(logn)+O(n)\overn/2}={{n\over2}O(logn)+O(n)\overn/2}=O(logn)
The name Scapegoat tree "[...] is based on the common wisdom that, when something goes wrong, the first thing people tend to do is find someone to blame (the scapegoat)."[3] In the Bible, a scapegoat is an animal that is ritually burdened with the sins of others, and then driven away.
. http://opendatastructures.org/versions/edition-0.1g/ods-python/8_Scapegoat_Trees.html. Open Data Structures (in pseudocode) . Chapter 8 - Scapegoat Trees . 0.1G β . Pat Morin . 2017-09-16.