ScapeGoat树是一种自平衡二进制搜索树,例如AVL树,红黑树,Splay树等。
- 在最坏的情况下,搜索时间为O(Log n)。删除和插入所花费的时间摊销O(Log n)
- 平衡的想法是确保节点的大小保持平衡。大小平衡a表示左右子树的大小最大为α*(节点的大小)。该思想基于以下事实:如果节点是A重量平衡的,那么它也是高度平衡的:height <= log 1 /&aplpha; (大小)+ 1
- 与其他自平衡BST不同,ScapeGoat树不需要每个节点额外的空间。例如,要求“红黑树”节点具有颜色。在下面的ScapeGoat树实现中,Node类中只有左,右和父指针。父类的使用是为了简化实现,可以避免。
插入(假设α= 2/3):
要在替罪羊树中插入值x ,请执行以下操作:
- 创建一个新节点u并使用BST插入算法插入x。
- 如果u的深度大于log 3/2 n,其中n是树中的节点数,则我们需要使树平衡。为了达到平衡,我们使用以下步骤找到替罪羊。
- 从u向上走,直到到达大小为(w)>(2/3)*大小(w.parent)的节点w。这个节点是替罪羊
- 重建以w.parent为根的子树。
重建子树是什么意思?
在重建过程中,我们只需将子树转换为最可能的平衡BST。我们首先将BST的有序遍历存储在数组中,然后通过将其递归地分为两半从数组中构建一个新的BST。
60 50
/ / \
40 42 58
\ Rebuild / \ / \
50 ---------> 40 47 55 60
\
55
/ \
47 58
/
42
以下是替罪羊树上插入操作的C++实现。
// C++ program to implement insertion in
// ScapeGoat Tree
#include
using namespace std;
// Utility function to get value of log32(n)
static int const log32(int n)
{
double const log23 = 2.4663034623764317;
return (int)ceil(log23 * log(n));
}
// A ScapeGoat Tree node
class Node
{
public:
Node *left, *right, *parent;
float value;
Node()
{
value = 0;
left = right = parent = NULL;
}
Node (float v)
{
value = v;
left = right = parent = NULL;
}
};
// This functions stores inorder traversal
// of tree rooted with ptr in an array arr[]
int storeInArray(Node *ptr, Node *arr[], int i)
{
if (ptr == NULL)
return i;
i = storeInArray(ptr->left, arr, i);
arr[i++] = ptr;
return storeInArray(ptr->right, arr, i);
}
// Class to represent a ScapeGoat Tree
class SGTree
{
private:
Node *root;
int n; // Number of nodes in Tree
public:
void preorder(Node *);
int size(Node *);
bool insert(float x);
void rebuildTree(Node *u);
SGTree() { root = NULL; n = 0; }
void preorder() { preorder(root); }
// Function to built tree with balanced nodes
Node *buildBalancedFromArray(Node **a, int i, int n);
// Height at which element is to be added
int BSTInsertAndFindDepth(Node *u);
};
// Preorder traversal of the tree
void SGTree::preorder(Node *node)
{
if (node != NULL)
{
cout << node->value << " ";
preorder(node -> left);
preorder(node -> right);
}
}
// To count number of nodes in the tree
int SGTree::size(Node *node)
{
if (node == NULL)
return 0;
return 1 + size(node->left) + size(node->right);
}
// To insert new element in the tree
bool SGTree::insert(float x)
{
// Create a new node
Node *node = new Node(x);
// Perform BST insertion and find depth of
// the inserted node.
int h = BSTInsertAndFindDepth(node);
// If tree becomes unbalanced
if (h > log32(n))
{
// Find Scapegoat
Node *p = node->parent;
while (3*size(p) <= 2*size(p->parent))
p = p->parent;
// Rebuild tree rooted under scapegoat
rebuildTree(p->parent);
}
return h >= 0;
}
// Function to rebuilt tree from new node. This
// function basically uses storeInArray() to
// first store inorder traversal of BST rooted
// with u in an array.
// Then it converts array to the most possible
// balanced BST using buildBalancedFromArray()
void SGTree::rebuildTree(Node *u)
{
int n = size(u);
Node *p = u->parent;
Node **a = new Node* [n];
storeInArray(u, a, 0);
if (p == NULL)
{
root = buildBalancedFromArray(a, 0, n);
root->parent = NULL;
}
else if (p->right == u)
{
p->right = buildBalancedFromArray(a, 0, n);
p->right->parent = p;
}
else
{
p->left = buildBalancedFromArray(a, 0, n);
p->left->parent = p;
}
}
// Function to built tree with balanced nodes
Node * SGTree::buildBalancedFromArray(Node **a,
int i, int n)
{
if (n== 0)
return NULL;
int m = n / 2;
// Now a[m] becomes the root of the new
// subtree a[0],.....,a[m-1]
a[i+m]->left = buildBalancedFromArray(a, i, m);
// elements a[0],...a[m-1] gets stored
// in the left subtree
if (a[i+m]->left != NULL)
a[i+m]->left->parent = a[i+m];
// elements a[m+1],....a[n-1] gets stored
// in the right subtree
a[i+m]->right =
buildBalancedFromArray(a, i+m+1, n-m-1);
if (a[i+m]->right != NULL)
a[i+m]->right->parent = a[i+m];
return a[i+m];
}
// Performs standard BST insert and returns
// depth of the inserted node.
int SGTree::BSTInsertAndFindDepth(Node *u)
{
// If tree is empty
Node *w = root;
if (w == NULL)
{
root = u;
n++;
return 0;
}
// While the node is not inserted
// or a node with same key exists.
bool done = false;
int d = 0;
do
{
if (u->value < w->value)
{
if (w->left == NULL)
{
w->left = u;
u->parent = w;
done = true;
}
else
w = w->left;
}
else if (u->value > w->value)
{
if (w->right == NULL)
{
w->right = u;
u->parent = w;
done = true;
}
else
w = w->right;
}
else
return -1;
d++;
}
while (!done);
n++;
return d;
}
// Driver code
int main()
{
SGTree sgt;
sgt.insert(7);
sgt.insert(6);
sgt.insert(3);
sgt.insert(1);
sgt.insert(0);
sgt.insert(8);
sgt.insert(9);
sgt.insert(4);
sgt.insert(5);
sgt.insert(2);
sgt.insert(3.5);
printf("Preorder traversal of the"
" constructed ScapeGoat tree is \n");
sgt.preorder();
return 0;
}
输出:
Preorder traversal of the constructed ScapeGoat tree is
7 6 3 1 0 2 4 3.5 5 8 9
具有10个节点,高度为5的替罪羊树。
7
/ \
6 8
/ \
5 9
/
2
/ \
1 4
/ /
0 3
Let’s insert 3.5 in the below scapegoat tree.
最初d = 5
由于d> log 3/2 n,即6> log 3/2 n,因此我们必须找到替罪羊以解决超高问题。
- 现在我们找到了一个ScapeGoat。我们从新添加的节点3.5开始,检查是否size(3.5)/ size(3)> 2/3。
- 由于size(3.5)= 1且size(3)= 2,因此size(3.5)/ size(3)=½小于2/3。因此,这不是替罪羊,我们将继续前进。
- 由于3不是替罪羊,因此我们移动并检查节点4的相同条件。由于size(3)= 2并且size(4)= 3,因此size(3)/ size(4)= 2/3不是大于2/3。因此,这不是替罪羊,我们将继续前进。
- 因为3不是替罪羊,所以我们移动并检查节点4的相同条件。由于size(3)= 2且size(4)= 3,因此size(3)/ size(4)= 2/3不大于2/3。因此,这不是替罪羊,我们将继续前进。
- 现在,size(4)/ size(2)= 3/6。由于size(4)= 3和size(2)= 6但3/6仍小于2/3,这不满足替罪羊的条件,因此我们再次上移。
- 现在,size(2)/ size(5)= 6/7。由于size(2)= 6且size(5)=7。6/7> 2/3满足了替罪羊的条件,因此我们在此处停止,因此节点5是替罪羊
最后,找到替罪羊之后,将在以替罪羊为根的子树(即5)处进行重建。
最终树:
与其他自平衡BST的比较
红黑和AVL:搜索,插入和删除的时间复杂度为O(Log n)
Splay树:最坏情况下,搜索,插入和删除的时间复杂度为O(n)。但是这些操作的摊销时间复杂度为O(Log n)。
ScapeGoat树:与Splay树一样,它易于实现,并且在最坏的情况下,搜索的时间复杂度为O(Log n)。插入和删除的最坏情况和摊销时间复杂度与替罪羊树的Splay树相同。
参考:
- https://zh.wikipedia.org/wiki/Scapegoat_tree
- http://opendatastructures.org/ods-java/8_Scapegoat_Trees.html