mirror of
https://github.com/TheAlgorithms/PHP.git
synced 2025-07-10 03:26:19 +02:00
* Added Disjoint Sets Data structure * Moved DisjointSetTest.php to tests/DataStructures * Update DataStructures/DisjointSets/DisjointSet.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Update DataStructures/DisjointSets/DisjointSetNode.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Update DataStructures/DisjointSets/DisjointSetNode.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Update tests/DataStructures/DisjointSetTest.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Considered PHPCS remarks. Unit Testing is now working. * Remove data type mixed. Considered annotations for php7.4. * Remove data type mixed. Considered annotations for php7.4. * updating DIRECTORY.md * Implemented Trie DataStructure * Added Trie to DIRECTORY.md * updating DIRECTORY.md * Implemented AVLTree DataStructure * updating DIRECTORY.md * Implemented AVLTree DataStructure * Implemented SegmentTreeNode.php * Implementing SegmentTree * Implementing SegmentTree with updateTree * Implementing SegmentTree with rangeUpdateTree * Implementing SegmentTree with query and queryTree * Added serializing and deserializing of the SegmentTree * Adding unit tests SegmentTree implementation * Added unit tests for SegmentTree updates and range updates * considering PHPCS for Added unit tests for SegmentTree updates and range updates * Added unit tests for SegmentTree serialization/deserialization and array updates reflections * Added unit tests for SegmentTree Edge Cases * Added unit tests for SegmentTree Exceptions (OutOfBoundsException, InvalidArgumentException) * Added SegmentTree to DIRECTORY.md * Implemented Segment Tree Data Structure * updating DIRECTORY.md * Added some comments to my files in: #160, #162, #163, #166. Implemented Segment Tree Data Structure. * Added some comments to my files in: #160, #162, #163, #166. Implemented Segment Tree Data Structure. * Added comments time complexity for query(), update() and buildTree() * Implemented Splay Tree Data Structure * Update tests/DataStructures/SplayTreeTest.php Co-authored-by: Brandon Johnson <bbj1979@gmail.com> * Implemented AVL Tree Data Structure. Added serialization/deserialization feature. Added corresponding unit test. --------- Co-authored-by: Brandon Johnson <bbj1979@gmail.com> Co-authored-by: Ramy-Badr-Ahmed <Ramy-Badr-Ahmed@users.noreply.github.com>
393 lines
11 KiB
PHP
393 lines
11 KiB
PHP
<?php
|
|
|
|
/*
|
|
* Created by: Ramy-Badr-Ahmed (https://github.com/Ramy-Badr-Ahmed)
|
|
* in Pull Request #163: https://github.com/TheAlgorithms/PHP/pull/163
|
|
* and #173: https://github.com/TheAlgorithms/PHP/pull/173
|
|
*
|
|
* Please mention me (@Ramy-Badr-Ahmed) in any issue or pull request addressing bugs/corrections to this file.
|
|
* Thank you!
|
|
*/
|
|
|
|
namespace DataStructures\AVLTree;
|
|
|
|
/**
|
|
* Class AVLTree
|
|
* Implements an AVL Tree data structure with self-balancing capability.
|
|
*/
|
|
class AVLTree
|
|
{
|
|
private ?AVLTreeNode $root;
|
|
private int $counter;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->root = null;
|
|
$this->counter = 0;
|
|
}
|
|
|
|
/**
|
|
* Get the root node of the AVL Tree.
|
|
*/
|
|
public function getRoot(): ?AVLTreeNode
|
|
{
|
|
return $this->root;
|
|
}
|
|
|
|
/**
|
|
* Retrieve a node by its key.
|
|
*
|
|
* @param mixed $key The key of the node to retrieve.
|
|
* @return ?AVLTreeNode The node with the specified key, or null if not found.
|
|
*/
|
|
public function getNode($key): ?AVLTreeNode
|
|
{
|
|
return $this->searchNode($this->root, $key);
|
|
}
|
|
|
|
/**
|
|
* Get the number of nodes in the AVL Tree.
|
|
*/
|
|
public function size(): int
|
|
{
|
|
return $this->counter;
|
|
}
|
|
|
|
/**
|
|
* Insert a key-value pair into the AVL Tree.
|
|
*
|
|
* @param mixed $key The key to insert.
|
|
* @param mixed $value The value associated with the key.
|
|
*/
|
|
public function insert($key, $value): void
|
|
{
|
|
$this->root = $this->insertNode($this->root, $key, $value);
|
|
$this->counter++;
|
|
}
|
|
|
|
/**
|
|
* Delete a node by its key from the AVL Tree.
|
|
*
|
|
* @param mixed $key The key of the node to delete.
|
|
*/
|
|
public function delete($key): void
|
|
{
|
|
$this->root = $this->deleteNode($this->root, $key);
|
|
$this->counter--;
|
|
}
|
|
|
|
/**
|
|
* Search for a value by its key.
|
|
*
|
|
* @param mixed $key The key to search for.
|
|
* @return mixed The value associated with the key, or null if not found.
|
|
*/
|
|
public function search($key)
|
|
{
|
|
$node = $this->searchNode($this->root, $key);
|
|
return $node ? $node->value : null;
|
|
}
|
|
|
|
/**
|
|
* Perform an in-order traversal of the AVL Tree.
|
|
* Initiates the traversal on the root node directly and returns the array of key-value pairs.
|
|
*/
|
|
public function inOrderTraversal(): array
|
|
{
|
|
return TreeTraversal::inOrder($this->root);
|
|
}
|
|
|
|
/**
|
|
* Perform a pre-order traversal of the AVL Tree.
|
|
* Initiates the traversal on the root node directly and returns the array of key-value pairs.
|
|
*/
|
|
public function preOrderTraversal(): array
|
|
{
|
|
return TreeTraversal::preOrder($this->root);
|
|
}
|
|
|
|
/**
|
|
* Perform a post-order traversal of the AVL Tree.
|
|
* Initiates the traversal on the root node directly and returns the array of key-value pairs.
|
|
*/
|
|
public function postOrderTraversal(): array
|
|
{
|
|
return TreeTraversal::postOrder($this->root);
|
|
}
|
|
|
|
/**
|
|
* Perform a breadth-first traversal of the AVL Tree.
|
|
*/
|
|
public function breadthFirstTraversal(): array
|
|
{
|
|
return TreeTraversal::breadthFirst($this->root);
|
|
}
|
|
|
|
/**
|
|
* Check if the AVL Tree is balanced.
|
|
* This method check balance starting from the root node directly
|
|
*/
|
|
public function isBalanced(): bool
|
|
{
|
|
return $this->isBalancedHelper($this->root);
|
|
}
|
|
|
|
/**
|
|
* Insert a node into the AVL Tree and balance the tree.
|
|
*
|
|
* @param ?AVLTreeNode $node The current node.
|
|
* @param mixed $key The key to insert.
|
|
* @param mixed $value The value to insert.
|
|
* @return AVLTreeNode The new root of the subtree.
|
|
*/
|
|
private function insertNode(?AVLTreeNode $node, $key, $value): AVLTreeNode
|
|
{
|
|
if ($node === null) {
|
|
return new AVLTreeNode($key, $value);
|
|
}
|
|
|
|
if ($key < $node->key) {
|
|
$node->left = $this->insertNode($node->left, $key, $value);
|
|
} elseif ($key > $node->key) {
|
|
$node->right = $this->insertNode($node->right, $key, $value);
|
|
} else {
|
|
$node->value = $value; // Update existing value
|
|
}
|
|
|
|
$node->updateHeight();
|
|
return $this->balance($node);
|
|
}
|
|
|
|
/**
|
|
* Delete a node by its key and balance the tree.
|
|
*
|
|
* @param ?AVLTreeNode $node The current node.
|
|
* @param mixed $key The key of the node to delete.
|
|
* @return ?AVLTreeNode The new root of the subtree.
|
|
*/
|
|
private function deleteNode(?AVLTreeNode $node, $key): ?AVLTreeNode
|
|
{
|
|
if ($node === null) {
|
|
return null;
|
|
}
|
|
|
|
if ($key < $node->key) {
|
|
$node->left = $this->deleteNode($node->left, $key);
|
|
} elseif ($key > $node->key) {
|
|
$node->right = $this->deleteNode($node->right, $key);
|
|
} else {
|
|
if (!$node->left) {
|
|
return $node->right;
|
|
}
|
|
if (!$node->right) {
|
|
return $node->left;
|
|
}
|
|
|
|
$minNode = $this->getMinNode($node->right);
|
|
$node->key = $minNode->key;
|
|
$node->value = $minNode->value;
|
|
$node->right = $this->deleteNode($node->right, $minNode->key);
|
|
}
|
|
|
|
$node->updateHeight();
|
|
return $this->balance($node);
|
|
}
|
|
|
|
/**
|
|
* Search for a node by its key.
|
|
*
|
|
* @param ?AVLTreeNode $node The current node.
|
|
* @param mixed $key The key to search for.
|
|
* @return ?AVLTreeNode The node with the specified key, or null if not found.
|
|
*/
|
|
private function searchNode(?AVLTreeNode $node, $key): ?AVLTreeNode
|
|
{
|
|
if ($node === null) {
|
|
return null;
|
|
}
|
|
|
|
if ($key < $node->key) {
|
|
return $this->searchNode($node->left, $key);
|
|
} elseif ($key > $node->key) {
|
|
return $this->searchNode($node->right, $key);
|
|
} else {
|
|
return $node;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper method to check if a subtree is balanced.
|
|
*
|
|
* @param ?AVLTreeNode $node The current node.
|
|
* @return bool True if the subtree is balanced, false otherwise.
|
|
*/
|
|
private function isBalancedHelper(?AVLTreeNode $node): bool
|
|
{
|
|
if ($node === null) {
|
|
return true;
|
|
}
|
|
|
|
$leftHeight = $node->left ? $node->left->height : 0;
|
|
$rightHeight = $node->right ? $node->right->height : 0;
|
|
|
|
$balanceFactor = abs($leftHeight - $rightHeight);
|
|
if ($balanceFactor > 1) {
|
|
return false;
|
|
}
|
|
|
|
return $this->isBalancedHelper($node->left) && $this->isBalancedHelper($node->right);
|
|
}
|
|
|
|
/**
|
|
* Balance the subtree rooted at the given node.
|
|
*
|
|
* @param ?AVLTreeNode $node The current node.
|
|
* @return ?AVLTreeNode The new root of the subtree.
|
|
*/
|
|
private function balance(?AVLTreeNode $node): ?AVLTreeNode
|
|
{
|
|
if ($node->balanceFactor() > 1) {
|
|
if ($node->left && $node->left->balanceFactor() < 0) {
|
|
$node->left = $this->rotateLeft($node->left);
|
|
}
|
|
return $this->rotateRight($node);
|
|
}
|
|
|
|
if ($node->balanceFactor() < -1) {
|
|
if ($node->right && $node->right->balanceFactor() > 0) {
|
|
$node->right = $this->rotateRight($node->right);
|
|
}
|
|
return $this->rotateLeft($node);
|
|
}
|
|
|
|
return $node;
|
|
}
|
|
|
|
/**
|
|
* Perform a left rotation on the given node.
|
|
*
|
|
* @param AVLTreeNode $node The node to rotate.
|
|
* @return AVLTreeNode The new root of the rotated subtree.
|
|
*/
|
|
private function rotateLeft(AVLTreeNode $node): AVLTreeNode
|
|
{
|
|
$newRoot = $node->right;
|
|
$node->right = $newRoot->left;
|
|
$newRoot->left = $node;
|
|
|
|
$node->updateHeight();
|
|
$newRoot->updateHeight();
|
|
|
|
return $newRoot;
|
|
}
|
|
|
|
/**
|
|
* Perform a right rotation on the given node.
|
|
*
|
|
* @param AVLTreeNode $node The node to rotate.
|
|
* @return AVLTreeNode The new root of the rotated subtree.
|
|
*/
|
|
private function rotateRight(AVLTreeNode $node): AVLTreeNode
|
|
{
|
|
$newRoot = $node->left;
|
|
$node->left = $newRoot->right;
|
|
$newRoot->right = $node;
|
|
|
|
$node->updateHeight();
|
|
$newRoot->updateHeight();
|
|
|
|
return $newRoot;
|
|
}
|
|
|
|
/**
|
|
* Get the node with the minimum key in the given subtree.
|
|
*
|
|
* @param AVLTreeNode $node The root of the subtree.
|
|
* @return AVLTreeNode The node with the minimum key.
|
|
*/
|
|
private function getMinNode(AVLTreeNode $node): AVLTreeNode
|
|
{
|
|
while ($node->left) {
|
|
$node = $node->left;
|
|
}
|
|
return $node;
|
|
}
|
|
|
|
/**
|
|
* Serializes the segment tree into a JSON string.
|
|
*
|
|
* @return string The serialized AVL Tree as a JSON string.
|
|
*/
|
|
public function serialize(): string
|
|
{
|
|
return json_encode($this->serializeTree($this->root));
|
|
}
|
|
|
|
/**
|
|
* Recursively serializes the AVL Tree.
|
|
*
|
|
* @param AVLTreeNode|null $node
|
|
* @return array
|
|
*/
|
|
private function serializeTree(?AVLTreeNode $node): array
|
|
{
|
|
if ($node === null) {
|
|
return [];
|
|
}
|
|
return [
|
|
'key' => $node->key,
|
|
'value' => $node->value,
|
|
'left' => $this->serializeTree($node->left),
|
|
'right' => $this->serializeTree($node->right),
|
|
'height' => $node->height,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Deserializes a JSON string into an AVL Tree object
|
|
*
|
|
* @param string $data The JSON representation of an AVL Tree to deserialize.
|
|
*/
|
|
public function deserialize(string $data): void
|
|
{
|
|
$this->root = $this->deserializeTree(json_decode($data, true));
|
|
$this->counter = 0;
|
|
$this->updateNodeCount($this->root);
|
|
}
|
|
|
|
/**
|
|
* Recursively deserializes an AVL Tree from an array representation.
|
|
*
|
|
* @param array $data The serialized data for the node.
|
|
* @return AVLTreeNode|null The root node of the deserialized tree.
|
|
*/
|
|
private function deserializeTree(array $data): ?AVLTreeNode
|
|
{
|
|
if (empty($data)) {
|
|
return null;
|
|
}
|
|
|
|
$node = new AVLTreeNode($data['key'], $data['value']);
|
|
$node->height = $data['height'];
|
|
|
|
$node->left = $this->deserializeTree($data['left']);
|
|
$node->right = $this->deserializeTree($data['right']);
|
|
|
|
return $node;
|
|
}
|
|
|
|
/**
|
|
* Updates the deserialized tree size.
|
|
*
|
|
* @param AVLTreeNode|null $node The root node of the deserialized tree.
|
|
*/
|
|
private function updateNodeCount(?AVLTreeNode $node): void
|
|
{
|
|
if ($node !== null) {
|
|
$this->counter++;
|
|
$this->updateNodeCount($node->left);
|
|
$this->updateNodeCount($node->right);
|
|
}
|
|
}
|
|
}
|