搜索、删除、插入等二叉搜索树(BST)操作的最坏情况时间复杂度为O(n)。最坏的情况发生在树倾斜时。我们可以得到最坏情况下的时间复杂度为O(Logn) AVL 还有红黑的树。 在实际情况下,我们能比AVL或红黑树做得更好吗? 喜欢 AVL 还有红黑相间的树,八字树也是 自平衡BST 。splay tree的主要思想是将最近访问的项目置于树的根目录下,这样,如果再次访问,则最近搜索的项目可以在O(1)时间内访问。其想法是使用参考位置(在一个典型应用中,80%的访问是对20%的项目的访问)。想象一下这样一种情况:我们拥有数百万或数十亿个密钥,而频繁访问的密钥却很少,这在许多实际应用中都很可能发生。 所有splay树操作平均运行时间为O(logn),其中n是树中的条目数。在最坏的情况下,任何单个操作都可能需要θ(n)时间。 搜索操作 Splay树中的搜索操作执行标准的BST搜索,除了搜索之外,它还显示(将节点移动到根节点)。如果搜索成功,则找到的节点将展开并成为新根。否则,在到达NULL之前访问的最后一个节点将展开,并成为新的根节点。 正在访问的节点有以下情况。 1) 节点是根 我们只返回根,不做任何其他事情,因为被访问的节点已经是根。 2) 齐格: 节点是根的子节点 (节点没有祖父母)。节点要么是根的左子节点(我们进行右旋转),要么是父节点的右子节点(我们进行左旋转)。 T1、T2和T3是以y(左侧)或x(右侧)为根的树的子树
y x / Zig (Right Rotation) / x T3 – - – - – - – - - -> T1 y / < - - - - - - - - - / T1 T2 Zag (Left Rotation) T2 T3
3) 节点既有父节点,也有祖父母节点 。可以有以下子类别。 …….. 3.a)之字形和之字形 节点是父节点的左子节点,父节点也是父节点的左子节点(两次右旋转),或者节点是其父节点的右子节点,父节点也是父节点的右子节点(两次左旋转)。
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)曲折和曲折 节点是父节点的右子节点,父节点是父节点的右左节点(左旋转后右旋转),或者节点是父节点的左子节点,父节点是父节点的右子节点(右旋转后左旋转)。
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(G) 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
需要注意的重要一点是,搜索或splay操作不仅将搜索到的键带到根,还平衡了BST。例如,在上述情况下,BST的高度降低了1。 实施:
C++
#include <bits/stdc++.h> 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<<root->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 " ; preOrder(root); return 0; } // This code is contributed by rathbhupendra |
C
// The code is adopted from http://goo.gl/SDH9hH #include<stdio.h> #include<stdlib.h> // 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 " ); 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 " ); 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 " ); preOrder(root); } } // This code is contributed by 29AjayKumar |
Javascript
<script> // Javascript implementation for above approach // An AVL tree node class Node { /* Helper function that allocates a new node with the given key and null left and right pointers. */ constructor(key) { this .key = key; this .left = this .right = null ; } } // A utility function to right // rotate subtree rooted with y // See the diagram given above. function rightRotate(x) { let 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. function leftRotate(x) { let 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 function splay(root,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. function search(root,key) { return splay(root, key); } // A utility function to print // preorder traversal of the tree. // The function also prints height of every node function preOrder(root) { if (root != null ) { document.write(root.key + " " ); preOrder(root.left); preOrder(root.right); } } // Driver code let root = new Node(100); root.left = new Node(50); root.right = new Node(200); root.left.left = new Node(40); root.left.left.left = new Node(30); root.left.left.left.left = new Node(20); root = search(root, 20); document.write( "Preorder traversal of the" + " modified Splay tree is <br>" ); preOrder(root); // This code is contributed by rag2127 </script> |
输出:
Preorder traversal of the modified Splay tree is20 50 30 40 100 200
总结 1) 八字树有很好的地方性。经常访问的项目很容易找到。不常出现的物品都已过时。 2) 所有splay tree操作平均需要O(Logn)时间。Splay树可以严格地显示为在任何操作序列(假设我们从一个空树开始)中,每个操作的平均运行时间为O(logn) 3) 与其他树相比,八字树更简单 AVL 红黑树,因为每个树节点都不需要额外的字段。 4) 不像 平衡二叉树 ,即使使用诸如搜索之类的只读操作,splay树也可以更改。 八字树的应用 Splay树已成为过去30年中发明的最广泛使用的基本数据结构,因为它们是许多应用程序中最快的平衡搜索树类型。 在Windows NT(虚拟内存、网络和文件系统代码)、GCC编译器和GNU C++库、SED字符串编辑器、系统网络路由器、UNIX MALLC、Linux可加载内核模块以及其他许多软件中最常用的实现方式中使用了s张开树(源: 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 如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写评论