mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-23 12:35:55 +08:00
Add SplayTree (#5142)
This commit is contained in:
@ -0,0 +1,324 @@
|
||||
package com.thealgorithms.datastructures.trees;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of a Splay Tree data structure.
|
||||
*
|
||||
* A splay tree is a self-adjusting binary search tree with the additional
|
||||
* property
|
||||
* that recently accessed elements are quick to access again. It performs basic
|
||||
* operations such as insertion, deletion, and searching in O(log n) amortized
|
||||
* time,
|
||||
* where n is the number of elements in the tree.
|
||||
*
|
||||
* The key feature of splay trees is the splay operation, which moves a node
|
||||
* closer
|
||||
* to the root of the tree when it is accessed. This operation helps to maintain
|
||||
* good balance and improves the overall performance of the tree. After
|
||||
* performing
|
||||
* a splay operation, the accessed node becomes the new root of the tree.
|
||||
*
|
||||
* Splay trees have applications in various areas, including caching, network
|
||||
* routing,
|
||||
* and dynamic optimality analysis.
|
||||
*/
|
||||
public class SplayTree {
|
||||
public static final TreeTraversal PRE_ORDER = new PreOrderTraversal();
|
||||
public static final TreeTraversal IN_ORDER = new InOrderTraversal();
|
||||
public static final TreeTraversal POST_ORDER = new PostOrderTraversal();
|
||||
|
||||
private Node root;
|
||||
|
||||
/**
|
||||
* Checks if the tree is empty.
|
||||
*
|
||||
* @return True if the tree is empty, otherwise false.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return root == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a key into the SplayTree.
|
||||
*
|
||||
* @param key The key to insert.
|
||||
*/
|
||||
public void insert(final int key) {
|
||||
root = insertRec(root, key);
|
||||
root = splay(root, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a key in the SplayTree.
|
||||
*
|
||||
* @param key The key to search for.
|
||||
* @return True if the key is found, otherwise false.
|
||||
*/
|
||||
public boolean search(int key) {
|
||||
root = splay(root, key);
|
||||
return root != null && root.key == key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a key from the SplayTree.
|
||||
*
|
||||
* @param key The key to delete.
|
||||
* @throws IllegalArgumentException If the tree is empty.
|
||||
*/
|
||||
public void delete(final int key) {
|
||||
if (isEmpty()) {
|
||||
throw new EmptyTreeException("Cannot delete from an empty tree");
|
||||
}
|
||||
|
||||
root = splay(root, key);
|
||||
|
||||
if (root.key != key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.left == null) {
|
||||
root = root.right;
|
||||
} else {
|
||||
Node temp = root;
|
||||
root = splay(root.left, findMax(root.left).key);
|
||||
root.right = temp.right;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a traversal of the SplayTree.
|
||||
*
|
||||
* @param traversal The type of traversal method.
|
||||
* @return A list containing the keys in the specified traversal order.
|
||||
*/
|
||||
public List<Integer> traverse(TreeTraversal traversal) {
|
||||
List<Integer> result = new LinkedList<>();
|
||||
traversal.traverse(root, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the node with the maximum key in a given subtree.
|
||||
*
|
||||
* <p>
|
||||
* This method traverses the right children of the subtree until it finds the
|
||||
* rightmost node, which contains the maximum key.
|
||||
* </p>
|
||||
*
|
||||
* @param root The root node of the subtree.
|
||||
* @return The node with the maximum key in the subtree.
|
||||
*/
|
||||
private Node findMax(Node root) {
|
||||
while (root.right != null) {
|
||||
root = root.right;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zig operation.
|
||||
*
|
||||
* <p>
|
||||
* The zig operation is used to perform a single rotation on a node to move it
|
||||
* closer to
|
||||
* the root of the tree. It is typically applied when the node is a left child
|
||||
* of its parent
|
||||
* and needs to be rotated to the right.
|
||||
* </p>
|
||||
*
|
||||
* @param x The node to perform the zig operation on.
|
||||
* @return The new root node after the operation.
|
||||
*/
|
||||
private Node rotateRight(Node x) {
|
||||
Node y = x.left;
|
||||
x.left = y.right;
|
||||
y.right = x;
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zag operation.
|
||||
*
|
||||
* <p>
|
||||
* The zag operation is used to perform a single rotation on a node to move it
|
||||
* closer to
|
||||
* the root of the tree. It is typically applied when the node is a right child
|
||||
* of its parent
|
||||
* and needs to be rotated to the left.
|
||||
* </p>
|
||||
*
|
||||
* @param x The node to perform the zag operation on.
|
||||
* @return The new root node after the operation.
|
||||
*/
|
||||
private Node rotateLeft(Node x) {
|
||||
Node y = x.right;
|
||||
x.right = y.left;
|
||||
y.left = x;
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splay operation.
|
||||
*
|
||||
* <p>
|
||||
* The splay operation is the core operation of a splay tree. It moves a
|
||||
* specified node
|
||||
* closer to the root of the tree by performing a series of rotations. The goal
|
||||
* of the splay
|
||||
* operation is to improve the access time for frequently accessed nodes by
|
||||
* bringing them
|
||||
* closer to the root.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The splay operation consists of three main cases:
|
||||
* <ul>
|
||||
* <li>Zig-Zig case: Perform two consecutive rotations.</li>
|
||||
* <li>Zig-Zag case: Perform two consecutive rotations in opposite
|
||||
* directions.</li>
|
||||
* <li>Zag-Zag case: Perform two consecutive rotations.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* After performing the splay operation, the accessed node becomes the new root
|
||||
* of the tree.
|
||||
* </p>
|
||||
*
|
||||
* @param root The root of the subtree to splay.
|
||||
* @param key The key to splay around.
|
||||
* @return The new root of the splayed subtree.
|
||||
*/
|
||||
private Node splay(Node root, final int key) {
|
||||
if (root == null || root.key == key) {
|
||||
return root;
|
||||
}
|
||||
|
||||
if (root.key > key) {
|
||||
if (root.left == null) {
|
||||
return root;
|
||||
}
|
||||
// Zig-Zig case
|
||||
if (root.left.key > key) {
|
||||
root.left.left = splay(root.left.left, key);
|
||||
root = rotateRight(root);
|
||||
} else if (root.left.key < key) {
|
||||
root.left.right = splay(root.left.right, key);
|
||||
if (root.left.right != null) {
|
||||
root.left = rotateLeft(root.left);
|
||||
}
|
||||
}
|
||||
return (root.left == null) ? root : rotateRight(root);
|
||||
} else {
|
||||
if (root.right == null) {
|
||||
return root;
|
||||
}
|
||||
// Zag-Zag case
|
||||
if (root.right.key > key) {
|
||||
root.right.left = splay(root.right.left, key);
|
||||
if (root.right.left != null) {
|
||||
root.right = rotateRight(root.right);
|
||||
}
|
||||
} else if (root.right.key < key) {
|
||||
root.right.right = splay(root.right.right, key);
|
||||
root = rotateLeft(root);
|
||||
}
|
||||
return (root.right == null) ? root : rotateLeft(root);
|
||||
}
|
||||
}
|
||||
|
||||
private Node insertRec(Node root, final int key) {
|
||||
if (root == null) {
|
||||
return new Node(key);
|
||||
}
|
||||
|
||||
if (key < root.key) {
|
||||
root.left = insertRec(root.left, key);
|
||||
} else if (key > root.key) {
|
||||
root.right = insertRec(root.right, key);
|
||||
} else {
|
||||
throw new DuplicateKeyException("Duplicate key: " + key);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public static class EmptyTreeException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public EmptyTreeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DuplicateKeyException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DuplicateKeyException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Node {
|
||||
final int key;
|
||||
Node left;
|
||||
Node right;
|
||||
|
||||
Node(int key) {
|
||||
this.key = key;
|
||||
left = null;
|
||||
right = null;
|
||||
}
|
||||
}
|
||||
|
||||
public interface TreeTraversal {
|
||||
/**
|
||||
* Recursive function for a specific order traversal.
|
||||
*
|
||||
* @param root The root of the subtree to traverse.
|
||||
* @param result The list to store the traversal result.
|
||||
*/
|
||||
void traverse(Node root, List<Integer> result);
|
||||
}
|
||||
|
||||
private static final class InOrderTraversal implements TreeTraversal {
|
||||
private InOrderTraversal() {
|
||||
}
|
||||
|
||||
public void traverse(Node root, List<Integer> result) {
|
||||
if (root != null) {
|
||||
traverse(root.left, result);
|
||||
result.add(root.key);
|
||||
traverse(root.right, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PreOrderTraversal implements TreeTraversal {
|
||||
private PreOrderTraversal() {
|
||||
}
|
||||
|
||||
public void traverse(Node root, List<Integer> result) {
|
||||
if (root != null) {
|
||||
result.add(root.key);
|
||||
traverse(root.left, result);
|
||||
traverse(root.right, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PostOrderTraversal implements TreeTraversal {
|
||||
private PostOrderTraversal() {
|
||||
}
|
||||
|
||||
public void traverse(Node root, List<Integer> result) {
|
||||
if (root != null) {
|
||||
traverse(root.left, result);
|
||||
traverse(root.right, result);
|
||||
result.add(root.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package com.thealgorithms.datastructures.trees;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
public class SplayTreeTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("traversalStrategies")
|
||||
public void testTraversal(SplayTree.TreeTraversal traversal, List<Integer> expected) {
|
||||
SplayTree tree = createComplexTree();
|
||||
List<Integer> result = tree.traverse(traversal);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("valuesToTest")
|
||||
public void testSearch(int value) {
|
||||
SplayTree tree = createComplexTree();
|
||||
assertTrue(tree.search(value));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("valuesToTest")
|
||||
public void testDelete(int value) {
|
||||
SplayTree tree = createComplexTree();
|
||||
assertTrue(tree.search(value));
|
||||
tree.delete(value);
|
||||
assertFalse(tree.search(value));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonExistentValues")
|
||||
public void testSearchNonExistent(int value) {
|
||||
SplayTree tree = createComplexTree();
|
||||
assertFalse(tree.search(value));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonExistentValues")
|
||||
public void testDeleteNonExistent(int value) {
|
||||
SplayTree tree = createComplexTree();
|
||||
tree.delete(value);
|
||||
assertFalse(tree.search(value));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("valuesToTest")
|
||||
public void testDeleteThrowsExceptionForEmptyTree(int value) {
|
||||
SplayTree tree = new SplayTree();
|
||||
assertThrows(SplayTree.EmptyTreeException.class, () -> tree.delete(value));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("valuesToTest")
|
||||
public void testInsertThrowsExceptionForDuplicateKeys(int value) {
|
||||
SplayTree tree = createComplexTree();
|
||||
assertThrows(SplayTree.DuplicateKeyException.class, () -> tree.insert(value));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("valuesToTest")
|
||||
public void testSearchInEmptyTree(int value) {
|
||||
SplayTree tree = new SplayTree();
|
||||
assertFalse(tree.search(value));
|
||||
}
|
||||
|
||||
private static Stream<Object[]> traversalStrategies() {
|
||||
return Stream.of(new Object[] {SplayTree.IN_ORDER, Arrays.asList(5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90)}, new Object[] {SplayTree.PRE_ORDER, Arrays.asList(15, 5, 10, 80, 70, 45, 25, 20, 35, 30, 40, 55, 50, 65, 60, 75, 90, 85)},
|
||||
new Object[] {SplayTree.POST_ORDER, Arrays.asList(10, 5, 20, 30, 40, 35, 25, 50, 60, 65, 55, 45, 75, 70, 85, 90, 80, 15)});
|
||||
}
|
||||
|
||||
private static Stream<Integer> valuesToTest() {
|
||||
return Stream.of(5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90);
|
||||
}
|
||||
|
||||
private static Stream<Integer> nonExistentValues() {
|
||||
return Stream.of(0, 100, 42, 58);
|
||||
}
|
||||
|
||||
private SplayTree createComplexTree() {
|
||||
SplayTree tree = new SplayTree();
|
||||
|
||||
tree.insert(50);
|
||||
tree.insert(30);
|
||||
tree.insert(40);
|
||||
tree.insert(70);
|
||||
tree.insert(60);
|
||||
tree.insert(20);
|
||||
tree.insert(80);
|
||||
tree.insert(10);
|
||||
tree.insert(25);
|
||||
tree.insert(35);
|
||||
tree.insert(45);
|
||||
tree.insert(55);
|
||||
tree.insert(65);
|
||||
tree.insert(75);
|
||||
tree.insert(85);
|
||||
tree.insert(5);
|
||||
tree.insert(90);
|
||||
tree.insert(15);
|
||||
|
||||
return tree;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user