📜  八卦树|设置1(搜索)

📅  最后修改于: 2021-04-17 09:16:35             🧑  作者: Mango

二进制搜索树(BST)操作(如搜索,删除,插入)的最坏情况时间复杂度为O(n)。最坏的情况发生在树倾斜时。我们可以将最坏情况下的时间复杂度表示为带有AVL和红黑树的O(Logn)。

在实际情况下,我们能否比AVL或红黑树做得更好?
像AVL和Red-Black Trees一样,Splay树也是自平衡BST。展开树的主要思想是将最近访问的项带到树的根,这使得最近一次搜索的项在再次访问时可以在O(1)时间内访问。这个想法是使用引用的局部性(在典型的应用程序中,80%的访问权是20%的项)。想象一下这样一种情况:我们有数百万或数十亿个键,而只有很少的键被频繁访问,这在许多实际应用中都是很有可能的。

所有展开树操作平均运行时间为O(log n),其中n是树中的条目数。在最坏的情况下,任何单个操作都可能花费Theta(n)时间。

搜索操作
Splay树中的搜索操作执行标准的BST搜索,除了搜索之外,它还会进行Splay(将节点移动到根)。如果搜索成功,则展开找到的节点并成为新的根。否则,将显示到达NULL之前访问的最后一个节点,并成为新的根。

有以下几种情况可以访问该节点。

1)节点是根节点我们只返回根节点,因为被访问的节点已经是根节点,所以无需执行其他任何操作。

2)Zig:节点是根节点的子节点(该节点没有祖父母)。节点是根的左子节点(我们进行右旋转)或节点是其父节点的右孩子(我们进行左旋转)。
T1,T2和T3是以y(在左侧)或x(在右侧)为根的树的子树。

y                                     x
               / \     Zig (Right Rotation)          /  \
              x   T3   – - – - – - – - - ->         T1   y 
             / \       < - - - - - - - - -              / \
            T1  T2     Zag (Left Rotation)            T2   T3

3) Node既有父代又有祖父母。可能存在以下子情况。
…….. 3.a)Zig-Zig和Zag-Zag节点是父级的左子级,父级也是大父级的左级子级(两次向右旋转),或者节点是其父级的右级子级,父级也是父级的右级子级。祖父母(两次左旋转)。

Zig-Zig (Left Left Case):
       G                        P                           X       
      / \                     /   \                        / \      
     P  T4   rightRotate(G)  X     G     rightRotate(P)  T1   P     
    / \      ============>  / \   / \    ============>       / \    
   X  T3                   T1 T2 T3 T4                      T2  G
  / \                                                          / \ 
 T1 T2                                                        T3  T4 

Zag-Zag (Right Right Case):
  G                          P                           X       
 /  \                      /   \                        / \      
T1   P     leftRotate(G)  G     X     leftRotate(P)    P   T4
    / \    ============> / \   / \    ============>   / \   
   T2   X               T1 T2 T3 T4                  G   T3
       / \                                          / \ 
      T3 T4                                        T1  T2

…….. 3.b)Zig-Zag和Zag-Zig节点是父级的左子级,父级是祖父级的右级子级(向左旋转,然后向右旋转),或者节点是其父级的右级子级,父级是左级子级祖父母的身份(右旋转,然后左旋转)。

Zag-Zig (Left Right Case):
       G                        G                            X       
      / \                     /   \                        /   \      
     P   T4  leftRotate(P)   X     T4    rightRotate(G)   P     G     
   /  \      ============>  / \          ============>   / \   /  \    
  T1   X                   P  T3                       T1  T2 T3  T4 
      / \                 / \                                       
    T2  T3              T1   T2                                     

Zig-Zag (Right Left Case):
  G                          G                           X       
 /  \                      /  \                        /   \      
T1   P    rightRotate(P)  T1   X     leftRotate(P)    G     P
    / \   =============>      / \    ============>   / \   / \   
   X  T4                    T2   P                 T1  T2 T3  T4
  / \                           / \                
 T2  T3                        T3  T4  

例子:

100                      100                       [20]
         /  \                    /   \                        \ 
       50   200                50    200                      50
      /          search(20)    /          search(20)         /  \  
     40          ======>     [20]         ========>         30   100
    /            1. Zig-Zig    \          2. Zig-Zig         \     \
   30               at 40       30            at 100         40    200  
  /                               \     
[20]                              40

要注意的重要一点是,搜索或展开操作不仅使搜索到的密钥成为根,而且还平衡了BST。例如,在上述情况下,BST的高度减少了1。

执行:

C++
#include 
using namespace std;
  
// An AVL tree node 
class node 
{ 
    public:
    int key; 
    node *left, *right; 
}; 
  
/* Helper function that allocates 
a new node with the given key and 
    NULL left and right pointers. */
node* newNode(int key) 
{ 
    node* Node = new node();
    Node->key = key; 
    Node->left = Node->right = NULL; 
    return (Node); 
} 
  
// A utility function to right 
// rotate subtree rooted with y 
// See the diagram given above. 
node *rightRotate(node *x) 
{ 
    node *y = x->left; 
    x->left = y->right; 
    y->right = x; 
    return y; 
} 
  
// A utility function to left 
// rotate subtree rooted with x 
// See the diagram given above. 
node *leftRotate(node *x) 
{ 
    node *y = x->right; 
    x->right = y->left; 
    y->left = x; 
    return y; 
} 
  
// This function brings the key at
// root if key is present in tree. 
// If key is not present, then it
// brings the last accessed item at 
// root. This function modifies the
// tree and returns the new root 
node *splay(node *root, int key) 
{ 
    // Base cases: root is NULL or
    // key is present at root 
    if (root == NULL || root->key == key) 
        return root; 
  
    // Key lies in left subtree 
    if (root->key > key) 
    { 
        // Key is not in tree, we are done 
        if (root->left == NULL) return root; 
  
        // Zig-Zig (Left Left) 
        if (root->left->key > key) 
        { 
            // First recursively bring the
            // key as root of left-left 
            root->left->left = splay(root->left->left, key); 
  
            // Do first rotation for root, 
            // second rotation is done after else 
            root = rightRotate(root); 
        } 
        else if (root->left->key < key) // Zig-Zag (Left Right) 
        { 
            // First recursively bring
            // the key as root of left-right 
            root->left->right = splay(root->left->right, key); 
  
            // Do first rotation for root->left 
            if (root->left->right != NULL) 
                root->left = leftRotate(root->left); 
        } 
  
        // Do second rotation for root 
        return (root->left == NULL)? root: rightRotate(root); 
    } 
    else // Key lies in right subtree 
    { 
        // Key is not in tree, we are done 
        if (root->right == NULL) return root; 
  
        // Zag-Zig (Right Left) 
        if (root->right->key > key) 
        { 
            // Bring the key as root of right-left 
            root->right->left = splay(root->right->left, key); 
  
            // Do first rotation for root->right 
            if (root->right->left != NULL) 
                root->right = rightRotate(root->right); 
        } 
        else if (root->right->key < key)// Zag-Zag (Right Right) 
        { 
            // Bring the key as root of 
            // right-right and do first rotation 
            root->right->right = splay(root->right->right, key); 
            root = leftRotate(root); 
        } 
  
        // Do second rotation for root 
        return (root->right == NULL)? root: leftRotate(root); 
    } 
} 
  
// The search function for Splay tree. 
// Note that this function returns the 
// new root of Splay Tree. If key is 
// present in tree then, it is moved to root. 
node *search(node *root, int key) 
{ 
    return splay(root, key); 
} 
  
// A utility function to print 
// preorder traversal of the tree. 
// The function also prints height of every node 
void preOrder(node *root) 
{ 
    if (root != NULL) 
    { 
        cout<key<<" "; 
        preOrder(root->left); 
        preOrder(root->right); 
    } 
} 
  
/* Driver code*/
int main() 
{ 
    node *root = newNode(100); 
    root->left = newNode(50); 
    root->right = newNode(200); 
    root->left->left = newNode(40); 
    root->left->left->left = newNode(30); 
    root->left->left->left->left = newNode(20); 
  
    root = search(root, 20); 
    cout << "Preorder traversal of the modified Splay tree is \n"; 
    preOrder(root); 
    return 0; 
} 
  
// This code is contributed by rathbhupendra


C
// The code is adopted from http://goo.gl/SDH9hH
#include
#include
  
// An AVL tree node
struct node
{
    int key;
    struct node *left, *right;
};
  
/* Helper function that allocates a new node with the given key and
    NULL left and right pointers. */
struct node* newNode(int key)
{
    struct node* node = (struct node*)malloc(sizeof(struct node));
    node->key   = key;
    node->left  = node->right  = NULL;
    return (node);
}
  
// A utility function to right rotate subtree rooted with y
// See the diagram given above.
struct node *rightRotate(struct node *x)
{
    struct node *y = x->left;
    x->left = y->right;
    y->right = x;
    return y;
}
  
// A utility function to left rotate subtree rooted with x
// See the diagram given above.
struct node *leftRotate(struct node *x)
{
    struct node *y = x->right;
    x->right = y->left;
    y->left = x;
    return y;
}
  
// This function brings the key at root if key is present in tree.
// If key is not present, then it brings the last accessed item at
// root.  This function modifies the tree and returns the new root
struct node *splay(struct node *root, int key)
{
    // Base cases: root is NULL or key is present at root
    if (root == NULL || root->key == key)
        return root;
  
    // Key lies in left subtree
    if (root->key > key)
    {
        // Key is not in tree, we are done
        if (root->left == NULL) return root;
  
        // Zig-Zig (Left Left)
        if (root->left->key > key)
        {
            // First recursively bring the key as root of left-left
            root->left->left = splay(root->left->left, key);
  
            // Do first rotation for root, second rotation is done after else
            root = rightRotate(root);
        }
        else if (root->left->key < key) // Zig-Zag (Left Right)
        {
            // First recursively bring the key as root of left-right
            root->left->right = splay(root->left->right, key);
  
            // Do first rotation for root->left
            if (root->left->right != NULL)
                root->left = leftRotate(root->left);
        }
  
        // Do second rotation for root
        return (root->left == NULL)? root: rightRotate(root);
    }
    else // Key lies in right subtree
    {
        // Key is not in tree, we are done
        if (root->right == NULL) return root;
  
        // Zag-Zig (Right Left)
        if (root->right->key > key)
        {
            // Bring the key as root of right-left
            root->right->left = splay(root->right->left, key);
  
            // Do first rotation for root->right
            if (root->right->left != NULL)
                root->right = rightRotate(root->right);
        }
        else if (root->right->key < key)// Zag-Zag (Right Right)
        {
            // Bring the key as root of right-right and do first rotation
            root->right->right = splay(root->right->right, key);
            root = leftRotate(root);
        }
  
        // Do second rotation for root
        return (root->right == NULL)? root: leftRotate(root);
    }
}
  
// The search function for Splay tree.  Note that this function
// returns the new root of Splay Tree.  If key is present in tree
// then, it is moved to root.
struct node *search(struct node *root, int key)
{
    return splay(root, key);
}
  
// A utility function to print preorder traversal of the tree.
// The function also prints height of every node
void preOrder(struct node *root)
{
    if (root != NULL)
    {
        printf("%d ", root->key);
        preOrder(root->left);
        preOrder(root->right);
    }
}
  
/* Driver program to test above function*/
int main()
{
    struct node *root = newNode(100);
    root->left = newNode(50);
    root->right = newNode(200);
    root->left->left = newNode(40);
    root->left->left->left = newNode(30);
    root->left->left->left->left = newNode(20);
  
    root = search(root, 20);
    printf("Preorder traversal of the modified Splay tree is \n");
    preOrder(root);
    return 0;
}


Java
// Java implementation for above approach
class GFG
{
  
// An AVL tree node 
static class node 
{ 
  
    int key; 
    node left, right; 
}; 
  
/* Helper function that allocates 
a new node with the given key and 
    null left and right pointers. */
static node newNode(int key) 
{ 
    node Node = new node();
    Node.key = key; 
    Node.left = Node.right = null; 
    return (Node); 
} 
  
// A utility function to right 
// rotate subtree rooted with y 
// See the diagram given above. 
static node rightRotate(node x) 
{ 
    node y = x.left; 
    x.left = y.right; 
    y.right = x; 
    return y; 
} 
  
// A utility function to left 
// rotate subtree rooted with x 
// See the diagram given above. 
static node leftRotate(node x) 
{ 
    node y = x.right; 
    x.right = y.left; 
    y.left = x; 
    return y; 
} 
  
// This function brings the key at
// root if key is present in tree. 
// If key is not present, then it
// brings the last accessed item at 
// root. This function modifies the
// tree and returns the new root 
static node splay(node root, int key) 
{ 
    // Base cases: root is null or
    // key is present at root 
    if (root == null || root.key == key) 
        return root; 
  
    // Key lies in left subtree 
    if (root.key > key) 
    { 
        // Key is not in tree, we are done 
        if (root.left == null) return root; 
  
        // Zig-Zig (Left Left) 
        if (root.left.key > key) 
        { 
            // First recursively bring the
            // key as root of left-left 
            root.left.left = splay(root.left.left, key); 
  
            // Do first rotation for root, 
            // second rotation is done after else 
            root = rightRotate(root); 
        } 
        else if (root.left.key < key) // Zig-Zag (Left Right) 
        { 
            // First recursively bring
            // the key as root of left-right 
            root.left.right = splay(root.left.right, key); 
  
            // Do first rotation for root.left 
            if (root.left.right != null) 
                root.left = leftRotate(root.left); 
        } 
  
        // Do second rotation for root 
        return (root.left == null) ? 
                              root : rightRotate(root); 
    } 
    else // Key lies in right subtree 
    { 
        // Key is not in tree, we are done 
        if (root.right == null) return root; 
  
        // Zag-Zig (Right Left) 
        if (root.right.key > key) 
        { 
            // Bring the key as root of right-left 
            root.right.left = splay(root.right.left, key); 
  
            // Do first rotation for root.right 
            if (root.right.left != null) 
                root.right = rightRotate(root.right); 
        } 
        else if (root.right.key < key)// Zag-Zag (Right Right) 
        { 
            // Bring the key as root of 
            // right-right and do first rotation 
            root.right.right = splay(root.right.right, key); 
            root = leftRotate(root); 
        } 
  
        // Do second rotation for root 
        return (root.right == null) ? 
                               root : leftRotate(root); 
    } 
} 
  
// The search function for Splay tree. 
// Note that this function returns the 
// new root of Splay Tree. If key is 
// present in tree then, it is moved to root. 
static node search(node root, int key) 
{ 
    return splay(root, key); 
} 
  
// A utility function to print 
// preorder traversal of the tree. 
// The function also prints height of every node 
static void preOrder(node root) 
{ 
    if (root != null) 
    { 
        System.out.print(root.key + " "); 
        preOrder(root.left); 
        preOrder(root.right); 
    } 
} 
  
// Driver code
public static void main(String[] args) 
{ 
    node root = newNode(100); 
    root.left = newNode(50); 
    root.right = newNode(200); 
    root.left.left = newNode(40); 
    root.left.left.left = newNode(30); 
    root.left.left.left.left = newNode(20); 
  
    root = search(root, 20); 
    System.out.print("Preorder traversal of the" +  
                     " modified Splay tree is \n"); 
    preOrder(root); 
} 
} 
  
// This code is contributed by 29AjayKumar


C#
// C# implementation for above approach
using System;
  
class GFG
{
  
// An AVL tree node 
public class node 
{ 
  
    public int key; 
    public node left, right; 
}; 
  
/* Helper function that allocates 
a new node with the given key and 
null left and right pointers. */
static node newNode(int key) 
{ 
    node Node = new node();
    Node.key = key; 
    Node.left = Node.right = null; 
    return (Node); 
} 
  
// A utility function to right 
// rotate subtree rooted with y 
// See the diagram given above. 
static node rightRotate(node x) 
{ 
    node y = x.left; 
    x.left = y.right; 
    y.right = x; 
    return y; 
} 
  
// A utility function to left 
// rotate subtree rooted with x 
// See the diagram given above. 
static node leftRotate(node x) 
{ 
    node y = x.right; 
    x.right = y.left; 
    y.left = x; 
    return y; 
} 
  
// This function brings the key at
// root if key is present in tree. 
// If key is not present, then it
// brings the last accessed item at 
// root. This function modifies the
// tree and returns the new root 
static node splay(node root, int key) 
{ 
    // Base cases: root is null or
    // key is present at root 
    if (root == null || root.key == key) 
        return root; 
  
    // Key lies in left subtree 
    if (root.key > key) 
    { 
        // Key is not in tree, we are done 
        if (root.left == null) return root; 
  
        // Zig-Zig (Left Left) 
        if (root.left.key > key) 
        { 
            // First recursively bring the
            // key as root of left-left 
            root.left.left = splay(root.left.left, key); 
  
            // Do first rotation for root, 
            // second rotation is done after else 
            root = rightRotate(root); 
        } 
        else if (root.left.key < key) // Zig-Zag (Left Right) 
        { 
            // First recursively bring
            // the key as root of left-right 
            root.left.right = splay(root.left.right, key); 
  
            // Do first rotation for root.left 
            if (root.left.right != null) 
                root.left = leftRotate(root.left); 
        } 
  
        // Do second rotation for root 
        return (root.left == null) ? 
                               root : rightRotate(root); 
    } 
    else // Key lies in right subtree 
    { 
        // Key is not in tree, we are done 
        if (root.right == null) return root; 
  
        // Zag-Zig (Right Left) 
        if (root.right.key > key) 
        { 
            // Bring the key as root of right-left 
            root.right.left = splay(root.right.left, key); 
  
            // Do first rotation for root.right 
            if (root.right.left != null) 
                root.right = rightRotate(root.right); 
        } 
        else if (root.right.key < key)// Zag-Zag (Right Right) 
        { 
            // Bring the key as root of 
            // right-right and do first rotation 
            root.right.right = splay(root.right.right, key); 
            root = leftRotate(root); 
        } 
  
        // Do second rotation for root 
        return (root.right == null) ? 
                               root : leftRotate(root); 
    } 
} 
  
// The search function for Splay tree. 
// Note that this function returns the 
// new root of Splay Tree. If key is 
// present in tree then, it is moved to root. 
static node search(node root, int key) 
{ 
    return splay(root, key); 
} 
  
// A utility function to print 
// preorder traversal of the tree. 
// The function also prints height of every node 
static void preOrder(node root) 
{ 
    if (root != null) 
    { 
        Console.Write(root.key + " "); 
        preOrder(root.left); 
        preOrder(root.right); 
    } 
} 
  
// Driver code
public static void Main(String[] args) 
{ 
    node root = newNode(100); 
    root.left = newNode(50); 
    root.right = newNode(200); 
    root.left.left = newNode(40); 
    root.left.left.left = newNode(30); 
    root.left.left.left.left = newNode(20); 
  
    root = search(root, 20); 
    Console.Write("Preorder traversal of the" + 
                  " modified Splay tree is \n"); 
    preOrder(root); 
} 
} 
  
// This code is contributed by 29AjayKumar


输出:

Preorder traversal of the modified Splay tree is
20 50 30 40 100 200

概括
1) Splay树具有出色的局部性。经常访问的项目很容易找到。稀有物品不合时宜。

2)所有展开树操作平均需要O(Logn)时间。可以严格显示Splay树在任何操作序列上的平均每次操作运行时间为O(log n)(假设我们从一棵空树开始)

3)与AVL和红黑树相比,Splay树更简单,因为在每个树节点中都不需要额外的字段。

4)与AVL树不同,即使使用诸如搜索之类的只读操作,八字树也可以更改。

Splay树的应用
Splay树已成为最近30年来发明最广泛使用的基本数据结构,因为它们是许多应用程序中最快的平衡搜索树类型。
Splay树用于Windows NT(在虚拟内存,网络和文件系统代码中),gcc编译器和GNU C++库,sed字符串编辑器,Fore Systems网络路由器,最受欢迎的Unix malloc实现,Linux可加载内核模块和其他许多软件中(来源:http://www.cs.berkeley.edu/~jrs/61b/lec/36)

参见展开树|设置2(插入)以进行八叉树插入。

参考:
http://www.cs.berkeley.edu/~jrs/61b/lec/36
http://www.cs.cornell.edu/courses/cs3110/2009fa/recitations/rec-splay.html
http://courses.cs.washington.edu/courses/cse326/01au/lectures/SplayTrees.ppt