diff --git a/.gitignore b/.gitignore index 36acca2..bb3d948 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ temp/ tmp/ .vscode/launch.json .vscode/extensions.json +lista_de_arquivos.txt +tests/lista_de_arquivos.php diff --git a/README.md b/README.md index 820c266..738f3ce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# KaririCode Contract +# KaririCode Framework: Data Structures Component [![en](https://img.shields.io/badge/lang-en-red.svg)](README.md) [![pt-br](https://img.shields.io/badge/lang-pt--br-green.svg)](README.pt-br.md) @@ -10,162 +10,388 @@ ## Overview -The `kariricode/kariricode-data-structure` package provides a set of standardized interfaces for common data structures and patterns within the KaririCode Framework. This library ensures consistency and interoperability across various components of the KaririCode ecosystem, following PSR standards and utilizing modern PHP practices. +The Data Structures component is a cornerstone of the KaririCode Framework, offering robust, high-performance, and type-safe implementations of essential data structures for PHP applications. This component is meticulously designed to meet the demands of modern, scalable software development, providing developers with a powerful toolkit to optimize their applications' data management capabilities. -## Features +## Key Features -- **🗂️ PSR Standards**: Adheres to PHP-FIG PSR standards for interoperability. -- **📚 Comprehensive Interfaces**: Includes interfaces for common data structures such as Collection, Heap, Map, Queue, Stack, and Tree. -- **🚀 Modern PHP**: Utilizes PHP 8.3 features to ensure type safety and modern coding practices. -- **🔍 High Quality**: Ensures code quality and security through rigorous testing and analysis tools. +- **Optimized Performance**: Carefully crafted implementations ensure optimal time and space complexity for all operations. +- **Type Safety**: Leverages PHP 8.0+ features for enhanced type checking and improved code reliability. +- **Memory Efficiency**: Implements custom memory management strategies to minimize overhead. +- **Iterative and Recursive API**: Offers both iterative and recursive methods for key operations, allowing developers to choose based on their specific use case. +- **Serialization Support**: All data structures implement PHP's Serializable interface for easy storage and transmission. +- **Extensive Testing**: Comprehensive unit and integration tests ensure reliability and correctness. +- **PSR Compliance**: Adheres to PHP-FIG standards for coding style (PSR-12) and autoloading (PSR-4). -## Installation +## Available Data Structures -You can install the package via Composer: +### TreeSet -```bash -composer require kariricode/kariricode-data-structure +An ordered set implementation based on a self-balancing binary search tree (Red-Black Tree). + +#### Complexity Analysis + +- Time Complexity: + - Add, Remove, Contains: O(log n) + - Minimum/Maximum: O(log n) + - Iteration: O(n) +- Space Complexity: O(n) + +#### Key Methods + +```php +public function add($element): void +public function remove($element): bool +public function contains($element): bool +public function union(TreeSet $other): TreeSet +public function intersection(TreeSet $other): TreeSet +public function difference(TreeSet $other): TreeSet +public function find(mixed $element): ?mixed +``` + +#### Usage Example + +```php +use KaririCode\DataStructure\Set\TreeSet; + +$set = new TreeSet(); +$set->add(5); +$set->add(3); +$set->add(7); +echo $set->contains(3); // Output: true +echo $set->find(5); // Output: 5 ``` -## Usage +### ArrayDeque + +A double-ended queue using a dynamic circular array. -Implement the provided interfaces in your classes to ensure consistent and reliable functionality across different components of the KaririCode Framework. +#### Complexity Analysis -Example of implementing the `CollectionList` interface: +- Time Complexity: + - AddFirst, AddLast, RemoveFirst, RemoveLast: Amortized O(1) + - Get, Set: O(1) +- Space Complexity: O(n) + +#### Key Methods ```php -addFirst(1); +$deque->addLast(2); +echo $deque->removeFirst(); // Output: 1 +echo $deque->removeLast(); // Output: 2 +``` -class MyCollection implements CollectionList -{ - private array $items = []; - - public function add(mixed $item): void - { - $this->items[] = $item; - } - - public function remove(mixed $item): bool - { - $index = array_search($item, $this->items, true); - if ($index === false) { - return false; - } - unset($this->items[$index]); - return true; - } - - public function get(int $index): mixed - { - return $this->items[$index] ?? null; - } - - public function clear(): void - { - $this->items = []; - } - - public function getIterator(): \Traversable - { - return new \ArrayIterator($this->items); - } - - public function count(): int - { - return count($this->items); - } - - public function offsetExists(mixed $offset): bool - { - return isset($this->items[$offset]); - } - - public function offsetGet(mixed $offset): mixed - { - return $this->items[$offset] ?? null; - } - - public function offsetSet(mixed $offset, mixed $value): void - { - if ($offset === null) { - $this->items[] = $value; - } else { - $this->items[$offset] = $value; - } - } - - public function offsetUnset(mixed $offset): void - { - unset($this->items[$offset]); - } -} +### ArrayQueue + +A simple queue using a circular array, providing amortized O(1) time complexity for enqueue and dequeue operations. + +#### Complexity Analysis + +- Time Complexity: + - Enqueue, Dequeue: Amortized O(1) +- Space Complexity: O(n) + +#### Key Methods + +```php +public function enqueue(mixed $element): void +public function dequeue(): mixed +public function peek(): mixed +public function isEmpty(): bool +public function size(): int +public function clear(): void +public function add(mixed $element): void +public function removeFirst(): mixed +``` + +#### Usage Example + +```php +use KaririCode\DataStructure\Queue\ArrayQueue; + +$queue = new ArrayQueue(); +$queue->add(1); +$queue->enqueue(2); +echo $queue->dequeue(); // Output: 1 +``` + +### TreeMap + +A map implementation based on a self-balancing binary search tree (Red-Black Tree). + +#### Complexity Analysis + +- Time Complexity: + - Put, Get, Remove: O(log n) + - ContainsKey: O(log n) + - Iteration: O(n) +- Space Complexity: O(n) + +#### Key Methods + +```php +public function put($key, $value): void +public function get($key): ?mixed +public function remove($key): bool +public function containsKey($key): bool +public function keys(): array +public function values(): array +public function clear(): void +public function getItems(): array +``` + +#### Usage Example + +```php +use KaririCode\DataStructure\Map\TreeMap; + +$map = new TreeMap(); +$map->put("one", 1); +$map->put("two", 2); +echo $map->get("one"); // Output: 1 +$map->remove("two"); +echo $map->containsKey("two"); // Output: false +``` + +### LinkedList + +A doubly-linked list implementation providing efficient insertions and deletions. + +#### Complexity Analysis + +- Time Complexity: + - Add, Remove: O(1) + - Get, Set: O(n) + - Iteration: O(n) +- Space Complexity: O(n) + +#### Key Methods + +```php +public function add($element): void +public function remove($element): bool +public function contains($element): bool +public function get(int $index): mixed +public function set(int $index, $element): void +public function clear(): void +public function size(): int +public function getItems(): array +``` + +#### Usage Example + +```php +use KaririCode\DataStructure\Collection\LinkedList; + +$list = new LinkedList(); +$list->add("first"); +$list->add("second"); +echo $list->get(1); // Output: "second" +$list->remove("first"); +echo $list->contains("first"); // Output: false +``` + +### BinaryHeap + +A binary heap implementation supporting both min-heap and max-heap functionality. + +#### Complexity Analysis + +- Time Complexity: + - Insert, ExtractMin/Max: O(log n) + - PeekMin/Max: O(1) +- Space Complexity: O(n) + +#### Key Methods + +```php +public function insert($element): void +public function extractMin(): mixed // For MinHeap +public function extractMax(): mixed // For MaxHeap +public function peek(): mixed +public function size(): int +public function isEmpty(): bool +``` + +#### Usage Example + +```php +use KaririCode\DataStructure\BinaryHeap; + +$heap = new BinaryHeap(); +$heap->insert(5); +$heap->insert(3); +$heap->insert(7); +echo $heap->extractMin(); // Output: 3 +echo $heap->peek(); // Output: 5 +``` + +### HashMap + +A hash map using PHP's built-in array as the underlying storage, providing O(1) average time complexity for put, get, and remove operations. + +#### Complexity Analysis + +- Time Complexity: + - Put, Get, Remove: Average O(1), Worst O(n) + - ContainsKey: Average O(1), Worst O(n) +- Space Complexity: O(n) + +#### Key Methods + +```php +public function put($key, $value): void +public function get($key): ?mixed +public function remove($key): bool +public function containsKey($key): bool +public function keys(): array +public function values(): array +public function clear(): void +public function size(): int +public function getIterator(): \Iterator +``` + +#### Usage Example + +```php +use KaririCode\DataStructure\Map\HashMap; + +$map = new HashMap(); +$map->put("one", 1); +$map->put("two", 2); +echo $map->get("one"); // Output: 1 +$map->remove("two"); +echo $map->containsKey("two"); // Output: false +``` + +### BinaryHeap + +A binary heap implementation supporting both min-heap and max-heap functionality. + +#### Complexity Analysis + +- Time Complexity: + - Insert, ExtractMin/Max: O(log n) + - PeekMin/Max: O(1) +- Space Complexity: O(n) + +#### Key Methods + +```php +public function insert($element): void +public function extractMin(): mixed // For MinHeap +public function extractMax(): mixed // For MaxHeap +public function peek(): mixed +public function size(): int +public function isEmpty(): bool ``` -## Development Environment +#### Usage Example + +```php +use KaririCode\DataStructure\BinaryHeap; + +$heap = new BinaryHeap('min'); +$heap->add(5); +$heap->add(3); +$heap->add(7); +echo $heap->poll(); // Output: 3 +echo $heap->peek(); // Output: 5 +``` -### Docker +## Installation -To maintain consistency and ensure the environment's integrity, we provide a Docker setup: +### Requirements -- **🐳 Docker Compose**: Used to manage multi-container Docker applications. -- **📦 Dockerfile**: Defines the Docker image for the PHP environment. +- PHP 8.0 or higher +- Composer -To start the environment: +### Via Composer ```bash -make up +composer require kariricode/data-structures ``` -### Makefile +### Manual Installation -We include a `Makefile` to streamline common development tasks: +Add the following to your `composer.json`: -- **Start services**: `make up` -- **Stop services**: `make down` -- **Run tests**: `make test` -- **Install dependencies**: `make composer-install` -- **Run code style checks**: `make cs-check` -- **Fix code style issues**: `make cs-fix` -- **Security checks**: `make security-check` +```json +{ + "require": { + "kariricode/data-structures": "^1.0" + } +} +``` -For a complete list of commands, run: +Then run: ```bash -make help +composer update ``` ## Testing -To run the tests, you can use the following command: +To run tests, use PHPUnit. Ensure you have PHPUnit installed and configured. You can run the tests using the following command: ```bash -make test +vendor/bin/phpunit --testdox ``` ## Contributing -Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) for details on the process for submitting pull requests. +We welcome contributions from the community! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests. -## Support +### Development Setup -For any issues, please visit our [issue tracker](https://github.com/Kariri-PHP-Framework/kariri-contract/issues). +1. Fork and clone the repository. +2. Install dependencies: `composer install`. +3. Run tests: `./vendor/bin/phpunit`. +4. Submit a pull request. ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -## About KaririCode +## Support and Community + +- **Documentation**: [https://docs.kariricode.com/data-structures](https://docs.kariricode.com/data-structures) +- **Issue Tracker**: [GitHub Issues](https://github.com/kariricode/data-structures/issues) +- **Community Forum**: [KaririCode Community](https://community.kariricode.com) +- **Professional Support**: For enterprise-grade support, contact us at enterprise@kariricode.com -The KaririCode Framework is a modern, robust, and scalable PHP framework designed to streamline web development by providing a comprehensive set of tools and components. For more information, visit the [KaririCode website](https://kariricode.org/). +## Acknowledgments -Join the KaririCode Club for access to exclusive content, community support, and advanced tutorials on PHP and the KaririCode Framework. Learn more at [KaririCode Club](https://kariricode.org/club). +- The KaririCode Framework team and contributors. +- The PHP community for their continuous support and inspiration. +- [PHPBench](https://github.com/phpbench/phpbench) for performance benchmarking tools. + +## Roadmap + +- [ ] Implement Skip List data structure. +- [ ] Add support for concurrent access in HashMap. +- [ ] Develop a B-Tree implementation for large datasets. +- [ ] Enhance documentation with more real-world use cases. +- [ ] Implement a graph data structure and common algorithms. --- +Built with ❤️ by the KaririCode team. Empowering developers to build faster, more efficient PHP applications. + Maintained by Walmir Silva - [walmir.silva@kariricode.org](mailto:walmir.silva@kariricode.org) diff --git a/composer.lock b/composer.lock index 5b825b9..4fc0d38 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "kariricode/contract", - "version": "v2.3.1", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/KaririCode-Framework/kariricode-contract.git", - "reference": "619c2f472a874a59b118460d7ac55e4eaa07b636" + "reference": "8aba8e4abddaf5006bd2c88af4be8de4779e32a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/KaririCode-Framework/kariricode-contract/zipball/619c2f472a874a59b118460d7ac55e4eaa07b636", - "reference": "619c2f472a874a59b118460d7ac55e4eaa07b636", + "url": "https://api.github.com/repos/KaririCode-Framework/kariricode-contract/zipball/8aba8e4abddaf5006bd2c88af4be8de4779e32a4", + "reference": "8aba8e4abddaf5006bd2c88af4be8de4779e32a4", "shasum": "" }, "require": { @@ -66,7 +66,7 @@ "issues": "https://github.com/KaririCode-Framework/kariricode-contract/issues", "source": "https://github.com/KaririCode-Framework/kariricode-contract" }, - "time": "2024-07-01T21:51:33+00:00" + "time": "2024-07-02T13:40:09+00:00" } ], "packages-dev": [ diff --git a/tests/bplus_tree_example.php b/tests/bplus_tree_example.php new file mode 100644 index 0000000..542e42b --- /dev/null +++ b/tests/bplus_tree_example.php @@ -0,0 +1,383 @@ +n = 0; + $this->key = array_fill(0, 2 * $t - 1, 0); + $this->child = array_fill(0, 2 * $t, null); + $this->leaf = true; + } +} + +class BTree +{ + private int $T; + private ?Node $root; + + public function __construct(int $t) + { + $this->T = $t; + $this->root = new Node($t); + $this->root->n = 0; + $this->root->leaf = true; + } + + public function insert(int $k): void + { + if ($this->root->n == 2 * $this->T - 1) { + $s = new Node($this->T); + $s->leaf = false; + $s->child[0] = $this->root; + $this->splitChild($s, 0, $this->root); + $this->insertNonFull($s, $k); + $this->root = $s; + } else { + $this->insertNonFull($this->root, $k); + } + } + + private function insertNonFull(Node $x, int $k): void + { + $i = $x->n - 1; + + if ($x->leaf) { + while ($i >= 0 && $k < $x->key[$i]) { + $x->key[$i + 1] = $x->key[$i]; + --$i; + } + $x->key[$i + 1] = $k; + $x->n = $x->n + 1; + } else { + while ($i >= 0 && $k < $x->key[$i]) { + --$i; + } + ++$i; + if ($x->child[$i]->n == 2 * $this->T - 1) { + $this->splitChild($x, $i, $x->child[$i]); + if ($k > $x->key[$i]) { + ++$i; + } + } + $this->insertNonFull($x->child[$i], $k); + } + } + + private function splitChild(Node $x, int $i, Node $y): void + { + $z = new Node($this->T); + $z->leaf = $y->leaf; + $z->n = $this->T - 1; + + for ($j = 0; $j < $this->T - 1; ++$j) { + $z->key[$j] = $y->key[$j + $this->T]; + } + + if (! $y->leaf) { + for ($j = 0; $j < $this->T; ++$j) { + $z->child[$j] = $y->child[$j + $this->T]; + } + } + + $y->n = $this->T - 1; + + for ($j = $x->n; $j >= $i + 1; --$j) { + $x->child[$j + 1] = $x->child[$j]; + } + + $x->child[$i + 1] = $z; + + for ($j = $x->n - 1; $j >= $i; --$j) { + $x->key[$j + 1] = $x->key[$j]; + } + + $x->key[$i] = $y->key[$this->T - 1]; + $x->n = $x->n + 1; + } + + public function remove(int $k): void + { + if (! $this->root) { + return; + } + + $this->removeFromNode($this->root, $k); + + if (0 == $this->root->n) { + if ($this->root->leaf) { + $this->root = null; + } else { + $this->root = $this->root->child[0]; + } + } + } + + private function removeFromNode(Node $x, int $k): void + { + $idx = $this->findKey($x, $k); + + if ($idx < $x->n && $x->key[$idx] == $k) { + if ($x->leaf) { + $this->removeFromLeaf($x, $idx); + } else { + $this->removeFromNonLeaf($x, $idx); + } + } else { + if ($x->leaf) { + return; + } + + $flag = ($idx == $x->n); + + if ($x->child[$idx]->n < $this->T) { + $this->fill($x, $idx); + } + + if ($flag && $idx > $x->n) { + $this->removeFromNode($x->child[$idx - 1], $k); + } else { + $this->removeFromNode($x->child[$idx], $k); + } + } + } + + private function removeFromLeaf(Node $x, int $idx): void + { + for ($i = $idx + 1; $i < $x->n; ++$i) { + $x->key[$i - 1] = $x->key[$i]; + } + --$x->n; + } + + private function removeFromNonLeaf(Node $x, int $idx): void + { + $k = $x->key[$idx]; + + if ($x->child[$idx]->n >= $this->T) { + $pred = $this->getPred($x, $idx); + $x->key[$idx] = $pred; + $this->removeFromNode($x->child[$idx], $pred); + } elseif ($x->child[$idx + 1]->n >= $this->T) { + $succ = $this->getSucc($x, $idx); + $x->key[$idx] = $succ; + $this->removeFromNode($x->child[$idx + 1], $succ); + } else { + $this->merge($x, $idx); + $this->removeFromNode($x->child[$idx], $k); + } + } + + private function getPred(Node $x, int $idx): int + { + $cur = $x->child[$idx]; + while (! $cur->leaf) { + $cur = $cur->child[$cur->n]; + } + + return $cur->key[$cur->n - 1]; + } + + private function getSucc(Node $x, int $idx): int + { + $cur = $x->child[$idx + 1]; + while (! $cur->leaf) { + $cur = $cur->child[0]; + } + + return $cur->key[0]; + } + + private function fill(Node $x, int $idx): void + { + if (0 != $idx && $x->child[$idx - 1]->n >= $this->T) { + $this->borrowFromPrev($x, $idx); + } elseif ($idx != $x->n && $x->child[$idx + 1]->n >= $this->T) { + $this->borrowFromNext($x, $idx); + } else { + if ($idx != $x->n) { + $this->merge($x, $idx); + } else { + $this->merge($x, $idx - 1); + } + } + } + + private function borrowFromPrev(Node $x, int $idx): void + { + $child = $x->child[$idx]; + $sibling = $x->child[$idx - 1]; + + for ($i = $child->n - 1; $i >= 0; --$i) { + $child->key[$i + 1] = $child->key[$i]; + } + + if (! $child->leaf) { + for ($i = $child->n; $i >= 0; --$i) { + $child->child[$i + 1] = $child->child[$i]; + } + } + + $child->key[0] = $x->key[$idx - 1]; + + if (! $child->leaf) { + $child->child[0] = $sibling->child[$sibling->n]; + } + + $x->key[$idx - 1] = $sibling->key[$sibling->n - 1]; + + ++$child->n; + --$sibling->n; + } + + private function borrowFromNext(Node $x, int $idx): void + { + $child = $x->child[$idx]; + $sibling = $x->child[$idx + 1]; + + $child->key[$child->n] = $x->key[$idx]; + + if (! $child->leaf) { + $child->child[$child->n + 1] = $sibling->child[0]; + } + + $x->key[$idx] = $sibling->key[0]; + + for ($i = 1; $i < $sibling->n; ++$i) { + $sibling->key[$i - 1] = $sibling->key[$i]; + } + + if (! $sibling->leaf) { + for ($i = 1; $i <= $sibling->n; ++$i) { + $sibling->child[$i - 1] = $sibling->child[$i]; + } + } + + ++$child->n; + --$sibling->n; + } + + private function merge(Node $x, int $idx): void + { + $child = $x->child[$idx]; + $sibling = $x->child[$idx + 1]; + + $child->key[$this->T - 1] = $x->key[$idx]; + + for ($i = 0; $i < $sibling->n; ++$i) { + $child->key[$i + $this->T] = $sibling->key[$i]; + } + + if (! $child->leaf) { + for ($i = 0; $i <= $sibling->n; ++$i) { + $child->child[$i + $this->T] = $sibling->child[$i]; + } + } + + for ($i = $idx + 1; $i < $x->n; ++$i) { + $x->key[$i - 1] = $x->key[$i]; + } + + for ($i = $idx + 2; $i <= $x->n; ++$i) { + $x->child[$i - 1] = $x->child[$i]; + } + + $child->n += $sibling->n + 1; + --$x->n; + } + + private function findKey(Node $x, int $k): int + { + $idx = 0; + while ($idx < $x->n && $x->key[$idx] < $k) { + ++$idx; + } + + return $idx; + } + + public function search(int $k): ?Node + { + return $this->searchKeyInNode($this->root, $k); + } + + private function searchKeyInNode(?Node $x, int $k): ?Node + { + if (null === $x) { + return null; + } + + $i = 0; + while ($i < $x->n && $k > $x->key[$i]) { + ++$i; + } + if ($i < $x->n && $k == $x->key[$i]) { + return $x; + } + if ($x->leaf) { + return null; + } + + return $this->searchKeyInNode($x->child[$i], $k); + } + + public function printTree(): void + { + if ($this->root) { + $this->printNode($this->root, 0); + } else { + echo "The tree is empty.\n"; + } + } + + private function printNode(Node $x, int $level): void + { + echo str_repeat(' ', $level); + echo "Level $level: "; + for ($i = 0; $i < $x->n; ++$i) { + echo $x->key[$i] . ' '; + } + echo "\n"; + + if (! $x->leaf) { + for ($i = 0; $i <= $x->n; ++$i) { + $this->printNode($x->child[$i], $level + 1); + } + } + } +} + +// Exemplo de uso +$t = new BTree(3); // Árvore B com grau mínimo 3 +$keys = [10, 20, 5, 6, 12, 30, 7, 17]; + +echo 'Inserting keys: ' . implode(', ', $keys) . "\n"; +foreach ($keys as $key) { + $t->insert($key); +} + +echo "\nInitial B-Tree:\n"; +$t->printTree(); + +echo "\nRemoving key 6:\n"; +$t->remove(6); +$t->printTree(); + +echo "\nRemoving key 30:\n"; +$t->remove(30); +$t->printTree(); + +echo "\nSearching for key 12:\n"; +$result = $t->search(12); +echo $result ? "Found\n" : "Not found\n"; + +echo "\nSearching for key 15:\n"; +$result = $t->search(15); +echo $result ? "Found\n" : "Not found\n"; diff --git a/tests/bplus_tree_example2.php b/tests/bplus_tree_example2.php new file mode 100644 index 0000000..7485ff2 --- /dev/null +++ b/tests/bplus_tree_example2.php @@ -0,0 +1,380 @@ +numKeys = 0; + $this->keys = array_fill(0, 2 * $degree - 1, 0); + $this->children = array_fill(0, 2 * $degree, null); + $this->isLeaf = true; + } +} + +class BTree +{ + private int $degree; + private ?BTreeNode $root; + + public function __construct(int $degree) + { + $this->degree = $degree; + $this->root = new BTreeNode($degree); + $this->root->numKeys = 0; + $this->root->isLeaf = true; + } + + public function insert(int $key): void + { + if ($this->root->numKeys === 2 * $this->degree - 1) { + $newRoot = new BTreeNode($this->degree); + $newRoot->isLeaf = false; + $newRoot->children[0] = $this->root; + $this->splitChild($newRoot, 0, $this->root); + $this->insertNonFull($newRoot, $key); + $this->root = $newRoot; + } else { + $this->insertNonFull($this->root, $key); + } + } + + private function insertNonFull(BTreeNode $node, int $key): void + { + $index = $node->numKeys - 1; + + if ($node->isLeaf) { + while ($index >= 0 && $key < $node->keys[$index]) { + $node->keys[$index + 1] = $node->keys[$index]; + --$index; + } + $node->keys[$index + 1] = $key; + ++$node->numKeys; + } else { + while ($index >= 0 && $key < $node->keys[$index]) { + --$index; + } + ++$index; + if ($node->children[$index]->numKeys === 2 * $this->degree - 1) { + $this->splitChild($node, $index, $node->children[$index]); + if ($key > $node->keys[$index]) { + ++$index; + } + } + $this->insertNonFull($node->children[$index], $key); + } + } + + private function splitChild(BTreeNode $parentNode, int $index, BTreeNode $fullChildNode): void + { + $newNode = new BTreeNode($this->degree); + $newNode->isLeaf = $fullChildNode->isLeaf; + $newNode->numKeys = $this->degree - 1; + + for ($j = 0; $j < $this->degree - 1; ++$j) { + $newNode->keys[$j] = $fullChildNode->keys[$j + $this->degree]; + } + + if (! $fullChildNode->isLeaf) { + for ($j = 0; $j < $this->degree; ++$j) { + $newNode->children[$j] = $fullChildNode->children[$j + $this->degree]; + } + } + + $fullChildNode->numKeys = $this->degree - 1; + + for ($j = $parentNode->numKeys; $j >= $index + 1; --$j) { + $parentNode->children[$j + 1] = $parentNode->children[$j]; + } + + $parentNode->children[$index + 1] = $newNode; + + for ($j = $parentNode->numKeys - 1; $j >= $index; --$j) { + $parentNode->keys[$j + 1] = $parentNode->keys[$j]; + } + + $parentNode->keys[$index] = $fullChildNode->keys[$this->degree - 1]; + ++$parentNode->numKeys; + } + + public function remove(int $key): void + { + if (! $this->root) { + return; + } + + $this->removeFromNode($this->root, $key); + + if (0 === $this->root->numKeys) { + $this->root = $this->root->isLeaf ? null : $this->root->children[0]; + } + } + + private function removeFromNode(BTreeNode $node, int $key): void + { + $index = $this->findKeyIndex($node, $key); + + if ($index < $node->numKeys && $node->keys[$index] === $key) { + if ($node->isLeaf) { + $this->removeFromLeaf($node, $index); + } else { + $this->removeFromNonLeaf($node, $index); + } + } else { + if ($node->isLeaf) { + return; + } + + $isLastChild = ($index === $node->numKeys); + + if ($node->children[$index]->numKeys < $this->degree) { + $this->fillNode($node, $index); + } + + if ($isLastChild && $index > $node->numKeys) { + $this->removeFromNode($node->children[$index - 1], $key); + } else { + $this->removeFromNode($node->children[$index], $key); + } + } + } + + private function removeFromLeaf(BTreeNode $node, int $index): void + { + for ($i = $index + 1; $i < $node->numKeys; ++$i) { + $node->keys[$i - 1] = $node->keys[$i]; + } + --$node->numKeys; + } + + private function removeFromNonLeaf(BTreeNode $node, int $index): void + { + $key = $node->keys[$index]; + + if ($node->children[$index]->numKeys >= $this->degree) { + $predKey = $this->getPredecessor($node, $index); + $node->keys[$index] = $predKey; + $this->removeFromNode($node->children[$index], $predKey); + } elseif ($node->children[$index + 1]->numKeys >= $this->degree) { + $succKey = $this->getSuccessor($node, $index); + $node->keys[$index] = $succKey; + $this->removeFromNode($node->children[$index + 1], $succKey); + } else { + $this->mergeNodes($node, $index); + $this->removeFromNode($node->children[$index], $key); + } + } + + private function getPredecessor(BTreeNode $node, int $index): int + { + $currentNode = $node->children[$index]; + while (! $currentNode->isLeaf) { + $currentNode = $currentNode->children[$currentNode->numKeys]; + } + + return $currentNode->keys[$currentNode->numKeys - 1]; + } + + private function getSuccessor(BTreeNode $node, int $index): int + { + $currentNode = $node->children[$index + 1]; + while (! $currentNode->isLeaf) { + $currentNode = $currentNode->children[0]; + } + + return $currentNode->keys[0]; + } + + private function fillNode(BTreeNode $parentNode, int $index): void + { + if (0 !== $index && $parentNode->children[$index - 1]->numKeys >= $this->degree) { + $this->borrowFromPrevious($parentNode, $index); + } elseif ($index !== $parentNode->numKeys && $parentNode->children[$index + 1]->numKeys >= $this->degree) { + $this->borrowFromNext($parentNode, $index); + } else { + if ($index !== $parentNode->numKeys) { + $this->mergeNodes($parentNode, $index); + } else { + $this->mergeNodes($parentNode, $index - 1); + } + } + } + + private function borrowFromPrevious(BTreeNode $parentNode, int $index): void + { + $childNode = $parentNode->children[$index]; + $siblingNode = $parentNode->children[$index - 1]; + + for ($i = $childNode->numKeys - 1; $i >= 0; --$i) { + $childNode->keys[$i + 1] = $childNode->keys[$i]; + } + + if (! $childNode->isLeaf) { + for ($i = $childNode->numKeys; $i >= 0; --$i) { + $childNode->children[$i + 1] = $childNode->children[$i]; + } + } + + $childNode->keys[0] = $parentNode->keys[$index - 1]; + + if (! $childNode->isLeaf) { + $childNode->children[0] = $siblingNode->children[$siblingNode->numKeys]; + } + + $parentNode->keys[$index - 1] = $siblingNode->keys[$siblingNode->numKeys - 1]; + + ++$childNode->numKeys; + --$siblingNode->numKeys; + } + + private function borrowFromNext(BTreeNode $parentNode, int $index): void + { + $childNode = $parentNode->children[$index]; + $siblingNode = $parentNode->children[$index + 1]; + + $childNode->keys[$childNode->numKeys] = $parentNode->keys[$index]; + + if (! $childNode->isLeaf) { + $childNode->children[$childNode->numKeys + 1] = $siblingNode->children[0]; + } + + $parentNode->keys[$index] = $siblingNode->keys[0]; + + for ($i = 1; $i < $siblingNode->numKeys; ++$i) { + $siblingNode->keys[$i - 1] = $siblingNode->keys[$i]; + } + + if (! $siblingNode->isLeaf) { + for ($i = 1; $i <= $siblingNode->numKeys; ++$i) { + $siblingNode->children[$i - 1] = $siblingNode->children[$i]; + } + } + + ++$childNode->numKeys; + --$siblingNode->numKeys; + } + + private function mergeNodes(BTreeNode $parentNode, int $index): void + { + $childNode = $parentNode->children[$index]; + $siblingNode = $parentNode->children[$index + 1]; + + $childNode->keys[$this->degree - 1] = $parentNode->keys[$index]; + + for ($i = 0; $i < $siblingNode->numKeys; ++$i) { + $childNode->keys[$i + $this->degree] = $siblingNode->keys[$i]; + } + + if (! $childNode->isLeaf) { + for ($i = 0; $i <= $siblingNode->numKeys; ++$i) { + $childNode->children[$i + $this->degree] = $siblingNode->children[$i]; + } + } + + for ($i = $index + 1; $i < $parentNode->numKeys; ++$i) { + $parentNode->keys[$i - 1] = $parentNode->keys[$i]; + } + + for ($i = $index + 2; $i <= $parentNode->numKeys; ++$i) { + $parentNode->children[$i - 1] = $parentNode->children[$i]; + } + + $childNode->numKeys += $siblingNode->numKeys + 1; + --$parentNode->numKeys; + } + + private function findKeyIndex(BTreeNode $node, int $key): int + { + $index = 0; + while ($index < $node->numKeys && $node->keys[$index] < $key) { + ++$index; + } + + return $index; + } + + public function search(int $key): ?BTreeNode + { + return $this->searchInNode($this->root, $key); + } + + private function searchInNode(?BTreeNode $node, int $key): ?BTreeNode + { + if (null === $node) { + return null; + } + + $index = 0; + while ($index < $node->numKeys && $key > $node->keys[$index]) { + ++$index; + } + if ($index < $node->numKeys && $key === $node->keys[$index]) { + return $node; + } + if ($node->isLeaf) { + return null; + } + + return $this->searchInNode($node->children[$index], $key); + } + + public function printTree(): void + { + if ($this->root) { + $this->printNode($this->root, 0); + } else { + echo "The tree is empty.\n"; + } + } + + private function printNode(BTreeNode $node, int $level): void + { + echo str_repeat(' ', $level); + echo "Level $level: "; + for ($i = 0; $i < $node->numKeys; ++$i) { + echo $node->keys[$i] . ' '; + } + echo "\n"; + + if (! $node->isLeaf) { + for ($i = 0; $i <= $node->numKeys; ++$i) { + $this->printNode($node->children[$i], $level + 1); + } + } + } +} + +// Exemplo de uso +$degree = 3; // Árvore B com grau mínimo 3 +$btree = new BTree($degree); +$keys = [10, 20, 5, 6, 12, 30, 7, 17]; + +echo 'Inserting keys: ' . implode(', ', $keys) . "\n"; +foreach ($keys as $key) { + $btree->insert($key); +} + +echo "\nInitial B-Tree:\n"; +$btree->printTree(); + +echo "\nRemoving key 6:\n"; +$btree->remove(6); +$btree->printTree(); + +echo "\nRemoving key 30:\n"; +$btree->remove(30); +$btree->printTree(); + +echo "\nSearching for key 12:\n"; +$result = $btree->search(12); +echo $result ? "Found\n" : "Not found\n"; + +echo "\nSearching for key 15:\n"; +$result = $btree->search(15); +echo $result ? "Found\n" : "Not found\n"; diff --git a/tests/bplus_tree_example3.php b/tests/bplus_tree_example3.php new file mode 100644 index 0000000..26f1853 --- /dev/null +++ b/tests/bplus_tree_example3.php @@ -0,0 +1,404 @@ +keyCount = 0; + $this->keys = array_fill(0, 2 * $degree - 1, null); + $this->children = array_fill(0, 2 * $degree, null); + $this->isLeaf = true; + } +} + +class BTree +{ + private int $minDegree; + private ?BTreeNode $root; + + public function __construct(int $degree) + { + $this->minDegree = $degree; + $this->root = new BTreeNode($degree); + } + + public function insert(int|float|string $key): void + { + if ($this->root->keyCount === 2 * $this->minDegree - 1) { + $newRoot = new BTreeNode($this->minDegree); + $newRoot->isLeaf = false; + $newRoot->children[0] = $this->root; + $this->splitChild($newRoot, 0, $this->root); + $this->insertNonFull($newRoot, $key); + $this->root = $newRoot; + } else { + $this->insertNonFull($this->root, $key); + } + } + + private function insertNonFull(BTreeNode $node, int|float|string $key): void + { + $i = $node->keyCount - 1; + + if ($node->isLeaf) { + while ($i >= 0 && $key < $node->keys[$i]) { + $node->keys[$i + 1] = $node->keys[$i]; + --$i; + } + $node->keys[$i + 1] = $key; + ++$node->keyCount; + } else { + while ($i >= 0 && $key < $node->keys[$i]) { + --$i; + } + ++$i; + if ($node->children[$i]->keyCount === 2 * $this->minDegree - 1) { + $this->splitChild($node, $i, $node->children[$i]); + if ($key > $node->keys[$i]) { + ++$i; + } + } + $this->insertNonFull($node->children[$i], $key); + } + } + + private function splitChild(BTreeNode $parent, int $index, BTreeNode $fullNode): void + { + $newNode = new BTreeNode($this->minDegree); + $newNode->isLeaf = $fullNode->isLeaf; + $newNode->keyCount = $this->minDegree - 1; + + for ($j = 0; $j < $this->minDegree - 1; ++$j) { + $newNode->keys[$j] = $fullNode->keys[$j + $this->minDegree]; + } + + if (! $fullNode->isLeaf) { + for ($j = 0; $j < $this->minDegree; ++$j) { + $newNode->children[$j] = $fullNode->children[$j + $this->minDegree]; + } + } + + $fullNode->keyCount = $this->minDegree - 1; + + for ($j = $parent->keyCount; $j >= $index + 1; --$j) { + $parent->children[$j + 1] = $parent->children[$j]; + } + + $parent->children[$index + 1] = $newNode; + + for ($j = $parent->keyCount - 1; $j >= $index; --$j) { + $parent->keys[$j + 1] = $parent->keys[$j]; + } + + $parent->keys[$index] = $fullNode->keys[$this->minDegree - 1]; + ++$parent->keyCount; + } + + public function remove(int|float|string $key): void + { + if (! $this->root) { + return; + } + + $this->removeFromNode($this->root, $key); + + if (0 === $this->root->keyCount) { + $this->root = $this->root->isLeaf ? null : $this->root->children[0]; + } + } + + private function removeFromNode(BTreeNode $node, int|float|string $key): void + { + $idx = $this->findKey($node, $key); + + if ($idx < $node->keyCount && $node->keys[$idx] === $key) { + if ($node->isLeaf) { + $this->removeFromLeaf($node, $idx); + } else { + $this->removeFromNonLeaf($node, $idx); + } + } else { + if ($node->isLeaf) { + return; + } + + $flag = ($idx === $node->keyCount); + + if ($node->children[$idx]->keyCount < $this->minDegree) { + $this->fill($node, $idx); + } + + if ($flag && $idx > $node->keyCount) { + $this->removeFromNode($node->children[$idx - 1], $key); + } else { + $this->removeFromNode($node->children[$idx], $key); + } + } + } + + private function removeFromLeaf(BTreeNode $node, int $index): void + { + for ($i = $index + 1; $i < $node->keyCount; ++$i) { + $node->keys[$i - 1] = $node->keys[$i]; + } + --$node->keyCount; + } + + private function removeFromNonLeaf(BTreeNode $node, int $index): void + { + $key = $node->keys[$index]; + + if ($node->children[$index]->keyCount >= $this->minDegree) { + $pred = $this->getPredecessor($node, $index); + $node->keys[$index] = $pred; + $this->removeFromNode($node->children[$index], $pred); + } elseif ($node->children[$index + 1]->keyCount >= $this->minDegree) { + $succ = $this->getSuccessor($node, $index); + $node->keys[$index] = $succ; + $this->removeFromNode($node->children[$index + 1], $succ); + } else { + $this->merge($node, $index); + $this->removeFromNode($node->children[$index], $key); + } + } + + private function getPredecessor(BTreeNode $node, int $index): int|float|string + { + $current = $node->children[$index]; + while (! $current->isLeaf) { + $current = $current->children[$current->keyCount]; + } + + return $current->keys[$current->keyCount - 1]; + } + + private function getSuccessor(BTreeNode $node, int $index): int|float|string + { + $current = $node->children[$index + 1]; + while (! $current->isLeaf) { + $current = $current->children[0]; + } + + return $current->keys[0]; + } + + private function fill(BTreeNode $node, int $index): void + { + if (0 !== $index && $node->children[$index - 1]->keyCount >= $this->minDegree) { + $this->borrowFromPrevious($node, $index); + } elseif ($index !== $node->keyCount && $node->children[$index + 1]->keyCount >= $this->minDegree) { + $this->borrowFromNext($node, $index); + } else { + if ($index !== $node->keyCount) { + $this->merge($node, $index); + } else { + $this->merge($node, $index - 1); + } + } + } + + private function borrowFromPrevious(BTreeNode $node, int $index): void + { + $child = $node->children[$index]; + $sibling = $node->children[$index - 1]; + + for ($i = $child->keyCount - 1; $i >= 0; --$i) { + $child->keys[$i + 1] = $child->keys[$i]; + } + + if (! $child->isLeaf) { + for ($i = $child->keyCount; $i >= 0; --$i) { + $child->children[$i + 1] = $child->children[$i]; + } + } + + $child->keys[0] = $node->keys[$index - 1]; + + if (! $child->isLeaf) { + $child->children[0] = $sibling->children[$sibling->keyCount]; + } + + $node->keys[$index - 1] = $sibling->keys[$sibling->keyCount - 1]; + + ++$child->keyCount; + --$sibling->keyCount; + } + + private function borrowFromNext(BTreeNode $node, int $index): void + { + $child = $node->children[$index]; + $sibling = $node->children[$index + 1]; + + $child->keys[$child->keyCount] = $node->keys[$index]; + + if (! $child->isLeaf) { + $child->children[$child->keyCount + 1] = $sibling->children[0]; + } + + $node->keys[$index] = $sibling->keys[0]; + + for ($i = 1; $i < $sibling->keyCount; ++$i) { + $sibling->keys[$i - 1] = $sibling->keys[$i]; + } + + if (! $sibling->isLeaf) { + for ($i = 1; $i <= $sibling->keyCount; ++$i) { + $sibling->children[$i - 1] = $sibling->children[$i]; + } + } + + ++$child->keyCount; + --$sibling->keyCount; + } + + private function merge(BTreeNode $node, int $index): void + { + $child = $node->children[$index]; + $sibling = $node->children[$index + 1]; + + $child->keys[$this->minDegree - 1] = $node->keys[$index]; + + for ($i = 0; $i < $sibling->keyCount; ++$i) { + $child->keys[$i + $this->minDegree] = $sibling->keys[$i]; + } + + if (! $child->isLeaf) { + for ($i = 0; $i <= $sibling->keyCount; ++$i) { + $child->children[$i + $this->minDegree] = $sibling->children[$i]; + } + } + + for ($i = $index + 1; $i < $node->keyCount; ++$i) { + $node->keys[$i - 1] = $node->keys[$i]; + } + + for ($i = $index + 2; $i <= $node->keyCount; ++$i) { + $node->children[$i - 1] = $node->children[$i]; + } + + $child->keyCount += $sibling->keyCount + 1; + --$node->keyCount; + } + + private function findKey(BTreeNode $node, int|float|string $key): int + { + $index = 0; + while ($index < $node->keyCount && $node->keys[$index] < $key) { + ++$index; + } + + return $index; + } + + public function search(int|float|string $key): ?BTreeNode + { + return $this->searchKeyInNode($this->root, $key); + } + + private function searchKeyInNode(?BTreeNode $node, int|float|string $key): ?BTreeNode + { + if (null === $node) { + return null; + } + + $i = 0; + while ($i < $node->keyCount && $key > $node->keys[$i]) { + ++$i; + } + if ($i < $node->keyCount && $key == $node->keys[$i]) { + return $node; + } + if ($node->isLeaf) { + return null; + } + + return $this->searchKeyInNode($node->children[$i], $key); + } + + public function printTree(): void + { + if ($this->root) { + $this->printNode($this->root, 0); + } else { + echo "The tree is empty.\n"; + } + } + + private function printNode(BTreeNode $node, int $level): void + { + echo str_repeat(' ', $level); + echo "Level $level: "; + for ($i = 0; $i < $node->keyCount; ++$i) { + echo $node->keys[$i] . ' '; + } + echo "\n"; + + if (! $node->isLeaf) { + for ($i = 0; $i <= $node->keyCount; ++$i) { + $this->printNode($node->children[$i], $level + 1); + } + } + } +} + +// Exemplo de uso +$tree = new BTree(3); // Árvore B com grau mínimo 3 +$keys = [10, 20, 5, 6, 12, 30, 7, 17]; + +echo 'Inserting keys: ' . implode(', ', $keys) . "\n"; +foreach ($keys as $key) { + $tree->insert($key); +} + +echo "\nInitial B-Tree:\n"; +$tree->printTree(); + +echo "\nRemoving key 6:\n"; +$tree->remove(6); +$tree->printTree(); + +echo "\nRemoving key 30:\n"; +$tree->remove(30); +$tree->printTree(); + +echo "\nSearching for key 12:\n"; +$result = $tree->search(12); +echo $result ? "Found\n" : "Not found\n"; + +echo "\nSearching for key 15:\n"; +$result = $tree->search(15); +echo $result ? "Found\n" : "Not found\n"; + +$tree = new BTree(3); // Árvore B com grau mínimo 3 +$keys = ['D', 'B', 'A', 'C', 'F', 'E', 'H', 'G']; + +echo 'Inserting keys: ' . implode(', ', $keys) . "\n"; +foreach ($keys as $key) { + $tree->insert($key); +} + +echo "\nInitial B-Tree:\n"; +$tree->printTree(); + +echo "\nRemoving key 'C':\n"; +$tree->remove('C'); +$tree->printTree(); + +echo "\nRemoving key 'F':\n"; +$tree->remove('F'); +$tree->printTree(); + +echo "\nSearching for key 'B':\n"; +$result = $tree->search('B'); +echo $result ? "Found\n" : "Not found\n"; + +echo "\nSearching for key 'Z':\n"; +$result = $tree->search('Z'); +echo $result ? "Found\n" : "Not found\n"; diff --git a/tests/bplus_tree_example4.php b/tests/bplus_tree_example4.php new file mode 100644 index 0000000..810b366 --- /dev/null +++ b/tests/bplus_tree_example4.php @@ -0,0 +1,493 @@ +keyCount = 0; + $this->keys = array_fill(0, 2 * $degree - 1, null); + $this->children = array_fill(0, 2 * $degree, null); + $this->isLeaf = true; + } + + public function isFull(int $degree): bool + { + return $this->keyCount === 2 * $degree - 1; + } + + public function isUnderflow(int $degree): bool + { + return $this->keyCount < $degree; + } + + public function isLeaf(): bool + { + return $this->isLeaf; + } +} + +class BPlusTree +{ + private int $minDegree; + private ?BPlusTreeNode $root; + + public function __construct(int $degree) + { + $this->minDegree = $degree; + $this->root = new BPlusTreeNode($degree); + } + + public function insert(int|float|string $key): void + { + if ($this->isRootFull()) { + $newRoot = new BPlusTreeNode($this->minDegree); + $newRoot->isLeaf = false; + $newRoot->children[0] = $this->root; + $this->splitChild($newRoot, 0, $this->root); + $this->insertNonFull($newRoot, $key); + $this->root = $newRoot; + } else { + $this->insertNonFull($this->root, $key); + } + } + + private function isRootFull(): bool + { + return $this->root->isFull($this->minDegree); + } + + private function insertNonFull(BPlusTreeNode $node, int|float|string $key): void + { + $i = $node->keyCount - 1; + + if ($node->isLeaf) { + while ($i >= 0 && $key < $node->keys[$i]) { + $node->keys[$i + 1] = $node->keys[$i]; + --$i; + } + $node->keys[$i + 1] = $key; + ++$node->keyCount; + } else { + while ($i >= 0 && $key < $node->keys[$i]) { + --$i; + } + ++$i; + if ($node->children[$i]->isFull($this->minDegree)) { + $this->splitChild($node, $i, $node->children[$i]); + if ($key > $node->keys[$i]) { + ++$i; + } + } + $this->insertNonFull($node->children[$i], $key); + } + } + + private function splitChild(BPlusTreeNode $parent, int $index, BPlusTreeNode $fullNode): void + { + $newNode = new BPlusTreeNode($this->minDegree); + $newNode->isLeaf = $fullNode->isLeaf; + $newNode->keyCount = $this->minDegree - 1; + + for ($j = 0; $j < $this->minDegree - 1; ++$j) { + $newNode->keys[$j] = $fullNode->keys[$j + $this->minDegree]; + } + + if (! $fullNode->isLeaf) { + for ($j = 0; $j < $this->minDegree; ++$j) { + $newNode->children[$j] = $fullNode->children[$j + $this->minDegree]; + } + } + + $fullNode->keyCount = $this->minDegree - 1; + + for ($j = $parent->keyCount; $j >= $index + 1; --$j) { + $parent->children[$j + 1] = $parent->children[$j]; + } + + $parent->children[$index + 1] = $newNode; + + for ($j = $parent->keyCount - 1; $j >= $index; --$j) { + $parent->keys[$j + 1] = $parent->keys[$j]; + } + + $parent->keys[$index] = $fullNode->keys[$this->minDegree - 1]; + ++$parent->keyCount; + } + + public function remove(int|float|string $key): void + { + if (! $this->root) { + return; + } + + $this->removeFromNode($this->root, $key); + + if (0 === $this->root->keyCount) { + $this->root = $this->root->isLeaf() ? null : $this->root->children[0]; + } + } + + private function removeFromNode(BPlusTreeNode $node, int|float|string $key): void + { + $idx = $this->findKey($node, $key); + + if ($this->keyExistsInNode($node, $idx, $key)) { + $node->isLeaf() ? $this->removeFromLeaf($node, $idx) : $this->removeFromNonLeaf($node, $idx); + } else { + if ($node->isLeaf()) { + return; + } + + $this->handleChildUnderflow($node, $idx, $key); + } + } + + private function keyExistsInNode(BPlusTreeNode $node, int $idx, int|float|string $key): bool + { + return $idx < $node->keyCount && $node->keys[$idx] === $key; + } + + private function handleChildUnderflow(BPlusTreeNode $node, int $idx, int|float|string $key): void + { + $flag = ($idx === $node->keyCount); + + if ($node->children[$idx]->isUnderflow($this->minDegree)) { + $this->fill($node, $idx); + } + + $this->removeFromNode( + $node->children[$flag && $idx > $node->keyCount ? $idx - 1 : $idx], + $key + ); + } + + private function removeFromLeaf(BPlusTreeNode $node, int $index): void + { + for ($i = $index + 1; $i < $node->keyCount; ++$i) { + $node->keys[$i - 1] = $node->keys[$i]; + } + --$node->keyCount; + } + + private function removeFromNonLeaf(BPlusTreeNode $node, int $index): void + { + $key = $node->keys[$index]; + + if ($node->children[$index]->keyCount >= $this->minDegree) { + $pred = $this->getPredecessor($node, $index); + $node->keys[$index] = $pred; + $this->removeFromNode($node->children[$index], $pred); + } elseif ($node->children[$index + 1]->keyCount >= $this->minDegree) { + $succ = $this->getSuccessor($node, $index); + $node->keys[$index] = $succ; + $this->removeFromNode($node->children[$index + 1], $succ); + } else { + $this->merge($node, $index); + $this->removeFromNode($node->children[$index], $key); + } + } + + private function getPredecessor(BPlusTreeNode $node, int $index): int|float|string + { + $current = $node->children[$index]; + while (! $current->isLeaf) { + $current = $current->children[$current->keyCount]; + } + + return $current->keys[$current->keyCount - 1]; + } + + private function getSuccessor(BPlusTreeNode $node, int $index): int|float|string + { + $current = $node->children[$index + 1]; + while (! $current->isLeaf) { + $current = $current->children[0]; + } + + return $current->keys[0]; + } + + private function fill(BPlusTreeNode $node, int $index): void + { + if (0 !== $index && $node->children[$index - 1]->keyCount >= $this->minDegree) { + $this->borrowFromPrevious($node, $index); + } elseif ($index !== $node->keyCount && $node->children[$index + 1]->keyCount >= $this->minDegree) { + $this->borrowFromNext($node, $index); + } else { + $this->merge($node, $index !== $node->keyCount ? $index : $index - 1); + } + } + + private function borrowFromPrevious(BPlusTreeNode $node, int $index): void + { + $child = $node->children[$index]; + $sibling = $node->children[$index - 1]; + + for ($i = $child->keyCount - 1; $i >= 0; --$i) { + $child->keys[$i + 1] = $child->keys[$i]; + } + + if (! $child->isLeaf) { + for ($i = $child->keyCount; $i >= 0; --$i) { + $child->children[$i + 1] = $child->children[$i]; + } + } + + $child->keys[0] = $node->keys[$index - 1]; + + if (! $child->isLeaf) { + $child->children[0] = $sibling->children[$sibling->keyCount]; + } + + $node->keys[$index - 1] = $sibling->keys[$sibling->keyCount - 1]; + + ++$child->keyCount; + --$sibling->keyCount; + } + + private function borrowFromNext(BPlusTreeNode $node, int $index): void + { + $child = $node->children[$index]; + $sibling = $node->children[$index + 1]; + + $child->keys[$child->keyCount] = $node->keys[$index]; + + if (! $child->isLeaf) { + $child->children[$child->keyCount + 1] = $sibling->children[0]; + } + + $node->keys[$index] = $sibling->keys[0]; + + for ($i = 1; $i < $sibling->keyCount; ++$i) { + $sibling->keys[$i - 1] = $sibling->keys[$i]; + } + + if (! $sibling->isLeaf) { + for ($i = 1; $i <= $sibling->keyCount; ++$i) { + $sibling->children[$i - 1] = $sibling->children[$i]; + } + } + + ++$child->keyCount; + --$sibling->keyCount; + } + + private function merge(BPlusTreeNode $node, int $index): void + { + $child = $node->children[$index]; + $sibling = $node->children[$index + 1]; + + $child->keys[$this->minDegree - 1] = $node->keys[$index]; + + for ($i = 0; $i < $sibling->keyCount; ++$i) { + $child->keys[$i + $this->minDegree] = $sibling->keys[$i]; + } + + if (! $child->isLeaf) { + for ($i = 0; $i <= $sibling->keyCount; ++$i) { + $child->children[$i + $this->minDegree] = $sibling->children[$i]; + } + } + + for ($i = $index + 1; $i < $node->keyCount; ++$i) { + $node->keys[$i - 1] = $node->keys[$i]; + } + + for ($i = $index + 2; $i <= $node->keyCount; ++$i) { + $node->children[$i - 1] = $node->children[$i]; + } + + $child->keyCount += $sibling->keyCount + 1; + --$node->keyCount; + } + + private function findKey(BPlusTreeNode $node, int|float|string $key): int + { + $index = 0; + while ($index < $node->keyCount && $node->keys[$index] < $key) { + ++$index; + } + + return $index; + } + + public function search(int|float|string $key): ?BPlusTreeNode + { + return $this->searchKeyInNode($this->root, $key); + } + + private function searchKeyInNode(?BPlusTreeNode $node, int|float|string $key): ?BPlusTreeNode + { + if (null === $node) { + return null; + } + + $i = 0; + while ($i < $node->keyCount && $key > $node->keys[$i]) { + ++$i; + } + if ($i < $node->keyCount && $key == $node->keys[$i]) { + return $node; + } + if ($node->isLeaf()) { + return null; + } + + return $this->searchKeyInNode($node->children[$i], $key); + } + + public function printTree(): void + { + if ($this->root) { + $this->printNode($this->root, 0); + } else { + echo "The tree is empty.\n"; + } + } + + private function printNode(BPlusTreeNode $node, int $level): void + { + echo str_repeat(' ', $level); + echo "Level $level: "; + for ($i = 0; $i < $node->keyCount; ++$i) { + echo $node->keys[$i] . ' '; + } + echo "\n"; + + if (! $node->isLeaf()) { + for ($i = 0; $i <= $node->keyCount; ++$i) { + $this->printNode($node->children[$i], $level + 1); + } + } + } +} + +// Exemplo de uso +$tree = new BPlusTree(3); // Árvore B com grau mínimo 3 +$keys = [10, 20, 5, 6, 12, 30, 7, 17]; + +echo 'Inserting keys: ' . implode(', ', $keys) . "\n"; +foreach ($keys as $key) { + $tree->insert($key); +} + +echo "\nInitial B-Tree:\n"; +$tree->printTree(); + +echo "\nRemoving key 6:\n"; +$tree->remove(6); +$tree->printTree(); + +echo "\nRemoving key 30:\n"; +$tree->remove(30); +$tree->printTree(); + +echo "\nSearching for key 12:\n"; +$result = $tree->search(12); +echo $result ? "Found\n" : "Not found\n"; + +echo "\nSearching for key 15:\n"; +$result = $tree->search(15); +echo $result ? "Found\n" : "Not found\n"; + +$tree = new BPlusTree(3); // Árvore B com grau mínimo 3 +$keys = ['D', 'B', 'A', 'C', 'F', 'E', 'H', 'G']; + +echo 'Inserting keys: ' . implode(', ', $keys) . "\n"; +foreach ($keys as $key) { + $tree->insert($key); +} + +echo "\nInitial B-Tree:\n"; +$tree->printTree(); + +echo "\nRemoving key 'C':\n"; +$tree->remove('C'); +$tree->printTree(); + +echo "\nRemoving key 'F':\n"; +$tree->remove('F'); +$tree->printTree(); + +echo "\nSearching for key 'B':\n"; +$result = $tree->search('B'); +echo $result ? "Found\n" : "Not found\n"; + +echo "\nSearching for key 'Z':\n"; +$result = $tree->search('Z'); +echo $result ? "Found\n" : "Not found\n"; + +function generateRandomKeys(int $count, int $min = 1, int $max = 1000000): array +{ + $keys = []; + for ($i = 0; $i < $count; ++$i) { + $keys[] = rand($min, $max); + } + + return $keys; +} + +function measurePerformance(int $numKeys, int $iterations): array +{ + $degree = 3; + $insertTimes = []; + $removeTimes = []; + + for ($i = 0; $i < $iterations; ++$i) { + $tree = new BPlusTree($degree); + $keys = generateRandomKeys($numKeys); + + // Medir tempo de inserção + $startInsert = microtime(true); + foreach ($keys as $key) { + $tree->insert($key); + } + $endInsert = microtime(true); + $insertTimes[] = $endInsert - $startInsert; + + // Medir tempo de remoção + $startRemove = microtime(true); + foreach ($keys as $key) { + $tree->remove($key); + } + $endRemove = microtime(true); + $removeTimes[] = $endRemove - $startRemove; + } + + return [ + 'insert' => $insertTimes, + 'remove' => $removeTimes, + ]; +} + +function calculateAverage(array $times): float +{ + return array_sum($times) / count($times); +} + +function testPerformance(array $numKeysList, int $iterations): void +{ + foreach ($numKeysList as $numKeys) { + $times = measurePerformance($numKeys, $iterations); + $avgInsertTime = calculateAverage($times['insert']); + $avgRemoveTime = calculateAverage($times['remove']); + + echo "Número de chaves: $numKeys\n"; + echo 'Inserção - Tempo médio: ' . number_format($avgInsertTime, 6) . " segundos\n"; + echo 'Remoção - Tempo médio: ' . number_format($avgRemoveTime, 6) . " segundos\n"; + echo "-----------------------------------------\n"; + } +} + +// Definir diferentes quantidades de chaves para testar +$numKeysList = [10000, 50000, 100000, 200000, 500000]; // Você pode ajustar esses valores conforme necessário +$iterations = 5; // Número de iterações para cada quantidade de chaves + +testPerformance($numKeysList, $iterations); diff --git a/tests/bplus_tree_example5.php b/tests/bplus_tree_example5.php new file mode 100644 index 0000000..1f56e8a --- /dev/null +++ b/tests/bplus_tree_example5.php @@ -0,0 +1,543 @@ +keyCount = 0; + $this->keys = array_fill(0, 2 * $degree - 1, null); + $this->children = array_fill(0, 2 * $degree, null); + $this->isLeaf = true; + } + + public function isFull(int $degree): bool + { + return $this->keyCount === 2 * $degree - 1; + } + + public function isUnderflow(int $degree): bool + { + return $this->keyCount < $degree; + } + + public function isLeaf(): bool + { + return $this->isLeaf; + } +} + +class BPlusTree +{ + private int $minDegree; + private ?BPlusTreeNode $root; + + public function __construct(int $degree) + { + $this->minDegree = $degree; + $this->root = new BPlusTreeNode($degree); + } + + public function getRoot(): BPlusTreeNode + { + return $this->root; + } + + public function insert(mixed $key): void + { + if ($this->isRootFull()) { + $newRoot = new BPlusTreeNode($this->minDegree); + $newRoot->isLeaf = false; + $newRoot->children[0] = $this->root; + $this->splitChild($newRoot, 0, $this->root); + $this->insertNonFull($newRoot, $key); + $this->root = $newRoot; + } else { + $this->insertNonFull($this->root, $key); + } + } + + private function isRootFull(): bool + { + return $this->root->isFull($this->minDegree); + } + + private function insertNonFull(BPlusTreeNode $node, mixed $key): void + { + $i = $node->keyCount - 1; + + if ($node->isLeaf) { + while ($i >= 0 && $key < $node->keys[$i]) { + $node->keys[$i + 1] = $node->keys[$i]; + --$i; + } + $node->keys[$i + 1] = $key; + ++$node->keyCount; + } else { + while ($i >= 0 && $key < $node->keys[$i]) { + --$i; + } + ++$i; + if ($node->children[$i]->isFull($this->minDegree)) { + $this->splitChild($node, $i, $node->children[$i]); + if ($key > $node->keys[$i]) { + ++$i; + } + } + $this->insertNonFull($node->children[$i], $key); + } + } + + private function splitChild(BPlusTreeNode $parent, int $index, BPlusTreeNode $fullNode): void + { + $newNode = new BPlusTreeNode($this->minDegree); + $newNode->isLeaf = $fullNode->isLeaf; + $newNode->keyCount = $this->minDegree - 1; + + for ($j = 0; $j < $this->minDegree - 1; ++$j) { + $newNode->keys[$j] = $fullNode->keys[$j + $this->minDegree]; + } + + if (! $fullNode->isLeaf) { + for ($j = 0; $j < $this->minDegree; ++$j) { + $newNode->children[$j] = $fullNode->children[$j + $this->minDegree]; + } + } + + $fullNode->keyCount = $this->minDegree - 1; + + for ($j = $parent->keyCount; $j >= $index + 1; --$j) { + $parent->children[$j + 1] = $parent->children[$j]; + } + + $parent->children[$index + 1] = $newNode; + + for ($j = $parent->keyCount - 1; $j >= $index; --$j) { + $parent->keys[$j + 1] = $parent->keys[$j]; + } + + $parent->keys[$index] = $fullNode->keys[$this->minDegree - 1]; + ++$parent->keyCount; + } + + public function remove(mixed $key): void + { + if (! $this->root) { + return; + } + + $this->removeFromNode($this->root, $key); + + if (0 === $this->root->keyCount) { + $this->root = $this->root->isLeaf() ? null : $this->root->children[0]; + } + } + + private function removeFromNode(BPlusTreeNode $node, mixed $key): void + { + $idx = $this->findKey($node, $key); + + if ($this->keyExistsInNode($node, $idx, $key)) { + $node->isLeaf() ? $this->removeFromLeaf($node, $idx) : $this->removeFromNonLeaf($node, $idx); + } else { + if ($node->isLeaf()) { + return; + } + + $this->handleChildUnderflow($node, $idx, $key); + } + } + + private function keyExistsInNode(BPlusTreeNode $node, int $idx, mixed $key): bool + { + return $idx < $node->keyCount && $node->keys[$idx] === $key; + } + + private function handleChildUnderflow(BPlusTreeNode $node, int $idx, mixed $key): void + { + $flag = ($idx === $node->keyCount); + + if ($node->children[$idx]->isUnderflow($this->minDegree)) { + $this->fill($node, $idx); + } + + $this->removeFromNode( + $node->children[$flag && $idx > $node->keyCount ? $idx - 1 : $idx], + $key + ); + } + + private function removeFromLeaf(BPlusTreeNode $node, int $index): void + { + for ($i = $index + 1; $i < $node->keyCount; ++$i) { + $node->keys[$i - 1] = $node->keys[$i]; + } + --$node->keyCount; + } + + private function removeFromNonLeaf(BPlusTreeNode $node, int $index): void + { + $key = $node->keys[$index]; + + if ($node->children[$index]->keyCount >= $this->minDegree) { + $pred = $this->getPredecessor($node, $index); + $node->keys[$index] = $pred; + $this->removeFromNode($node->children[$index], $pred); + } elseif ($node->children[$index + 1]->keyCount >= $this->minDegree) { + $succ = $this->getSuccessor($node, $index); + $node->keys[$index] = $succ; + $this->removeFromNode($node->children[$index + 1], $succ); + } else { + $this->merge($node, $index); + $this->removeFromNode($node->children[$index], $key); + } + } + + private function getPredecessor(BPlusTreeNode $node, int $index): mixed + { + $current = $node->children[$index]; + while (! $current->isLeaf) { + $current = $current->children[$current->keyCount]; + } + + return $current->keys[$current->keyCount - 1]; + } + + private function getSuccessor(BPlusTreeNode $node, int $index): mixed + { + $current = $node->children[$index + 1]; + while (! $current->isLeaf) { + $current = $current->children[0]; + } + + return $current->keys[0]; + } + + private function fill(BPlusTreeNode $node, int $index): void + { + if (0 !== $index && $node->children[$index - 1]->keyCount >= $this->minDegree) { + $this->borrowFromPrevious($node, $index); + } elseif ($index !== $node->keyCount && $node->children[$index + 1]->keyCount >= $this->minDegree) { + $this->borrowFromNext($node, $index); + } else { + $this->merge($node, $index !== $node->keyCount ? $index : $index - 1); + } + } + + private function borrowFromPrevious(BPlusTreeNode $node, int $index): void + { + $child = $node->children[$index]; + $sibling = $node->children[$index - 1]; + + for ($i = $child->keyCount - 1; $i >= 0; --$i) { + $child->keys[$i + 1] = $child->keys[$i]; + } + + if (! $child->isLeaf) { + for ($i = $child->keyCount; $i >= 0; --$i) { + $child->children[$i + 1] = $child->children[$i]; + } + } + + $child->keys[0] = $node->keys[$index - 1]; + + if (! $child->isLeaf) { + $child->children[0] = $sibling->children[$sibling->keyCount]; + } + + $node->keys[$index - 1] = $sibling->keys[$sibling->keyCount - 1]; + + ++$child->keyCount; + --$sibling->keyCount; + } + + private function borrowFromNext(BPlusTreeNode $node, int $index): void + { + $child = $node->children[$index]; + $sibling = $node->children[$index + 1]; + + $child->keys[$child->keyCount] = $node->keys[$index]; + + if (! $child->isLeaf) { + $child->children[$child->keyCount + 1] = $sibling->children[0]; + } + + $node->keys[$index] = $sibling->keys[0]; + + for ($i = 1; $i < $sibling->keyCount; ++$i) { + $sibling->keys[$i - 1] = $sibling->keys[$i]; + } + + if (! $sibling->isLeaf) { + for ($i = 1; $i <= $sibling->keyCount; ++$i) { + $sibling->children[$i - 1] = $sibling->children[$i]; + } + } + + ++$child->keyCount; + --$sibling->keyCount; + } + + private function merge(BPlusTreeNode $node, int $index): void + { + $child = $node->children[$index]; + $sibling = $node->children[$index + 1]; + + $child->keys[$this->minDegree - 1] = $node->keys[$index]; + + for ($i = 0; $i < $sibling->keyCount; ++$i) { + $child->keys[$i + $this->minDegree] = $sibling->keys[$i]; + } + + if (! $child->isLeaf) { + for ($i = 0; $i <= $sibling->keyCount; ++$i) { + $child->children[$i + $this->minDegree] = $sibling->children[$i]; + } + } + + for ($i = $index + 1; $i < $node->keyCount; ++$i) { + $node->keys[$i - 1] = $node->keys[$i]; + } + + for ($i = $index + 2; $i <= $node->keyCount; ++$i) { + $node->children[$i - 1] = $node->children[$i]; + } + + $child->keyCount += $sibling->keyCount + 1; + --$node->keyCount; + } + + private function findKey(BPlusTreeNode $node, mixed $key): int + { + $index = 0; + while ($index < $node->keyCount && $node->keys[$index] < $key) { + ++$index; + } + + return $index; + } + + public function search(mixed $key): ?BPlusTreeNode + { + return $this->searchKeyInNode($this->root, $key); + } + + private function searchKeyInNode(?BPlusTreeNode $node, mixed $key): ?BPlusTreeNode + { + if (null === $node) { + return null; + } + + $i = 0; + while ($i < $node->keyCount && $key > $node->keys[$i]) { + ++$i; + } + if ($i < $node->keyCount && $key == $node->keys[$i]) { + return $node; + } + if ($node->isLeaf()) { + return null; + } + + return $this->searchKeyInNode($node->children[$i], $key); + } + + public function printTree(): void + { + if ($this->root) { + $this->printNode($this->root, 0); + } else { + echo "The tree is empty.\n"; + } + } + + private function printNode(BPlusTreeNode $node, int $level): void + { + echo str_repeat(' ', $level); + echo "Level $level: "; + for ($i = 0; $i < $node->keyCount; ++$i) { + echo $node->keys[$i] . ' '; + } + echo "\n"; + + if (! $node->isLeaf()) { + for ($i = 0; $i <= $node->keyCount; ++$i) { + $this->printNode($node->children[$i], $level + 1); + } + } + } +} + +class BPlusTreeSearchOptimizations +{ + /** + * Busca binária adaptada para BPlusTree. + */ + public static function binarySearch(BPlusTree $tree, mixed $key): ?BPlusTreeNode + { + $node = $tree->getRoot(); + while (null !== $node) { + $index = self::binarySearchInNode($node, $key); + if ($index < $node->keyCount && $node->keys[$index] === $key) { + return $node; + } + if ($node->isLeaf()) { + return null; + } + $node = $node->children[$index]; + } + + return null; + } + + private static function binarySearchInNode(BPlusTreeNode $node, mixed $key): int + { + $left = 0; + $right = $node->keyCount - 1; + + while ($left <= $right) { + $mid = $left + (($right - $left) >> 1); + + if ($node->keys[$mid] === $key) { + return $mid; + } + + if ($node->keys[$mid] < $key) { + $left = $mid + 1; + } else { + $right = $mid - 1; + } + } + + return $left; + } + + /** + * Busca por interpolação adaptada para BPlusTree. + */ + public static function interpolationSearch(BPlusTree $tree, int|float $key): ?BPlusTreeNode + { + $node = $tree->getRoot(); + while (null !== $node) { + $index = self::interpolationSearchInNode($node, $key); + if ($index < $node->keyCount && $node->keys[$index] === $key) { + return $node; + } + if ($node->isLeaf()) { + return null; + } + $node = $node->children[$index]; + } + + return null; + } + + private static function interpolationSearchInNode(BPlusTreeNode $node, int|float $key): int + { + $low = 0; + $high = $node->keyCount - 1; + + while ($low <= $high && $key >= $node->keys[$low] && $key <= $node->keys[$high]) { + if ($low === $high) { + return $low; + } + + $pos = $low + (($high - $low) / ($node->keys[$high] - $node->keys[$low])) * ($key - $node->keys[$low]); + $pos = (int) $pos; + + if ($node->keys[$pos] === $key) { + return $pos; + } + + if ($node->keys[$pos] < $key) { + $low = $pos + 1; + } else { + $high = $pos - 1; + } + } + + return $low; + } + + /** + * Busca exponencial adaptada para BPlusTree. + */ + public static function exponentialSearch(BPlusTree $tree, mixed $key): ?BPlusTreeNode + { + $node = $tree->getRoot(); + while (null !== $node) { + $index = self::exponentialSearchInNode($node, $key); + if ($index < $node->keyCount && $node->keys[$index] === $key) { + return $node; + } + if ($node->isLeaf()) { + return null; + } + $node = $node->children[$index]; + } + + return null; + } + + private static function exponentialSearchInNode(BPlusTreeNode $node, mixed $key): int + { + if (0 === $node->keyCount) { + return 0; + } + + $bound = 1; + while ($bound < $node->keyCount && $node->keys[$bound - 1] < $key) { + $bound *= 2; + } + + return self::binarySearchBounded($node, $key, (int) ($bound / 2), min($bound, $node->keyCount)); + } + + private static function binarySearchBounded(BPlusTreeNode $node, mixed $key, int $left, int $right): int + { + while ($left <= $right) { + $mid = $left + (($right - $left) >> 1); + + if ($node->keys[$mid] === $key) { + return $mid; + } + + if ($node->keys[$mid] < $key) { + $left = $mid + 1; + } else { + $right = $mid - 1; + } + } + + return $left; + } +} + +// Exemplo de uso +$tree = new BPlusTree(3); // Criando uma árvore B+ com grau mínimo 3 +for ($i = 1; $i <= 1000000; ++$i) { + $tree->insert($i); +} + +$target = 500000; + +$start = microtime(true); +$result = BPlusTreeSearchOptimizations::binarySearch($tree, $target); +$end = microtime(true); +echo 'Busca Binária: ' . ($end - $start) . " segundos\n"; + +$start = microtime(true); +$result = BPlusTreeSearchOptimizations::interpolationSearch($tree, $target); +$end = microtime(true); +echo 'Busca por Interpolação: ' . ($end - $start) . " segundos\n"; + +$start = microtime(true); +$result = BPlusTreeSearchOptimizations::exponentialSearch($tree, $target); +$end = microtime(true); +echo 'Busca Exponencial: ' . ($end - $start) . " segundos\n";