Introduction to Splay tree data structure
Last Updated :
11 Apr, 2024
Splay tree is a self-adjusting binary search tree data structure, which means that the tree structure is adjusted dynamically based on the accessed or inserted elements. In other words, the tree automatically reorganizes itself so that frequently accessed or inserted elements become closer to the root node.
- The splay tree was first introduced by Daniel Dominic Sleator and Robert Endre Tarjan in 1985. It has a simple and efficient implementation that allows it to perform search, insertion, and deletion operations in O(log n) amortized time complexity, where n is the number of elements in the tree.
- The basic idea behind splay trees is to bring the most recently accessed or inserted element to the root of the tree by performing a sequence of tree rotations, called splaying. Splaying is a process of restructuring the tree by making the most recently accessed or inserted element the new root and gradually moving the remaining nodes closer to the root.
- Splay trees are highly efficient in practice due to their self-adjusting nature, which reduces the overall access time for frequently accessed elements. This makes them a good choice for applications that require fast and dynamic data structures, such as caching systems, data compression, and network routing algorithms.
- However, the main disadvantage of splay trees is that they do not guarantee a balanced tree structure, which may lead to performance degradation in worst-case scenarios. Also, splay trees are not suitable for applications that require guaranteed worst-case performance, such as real-time systems or safety-critical systems.
Overall, splay trees are a powerful and versatile data structure that offers fast and efficient access to frequently accessed or inserted elements. They are widely used in various applications and provide an excellent tradeoff between performance and simplicity.
A splay tree is a self-balancing binary search tree, designed for efficient access to data elements based on their key values.
- The key feature of a splay tree is that each time an element is accessed, it is moved to the root of the tree, creating a more balanced structure for subsequent accesses.
- Splay trees are characterized by their use of rotations, which are local transformations of the tree that change its shape but preserve the order of the elements.
- Rotations are used to bring the accessed element to the root of the tree, and also to rebalance the tree if it becomes unbalanced after multiple accesses.
Operations in a splay tree:
- Insertion: To insert a new element into the tree, start by performing a regular binary search tree insertion. Then, apply rotations to bring the newly inserted element to the root of the tree.
- Deletion: To delete an element from the tree, first locate it using a binary search tree search. Then, if the element has no children, simply remove it. If it has one child, promote that child to its position in the tree. If it has two children, find the successor of the element (the smallest element in its right subtree), swap its key with the element to be deleted, and delete the successor instead.
- Search: To search for an element in the tree, start by performing a binary search tree search. If the element is found, apply rotations to bring it to the root of the tree. If it is not found, apply rotations to the last node visited in the search, which becomes the new root.
- Rotation: The rotations used in a splay tree are either a Zig or a Zig-Zig rotation. A Zig rotation is used to bring a node to the root, while a Zig-Zig rotation is used to balance the tree after multiple accesses to elements in the same subtree.
Here’s a step-by-step explanation of the rotation operations:
- Zig Rotation: If a node has a right child, perform a right rotation to bring it to the root. If it has a left child, perform a left rotation.
- Zig-Zig Rotation: If a node has a grandchild that is also its child’s right or left child, perform a double rotation to balance the tree. For example, if the node has a right child and the right child has a left child, perform a right-left rotation. If the node has a left child and the left child has a right child, perform a left-right rotation.
- Note: The specific implementation details, including the exact rotations used, may vary depending on the exact form of the splay tree.
Rotations in Splay Tree
- Zig Rotation
- Zag Rotation
- Zig – Zig Rotation
- Zag – Zag Rotation
- Zig – Zag Rotation
- Zag – Zig Rotation
1) Zig Rotation:
The Zig Rotation in splay trees operates in a manner similar to the single right rotation in AVL Tree rotations. This rotation results in nodes moving one position to the right from their current location. For example, consider the following scenario:
Zig Rotation (Single Rotation)
2) Zag Rotation:
The Zag Rotation in splay trees operates in a similar fashion to the single left rotation in AVL Tree rotations. During this rotation, nodes shift one position to the left from their current location. For instance, consider the following illustration:
Zag Rotation (Single left Rotation)
 3) Zig-Zig Rotation:
The Zig-Zig Rotation in splay trees is a double zig rotation. This rotation results in nodes shifting two positions to the right from their current location. Take a look at the following example for a better understanding:
Zig-Zig Rotation (Double Right Rotation)
4) Zag-Zag Rotation:
In splay trees, the Zag-Zag Rotation is a double zag rotation. This rotation causes nodes to move two positions to the left from their present position. For example:
Zag-Zag Rotation (Double left rotation)
5) Zig-Zag Rotation:
The Zig-Zag Rotation in splay trees is a combination of a zig rotation followed by a zag rotation. As a result of this rotation, nodes shift one position to the right and then one position to the left from their current location. The following illustration provides a visual representation of this concept:
Zig- Zag rotation
6) Zag-Zig Rotation:
The Zag-Zig Rotation in splay trees is a series of zag rotations followed by a zig rotation. This results in nodes moving one position to the left, followed by a shift one position to the right from their current location. The following illustration offers a visual representation of this concept:
Zag-Zig Rotation
Below is the C++ code to implement rotations in the Splay tree:
C++
#include <iostream>
using namespace std;
struct Node {
int key;
Node *left, *right;
};
Node* newNode(int key) {
Node* node = new Node();
node->key = key;
node->left = node->right = nullptr;
return node;
}
Node* rightRotate(Node* x) {
Node* y = x->left;
x->left = y->right;
y->right = x;
return y;
}
Node* leftRotate(Node* x) {
Node* y = x->right;
x->right = y->left;
y->left = x;
return y;
}
Node* splay(Node* root, int key) {
if (root == nullptr || root->key == key)
return root;
if (root->key > key) {
if (root->left == nullptr)
return root;
if (root->left->key > key) {
root->left->left = splay(root->left->left, key);
root = rightRotate(root);
}
else if (root->left->key < key) {
root->left->right = splay(root->left->right, key);
if (root->left->right != nullptr)
root->left = leftRotate(root->left);
}
return (root->left == nullptr) ? root : rightRotate(root);
}
else {
if (root->right == nullptr)
return root;
if (root->right->key > key) {
root->right->left = splay(root->right->left, key);
if (root->right->left != nullptr)
root->right = rightRotate(root->right);
}
else if (root->right->key < key) {
root->right->right = splay(root->right->right, key);
root = leftRotate(root);
}
return (root->right == nullptr) ? root : leftRotate(root);
}
}
Node* insert(Node* root, int key) {
if (root == nullptr)
return newNode(key);
root = splay(root, key);
if (root->key == key)
return root;
Node* node = newNode(key);
if (root->key > key) {
node->right = root;
node->left = root->left;
root->left = nullptr;
}
else {
node->left = root;
node->right = root->right;
root->right = nullptr;
}
return node;
}
void preOrder(Node* node) {
if (node != nullptr) {
cout << node->key << " ";
preOrder(node->left);
preOrder(node->right);
}
}
int main() {
Node* root = nullptr;
root = insert(root, 100);
root = insert(root, 50);
root = insert(root, 200);
root = insert(root, 40);
root = insert(root, 60);
cout << "Preorder traversal of the modified Splay tree:" << endl;
preOrder(root);
return 0;
}
Java
// Java Program for the above approach
class Node {
public int key;
public Node left, right;
}
class SplayTree {
static Node newNode(int key) {
Node node = new Node();
node.key = key;
node.left = node.right = null;
return node;
}
static Node rightRotate(Node x) {
Node y = x.left;
x.left = y.right;
y.right = x;
return y;
}
static Node leftRotate(Node x) {
Node y = x.right;
x.right = y.left;
y.left = x;
return y;
}
static Node splay(Node root, int key) {
if (root == null || root.key == key)
return root;
if (root.key > key) {
if (root.left == null)
return root;
if (root.left.key > key) {
root.left.left = splay(root.left.left, key);
root = rightRotate(root);
}
else if (root.left.key < key) {
root.left.right = splay(root.left.right, key);
if (root.left.right != null)
root.left = leftRotate(root.left);
}
return (root.left == null) ? root : rightRotate(root);
}
else {
if (root.right == null)
return root;
if (root.right.key > key) {
root.right.left = splay(root.right.left, key);
if (root.right.left != null)
root.right = rightRotate(root.right);
}
else if (root.right.key < key) {
root.right.right = splay(root.right.right, key);
root = leftRotate(root);
}
return (root.right == null) ? root : leftRotate(root);
}
}
static Node insert(Node root, int key) {
if (root == null)
return newNode(key);
root = splay(root, key);
if (root.key == key)
return root;
Node node = newNode(key);
if (root.key > key) {
node.right = root;
node.left = root.left;
root.left = null;
}
else {
node.left = root;
node.right = root.right;
root.right = null;
}
return node;
}
static void preOrder(Node node) {
if (node != null) {
System.out.println();
System.out.print(node.key + " ");
preOrder(node.left);
preOrder(node.right);
}
}
public static void main(String[] args) {
Node root = null;
root = insert(root, 100);
root = insert(root, 50);
root = insert(root, 200);
root = insert(root, 40);
root = insert(root, 60);
System.out.println("Preorder traversal of the modified Splay tree:");
preOrder(root);
}
}
// This code is contributed by princekumaras
Python3
class Node:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
def new_node(key):
return Node(key)
def right_rotate(x):
y = x.left
x.left = y.right
y.right = x
return y
def left_rotate(x):
y = x.right
x.right = y.left
y.left = x
return y
def splay(root, key):
if root is None :
return new_node(key)
if root.key == key:
return root
if root.key > key:
if root.left is None:
return root
if root.left.key > key:
root.left.left = splay(root.left.left, key)
root = right_rotate(root)
elif root.left.key < key:
root.left.right = splay(root.left.right, key)
if root.left.right:
root.left = left_rotate(root.left)
return root.left if root.left is None else right_rotate(root)
else:
if root.right is None:
return root
if root.right.key > key:
root.right.left = splay(root.right.left, key)
if root.right.left:
root.right = right_rotate(root.right)
elif root.right.key < key:
root.right.right = splay(root.right.right, key)
root = left_rotate(root)
return root.right if root.right is None else left_rotate(root)
def insert(root, key):
if root is None:
return new_node(key)
root = splay(root, key)
if root.key == key:
return root
node = new_node(key)
if root.key > key:
node.right = root
node.left = root.left
root.left = None
else:
node.left = root
node.right = root.right
root.right = None
return node
def pre_order(node):
if node:
print(node.key, end=" ")
pre_order(node.left)
pre_order(node.right)
if __name__ == "__main__":
root = None
root = insert(root, 100)
root = insert(root, 50)
root = insert(root, 200)
root = insert(root, 40)
root = insert(root, 60)
print("Preorder traversal of the modified Splay tree:")
pre_order(root)
C#
// C# program for the above approach
using System;
class Node {
public int key;
public Node left, right;
}
class SplayTree {
static Node newNode(int key)
{
Node node = new Node();
node.key = key;
node.left = node.right = null;
return node;
}
static Node rightRotate(Node x)
{
Node y = x.left;
x.left = y.right;
y.right = x;
return y;
}
static Node leftRotate(Node x)
{
Node y = x.right;
x.right = y.left;
y.left = x;
return y;
}
static Node splay(Node root, int key)
{
if (root == null || root.key == key)
return root;
if (root.key > key) {
if (root.left == null)
return root;
if (root.left.key > key) {
root.left.left = splay(root.left.left, key);
root = rightRotate(root);
}
else if (root.left.key < key) {
root.left.right
= splay(root.left.right, key);
if (root.left.right != null)
root.left = leftRotate(root.left);
}
return (root.left == null) ? root
: rightRotate(root);
}
else {
if (root.right == null)
return root;
if (root.right.key > key) {
root.right.left
= splay(root.right.left, key);
if (root.right.left != null)
root.right = rightRotate(root.right);
}
else if (root.right.key < key) {
root.right.right
= splay(root.right.right, key);
root = leftRotate(root);
}
return (root.right == null) ? root
: leftRotate(root);
}
}
static Node insert(Node root, int key)
{
if (root == null)
return newNode(key);
root = splay(root, key);
if (root.key == key)
return root;
Node node = newNode(key);
if (root.key > key) {
node.right = root;
node.left = root.left;
root.left = null;
}
else {
node.left = root;
node.right = root.right;
root.right = null;
}
return node;
}
static void preOrder(Node node)
{
if (node != null) {
Console.Write(node.key + " ");
preOrder(node.left);
preOrder(node.right);
}
}
public static void Main(string[] args)
{
Node root = null;
root = insert(root, 100);
root = insert(root, 50);
root = insert(root, 200);
root = insert(root, 40);
root = insert(root, 60);
Console.WriteLine(
"Preorder traversal of the modified Splay tree:");
preOrder(root);
}
}
// This code is contributed by Prince Kumar
Javascript
// Javascript code addition
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
class SplayTree {
static newNode(key) {
const node = new Node(key);
return node;
}
static rightRotate(x) {
const y = x.left;
x.left = y.right;
y.right = x;
return y;
}
static leftRotate(x) {
const y = x.right;
x.right = y.left;
y.left = x;
return y;
}
static splay(root, key) {
if (root == null || root.key == key) {
return root;
}
if (root.key > key) {
if (root.left == null) {
return root;
}
if (root.left.key > key) {
root.left.left = SplayTree.splay(root.left.left, key);
root = SplayTree.rightRotate(root);
} else if (root.left.key < key) {
root.left.right = SplayTree.splay(root.left.right, key);
if (root.left.right != null) {
root.left = SplayTree.leftRotate(root.left);
}
}
return root.left == null ? root : SplayTree.rightRotate(root);
} else {
if (root.right == null) {
return root;
}
if (root.right.key > key) {
root.right.left = SplayTree.splay(root.right.left, key);
if (root.right.left != null) {
root.right = SplayTree.rightRotate(root.right);
}
} else if (root.right.key < key) {
root.right.right = SplayTree.splay(root.right.right, key);
root = SplayTree.leftRotate(root);
}
return root.right == null ? root : SplayTree.leftRotate(root);
}
}
static insert(root, key) {
if (root == null) {
return SplayTree.newNode(key);
}
root = SplayTree.splay(root, key);
if (root.key == key) {
return root;
}
const node = SplayTree.newNode(key);
if (root.key > key) {
node.right = root;
node.left = root.left;
root.left = null;
} else {
node.left = root;
node.right = root.right;
root.right = null;
}
return node;
}
static preOrder(node) {
if (node != null) {
console.log(node.key + " ");
SplayTree.preOrder(node.left);
SplayTree.preOrder(node.right);
}
}
}
let root = null;
root = SplayTree.insert(root, 100);
root = SplayTree.insert(root, 50);
root = SplayTree.insert(root, 200);
root = SplayTree.insert(root, 40);
root = SplayTree.insert(root, 60);
console.log("Preorder traversal of the modified Splay tree:");
SplayTree.preOrder(root);
// The code is contributed by Nidhi goel.
OutputPreorder traversal of the modified Splay tree:
Drawbacks of splay tree data structure:
- Unbalanced Trees: Splay trees can become unbalanced and inefficient if the tree is repeatedly rotated in the same direction.
- Memory Usage: Splay trees can use a lot of memory compared to other data structures because each node contains additional information.
- Complexity: Splay trees can have a high time complexity for basic operations such as insertion and deletion because the trees need to be reorganized after every operation.
- Reorganization Overhead: The splaying operation required in every operation can be time-consuming and result in a high overhead.
- Limited Use Cases: Splay trees are not suitable for all data structures and have limited use cases because they don’t handle duplicate keys efficiently.
Applications of the splay tree:
- Caching: Splay trees can be used to implement cache memory management, where the most frequently accessed items are moved to the top of the tree for quicker access.
- Database Indexing: Splay trees can be used to index databases for faster searching and retrieval of data.
- File Systems: Splay trees can be used to store file system metadata, such as the allocation table, directory structure, and file attributes.
- Data Compression: Splay trees can be used to compress data by identifying and encoding repeating patterns.
- Text Processing: Splay trees can be used in text processing applications, such as spell-checkers, where words are stored in a splay tree for quick searching and retrieval.
- Graph Algorithms: Splay trees can be used to implement graph algorithms, such as finding the shortest path in a weighted graph.
- Online Gaming: Splay trees can be used in online gaming to store and manage high scores, leaderboards, and player statistics.
Sure, here are some advantages and disadvantages of splay trees, as well as some recommended books for learning more about the topic:
Advantages of Splay Trees:
Splay trees have amortized time complexity of O(log n) for many operations, making them faster than many other balanced tree data structures in some cases.
Splay trees are self-adjusting, meaning that they automatically balance themselves as items are inserted and removed. This can help to avoid the performance degradation that can occur when a tree becomes unbalanced.
Disadvantages of Splay Trees:
Splay trees can have worst-case time complexity of O(n) for some operations, making them less predictable than other balanced tree data structures like AVL trees or red-black trees.
Splay trees may not be suitable for certain applications where predictable performance is required.
Recommended books on Splay Trees:
“Data Structures and Algorithm Analysis in Java” by Mark Allen Weiss
“Introduction to Algorithms” by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein
“Data Structures and Algorithms in C++” by Michael T. Goodrich and Roberto Tamassia
Conclusion:
In conclusion, Splay Trees are a dynamic self-balancing binary search tree data structure that provides an efficient way of searching, inserting, and deleting elements. They differ from traditional balanced binary search trees like AVL and Red-Black trees, as they reorganize the tree after each operation to bring the recently accessed node to the root. This helps to reduce the height of the tree and results in faster operations. Splay Trees are highly flexible and can be adapted to various use cases. Although they may have a higher overhead in terms of rotations, their simplicity and versatility make them useful tools for solving a wide range of problems.
Please Login to comment...