mirror of
https://github.com/TheAlgorithms/PHP.git
synced 2025-01-16 22:58:14 +01:00
Implemented AVL Tree Data Structure (#163)
* 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 AVLTree DataStructure * Implemented AVLTree DataStructure --------- Co-authored-by: Brandon Johnson <bbj1979@gmail.com> Co-authored-by: Ramy-Badr-Ahmed <Ramy-Badr-Ahmed@users.noreply.github.com>
This commit is contained in:
parent
8c808cd2e6
commit
e43b4bfec8
@ -17,6 +17,9 @@
|
||||
* [Speedconversion](./Conversions/SpeedConversion.php)
|
||||
|
||||
## Datastructures
|
||||
* AVLTree
|
||||
* [AVLTree](./DataStructures/AVLTree/AVLTree.php)
|
||||
* [AVLTreeNode](./DataStructures/AVLTree/AVLTreeNode.php)
|
||||
* Disjointsets
|
||||
* [Disjointset](./DataStructures/DisjointSets/DisjointSet.php)
|
||||
* [Disjointsetnode](./DataStructures/DisjointSets/DisjointSetNode.php)
|
||||
@ -117,6 +120,7 @@
|
||||
* Conversions
|
||||
* [Conversionstest](./tests/Conversions/ConversionsTest.php)
|
||||
* Datastructures
|
||||
* [AVLTreeTest](./tests/DataStructures/AVLTreeTest.php)
|
||||
* [Disjointsettest](./tests/DataStructures/DisjointSetTest.php)
|
||||
* [Doublylinkedlisttest](./tests/DataStructures/DoublyLinkedListTest.php)
|
||||
* [Queuetest](./tests/DataStructures/QueueTest.php)
|
||||
|
306
DataStructures/AVLTree/AVLTree.php
Normal file
306
DataStructures/AVLTree/AVLTree.php
Normal file
@ -0,0 +1,306 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
41
DataStructures/AVLTree/AVLTreeNode.php
Normal file
41
DataStructures/AVLTree/AVLTreeNode.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace DataStructures\AVLTree;
|
||||
|
||||
class AVLTreeNode
|
||||
{
|
||||
/**
|
||||
* @var int|string
|
||||
*/
|
||||
public $key;
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $value;
|
||||
public ?AVLTreeNode $left;
|
||||
public ?AVLTreeNode $right;
|
||||
public int $height;
|
||||
|
||||
public function __construct($key, $value, ?AVLTreeNode $left = null, ?AVLTreeNode $right = null)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->value = $value;
|
||||
$this->left = $left;
|
||||
$this->right = $right;
|
||||
$this->height = 1; // New node is initially at height 1
|
||||
}
|
||||
|
||||
public function updateHeight(): void
|
||||
{
|
||||
$leftHeight = $this->left ? $this->left->height : 0;
|
||||
$rightHeight = $this->right ? $this->right->height : 0;
|
||||
$this->height = max($leftHeight, $rightHeight) + 1;
|
||||
}
|
||||
|
||||
public function balanceFactor(): int
|
||||
{
|
||||
$leftHeight = $this->left ? $this->left->height : 0;
|
||||
$rightHeight = $this->right ? $this->right->height : 0;
|
||||
return $leftHeight - $rightHeight;
|
||||
}
|
||||
}
|
80
DataStructures/AVLTree/TreeTraversal.php
Normal file
80
DataStructures/AVLTree/TreeTraversal.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace DataStructures\AVLTree;
|
||||
|
||||
abstract class TreeTraversal
|
||||
{
|
||||
/**
|
||||
* Perform an in-order traversal of the subtree.
|
||||
* Recursively traverses the subtree rooted at the given node.
|
||||
*/
|
||||
public static function inOrder(?AVLTreeNode $node): array
|
||||
{
|
||||
$result = [];
|
||||
if ($node !== null) {
|
||||
$result = array_merge($result, self::inOrder($node->left));
|
||||
$result[] = [$node->key => $node->value];
|
||||
$result = array_merge($result, self::inOrder($node->right));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a pre-order traversal of the subtree.
|
||||
* Recursively traverses the subtree rooted at the given node.
|
||||
*/
|
||||
public static function preOrder(?AVLTreeNode $node): array
|
||||
{
|
||||
$result = [];
|
||||
if ($node !== null) {
|
||||
$result[] = [$node->key => $node->value];
|
||||
$result = array_merge($result, self::preOrder($node->left));
|
||||
$result = array_merge($result, self::preOrder($node->right));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a post-order traversal of the subtree.
|
||||
* Recursively traverses the subtree rooted at the given node.
|
||||
*/
|
||||
public static function postOrder(?AVLTreeNode $node): array
|
||||
{
|
||||
$result = [];
|
||||
if ($node !== null) {
|
||||
$result = array_merge($result, self::postOrder($node->left));
|
||||
$result = array_merge($result, self::postOrder($node->right));
|
||||
$result[] = [$node->key => $node->value];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a breadth-first traversal of the AVL Tree.
|
||||
*/
|
||||
public static function breadthFirst(?AVLTreeNode $root): array
|
||||
{
|
||||
$result = [];
|
||||
if ($root === null) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$queue = [];
|
||||
$queue[] = $root;
|
||||
|
||||
while (!empty($queue)) {
|
||||
$currentNode = array_shift($queue);
|
||||
$result[] = [$currentNode->key => $currentNode->value];
|
||||
|
||||
if ($currentNode->left !== null) {
|
||||
$queue[] = $currentNode->left;
|
||||
}
|
||||
|
||||
if ($currentNode->right !== null) {
|
||||
$queue[] = $currentNode->right;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
295
tests/DataStructures/AVLTreeTest.php
Normal file
295
tests/DataStructures/AVLTreeTest.php
Normal file
@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace DataStructures;
|
||||
|
||||
require_once __DIR__ . '/../../DataStructures/AVLTree/AVLTree.php';
|
||||
require_once __DIR__ . '/../../DataStructures/AVLTree/AVLTreeNode.php';
|
||||
require_once __DIR__ . '/../../DataStructures/AVLTree/TreeTraversal.php';
|
||||
|
||||
use DataStructures\AVLTree\AVLTree;
|
||||
use DataStructures\AVLTree\TreeTraversal;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
class AVLTreeTest extends TestCase
|
||||
{
|
||||
private AVLTree $tree;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->tree = new AVLTree();
|
||||
}
|
||||
|
||||
private function populateTree(): void
|
||||
{
|
||||
$this->tree->insert(10, 'Value 10');
|
||||
$this->tree->insert(20, 'Value 20');
|
||||
$this->tree->insert(5, 'Value 5');
|
||||
$this->tree->insert(15, 'Value 15');
|
||||
}
|
||||
|
||||
public function testInsertAndSearch(): void
|
||||
{
|
||||
$this->populateTree();
|
||||
|
||||
$this->assertEquals('Value 10', $this->tree->search(10), 'Value for key 10 should be "Value 10"');
|
||||
$this->assertEquals('Value 20', $this->tree->search(20), 'Value for key 20 should be "Value 20"');
|
||||
$this->assertEquals('Value 5', $this->tree->search(5), 'Value for key 5 should be "Value 5"');
|
||||
$this->assertNull($this->tree->search(25), 'Value for non-existent key 25 should be null');
|
||||
}
|
||||
|
||||
public function testDelete(): void
|
||||
{
|
||||
$this->populateTree();
|
||||
|
||||
$this->tree->delete(20);
|
||||
$this->tree->delete(5);
|
||||
|
||||
$this->assertNull($this->tree->search(20), 'Value for deleted key 20 should be null');
|
||||
$this->assertNull($this->tree->search(5), 'Value for deleted key 5 should be null');
|
||||
|
||||
$this->tree->delete(50);
|
||||
|
||||
$this->assertNotNull($this->tree->search(10), 'Value for key 10 should still exist');
|
||||
$this->assertNotNull($this->tree->search(15), 'Value for key 15 should still exist');
|
||||
$this->assertNull($this->tree->search(50), 'Value for non-existent key 50 should be null');
|
||||
|
||||
$expectedInOrderAfterDelete = [
|
||||
[10 => 'Value 10'],
|
||||
[15 => 'Value 15']
|
||||
];
|
||||
|
||||
$result = TreeTraversal::inOrder($this->tree->getRoot());
|
||||
$this->assertEquals(
|
||||
$expectedInOrderAfterDelete,
|
||||
$result,
|
||||
'In-order traversal after deletion should match expected result'
|
||||
);
|
||||
}
|
||||
|
||||
public function testInOrderTraversal(): void
|
||||
{
|
||||
$this->populateTree();
|
||||
|
||||
$expectedInOrder = [
|
||||
[5 => 'Value 5'],
|
||||
[10 => 'Value 10'],
|
||||
[15 => 'Value 15'],
|
||||
[20 => 'Value 20']
|
||||
];
|
||||
|
||||
$result = $this->tree->inOrderTraversal();
|
||||
$this->assertEquals($expectedInOrder, $result, 'In-order traversal should match expected result');
|
||||
}
|
||||
|
||||
public function testPreOrderTraversal(): void
|
||||
{
|
||||
$this->populateTree();
|
||||
|
||||
$expectedPreOrder = [
|
||||
[10 => 'Value 10'],
|
||||
[5 => 'Value 5'],
|
||||
[20 => 'Value 20'],
|
||||
[15 => 'Value 15']
|
||||
];
|
||||
|
||||
$result = $this->tree->preOrderTraversal();
|
||||
$this->assertEquals($expectedPreOrder, $result, 'Pre-order traversal should match expected result');
|
||||
}
|
||||
|
||||
public function testPostOrderTraversal(): void
|
||||
{
|
||||
$this->populateTree();
|
||||
|
||||
$expectedPostOrder = [
|
||||
[5 => 'Value 5'],
|
||||
[15 => 'Value 15'],
|
||||
[20 => 'Value 20'],
|
||||
[10 => 'Value 10']
|
||||
];
|
||||
|
||||
$result = TreeTraversal::postOrder($this->tree->getRoot());
|
||||
$this->assertEquals($expectedPostOrder, $result, 'Post-order traversal should match expected result');
|
||||
}
|
||||
|
||||
public function testBreadthFirstTraversal(): void
|
||||
{
|
||||
$this->populateTree();
|
||||
|
||||
$expectedBFT = [
|
||||
[10 => 'Value 10'],
|
||||
[5 => 'Value 5'],
|
||||
[20 => 'Value 20'],
|
||||
[15 => 'Value 15']
|
||||
];
|
||||
|
||||
$result = TreeTraversal::breadthFirst($this->tree->getRoot());
|
||||
$this->assertEquals($expectedBFT, $result, 'Breadth-first traversal should match expected result');
|
||||
}
|
||||
|
||||
public function testInsertAndDeleteSingleNode(): void
|
||||
{
|
||||
$this->tree = new AVLTree();
|
||||
|
||||
$this->tree->insert(1, 'One');
|
||||
$this->assertEquals('One', $this->tree->search(1), 'Value for key 1 should be "One"');
|
||||
$this->tree->delete(1);
|
||||
$this->assertNull($this->tree->search(1), 'Value for key 1 should be null after deletion');
|
||||
}
|
||||
|
||||
public function testDeleteFromEmptyTree(): void
|
||||
{
|
||||
$this->tree = new AVLTree();
|
||||
|
||||
$this->tree->delete(1);
|
||||
$this->assertNull($this->tree->search(1), 'Value for key 1 should be null as it was never inserted');
|
||||
}
|
||||
|
||||
public function testInsertDuplicateKeys(): void
|
||||
{
|
||||
$this->tree = new AVLTree();
|
||||
|
||||
$this->tree->insert(1, 'One');
|
||||
$this->tree->insert(1, 'One Updated');
|
||||
$this->assertEquals(
|
||||
'One Updated',
|
||||
$this->tree->search(1),
|
||||
'Value for key 1 should be "One Updated" after updating'
|
||||
);
|
||||
}
|
||||
|
||||
public function testLargeTree(): void
|
||||
{
|
||||
// Inserting a large number of nodes
|
||||
for ($i = 1; $i <= 1000; $i++) {
|
||||
$this->tree->insert($i, "Value $i");
|
||||
}
|
||||
|
||||
// Verify that all inserted nodes can be searched
|
||||
for ($i = 1; $i <= 1000; $i++) {
|
||||
$this->assertEquals("Value $i", $this->tree->search($i), "Value for key $i should be 'Value $i'");
|
||||
}
|
||||
|
||||
// Verify that all inserted nodes can be deleted
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$this->tree->delete($i);
|
||||
$this->assertNull($this->tree->search($i), "Value for key $i should be null after deletion");
|
||||
}
|
||||
}
|
||||
|
||||
public function testBalance(): void
|
||||
{
|
||||
$this->populateTree();
|
||||
|
||||
// Perform operations that may unbalance the tree
|
||||
$this->tree->insert(30, 'Value 30');
|
||||
$this->tree->insert(25, 'Value 25');
|
||||
|
||||
// After insertions, check the balance
|
||||
$this->assertTrue($this->tree->isBalanced(), 'Tree should be balanced after insertions');
|
||||
}
|
||||
|
||||
public function testRightRotation(): void
|
||||
{
|
||||
$this->populateTreeForRightRotation();
|
||||
|
||||
// Insert a node that will trigger a right rotation
|
||||
$this->tree->insert(40, 'Value 40');
|
||||
|
||||
// Verify the tree structure after rotation
|
||||
$root = $this->tree->getRoot();
|
||||
$this->assertEquals(20, $root->key, 'Root should be 20 after right rotation');
|
||||
$this->assertEquals(10, $root->left->key, 'Left child of root should be 10');
|
||||
$this->assertEquals(30, $root->right->key, 'Right child of root should be 30');
|
||||
}
|
||||
|
||||
private function populateTreeForRightRotation(): void
|
||||
{
|
||||
// Insert nodes in a way that requires a right rotation
|
||||
$this->tree->insert(10, 'Value 10');
|
||||
$this->tree->insert(20, 'Value 20');
|
||||
$this->tree->insert(30, 'Value 30'); // This should trigger a right rotation around 10
|
||||
}
|
||||
|
||||
public function testLeftRotation(): void
|
||||
{
|
||||
$this->populateTreeForLeftRotation();
|
||||
|
||||
// Insert a node that will trigger a left rotation
|
||||
$this->tree->insert(5, 'Value 5');
|
||||
|
||||
// Verify the tree structure after rotation
|
||||
$root = $this->tree->getRoot();
|
||||
$this->assertEquals(20, $root->key, 'Root should be 20 after left rotation');
|
||||
$this->assertEquals(10, $root->left->key, 'Left child of root should be 10');
|
||||
$this->assertEquals(30, $root->right->key, 'Right child of root should be 30');
|
||||
}
|
||||
|
||||
private function populateTreeForLeftRotation(): void
|
||||
{
|
||||
$this->tree->insert(30, 'Value 30');
|
||||
$this->tree->insert(20, 'Value 20');
|
||||
$this->tree->insert(10, 'Value 10'); // This should trigger a left rotation around 30
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function testGetMinNode(): void
|
||||
{
|
||||
$this->populateTree();
|
||||
|
||||
// Using Reflection to access the private getMinNode method
|
||||
$reflection = new ReflectionClass($this->tree);
|
||||
$method = $reflection->getMethod('getMinNode');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$minNode = $method->invoke($this->tree, $this->tree->getRoot());
|
||||
|
||||
// Verify the minimum node
|
||||
$this->assertEquals(5, $minNode->key, 'Minimum key in the tree should be 5');
|
||||
$this->assertEquals('Value 5', $minNode->value, 'Value for minimum key 5 should be "Value 5"');
|
||||
}
|
||||
|
||||
public function testSizeAfterInsertions(): void
|
||||
{
|
||||
$this->tree = new AVLTree();
|
||||
|
||||
$this->assertEquals(0, $this->tree->size(), 'Size should be 0 initially');
|
||||
|
||||
$this->tree->insert(10, 'Value 10');
|
||||
$this->tree->insert(20, 'Value 20');
|
||||
$this->tree->insert(5, 'Value 5');
|
||||
|
||||
$this->assertEquals(3, $this->tree->size(), 'Size should be 3 after 3 insertions');
|
||||
|
||||
$this->tree->delete(20);
|
||||
|
||||
$this->assertEquals(2, $this->tree->size(), 'Size should be 2 after deleting 1 node');
|
||||
}
|
||||
|
||||
public function testSizeAfterMultipleInsertionsAndDeletions(): void
|
||||
{
|
||||
$this->tree = new AVLTree();
|
||||
|
||||
// Insert nodes
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$this->tree->insert($i, "Value $i");
|
||||
}
|
||||
|
||||
$this->assertEquals(10, $this->tree->size(), 'Size should be 10 after 10 insertions');
|
||||
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$this->tree->delete($i);
|
||||
}
|
||||
|
||||
$this->assertEquals(5, $this->tree->size(), 'Size should be 5 after deleting 5 nodes');
|
||||
}
|
||||
|
||||
public function testSizeOnEmptyTree(): void
|
||||
{
|
||||
$this->tree = new AVLTree();
|
||||
$this->assertEquals(0, $this->tree->size(), 'Size should be 0 for an empty tree');
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user