mirror of
https://github.com/TheAlgorithms/PHP.git
synced 2025-07-21 17:01:21 +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 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>
296 lines
9.5 KiB
PHP
296 lines
9.5 KiB
PHP
<?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');
|
|
}
|
|
}
|