📜  ScapeGoat树|设置1(介绍和插入)

📅  最后修改于: 2021-04-17 13:04:25             🧑  作者: Mango

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 3/2 n其中n = 10;
替罪羊1

由于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。因此,这不是替罪羊,我们将继续前进。

山羊山羊树1

  • 由于3不是替罪羊,因此我们移动并检查节点4的相同条件。由于size(3)= 2并且size(4)= 3,因此size(3)/ size(4)= 2/3不是大于2/3。因此,这不是替罪羊,我们将继续前进。

替罪羊树2

  • 因为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,这不满足替罪羊的条件,因此我们再次上移。

替罪羊tree3

  • 现在,size(2)/ size(5)= 6/7。由于size(2)= 6且size(5)=7。6/7> 2/3满足了替罪羊的条件,因此我们在此处停止,因此节点5是替罪羊

替罪羊树4
最后,找到替罪羊之后,将在以替罪羊为根的子树(即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