mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-02-06 04:28:41 +00:00
continue graph implementation
This commit is contained in:
parent
89f9d0c2f3
commit
65d76e11ea
|
|
@ -1,226 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Orange Management
|
|
||||||
*
|
|
||||||
* PHP Version 7.4
|
|
||||||
*
|
|
||||||
* @package phpOMS\Stdlib\Graph
|
|
||||||
* @copyright Dennis Eichhorn
|
|
||||||
* @license OMS License 1.0
|
|
||||||
* @version 1.0.0
|
|
||||||
* @link https://orange-management.org
|
|
||||||
*/
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace phpOMS\Stdlib\Graph;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tree class.
|
|
||||||
*
|
|
||||||
* @package phpOMS\Stdlib\Graph
|
|
||||||
* @license OMS License 1.0
|
|
||||||
* @link https://orange-management.org
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
class BinaryTree extends Tree
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Invert the tree
|
|
||||||
*
|
|
||||||
* @return BinaryTree
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public static function invert($list) : self
|
|
||||||
{
|
|
||||||
if (empty($list->getNodes())) {
|
|
||||||
return $list;
|
|
||||||
}
|
|
||||||
|
|
||||||
$left = $list->getLeft();
|
|
||||||
$list->setLeft($list->invert($list->nodes[1]));
|
|
||||||
$list->setRight($list->invert($left));
|
|
||||||
|
|
||||||
return $list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get left node of a node.
|
|
||||||
*
|
|
||||||
* @param Node $base Tree node
|
|
||||||
*
|
|
||||||
* @return null|Node Left node
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getLeft(Node $base) : ?Node
|
|
||||||
{
|
|
||||||
$neighbors = $base->getNeighbors($base);
|
|
||||||
|
|
||||||
// todo: index can be wrong, see setLeft/setRight
|
|
||||||
return $neighbors[0] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get right node of a node.
|
|
||||||
*
|
|
||||||
* @param Node $base Tree node
|
|
||||||
*
|
|
||||||
* @return null|Node Right node
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getRight(Node $base) : ?Node
|
|
||||||
{
|
|
||||||
$neighbors = $this->getNeighbors($base);
|
|
||||||
|
|
||||||
// todo: index can be wrong, see setLeft/setRight
|
|
||||||
return $neighbors[1] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set left node of node.
|
|
||||||
*
|
|
||||||
* @param Node $base Base node
|
|
||||||
* @param Node $left Left node
|
|
||||||
*
|
|
||||||
* @return BinaryTree
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setLeft(Node $base, Node $left) : self
|
|
||||||
{
|
|
||||||
if ($this->getLeft($base) === null) {
|
|
||||||
$this->addNodeRelative($base, $left);
|
|
||||||
// todo: doesn't know that this is left
|
|
||||||
// todo: maybe need to add numerics to edges?
|
|
||||||
} else {
|
|
||||||
// todo: replace node
|
|
||||||
$a = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set right node of node.
|
|
||||||
*
|
|
||||||
* @param Node $base Base node
|
|
||||||
* @param Node $right Right node
|
|
||||||
*
|
|
||||||
* @return BinaryTree
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setRight(Node $base, Node $right) : self
|
|
||||||
{
|
|
||||||
if ($this->getRight($base) === null) {
|
|
||||||
$this->addNodeRelative($base, $right);
|
|
||||||
// todo: doesn't know that this is right
|
|
||||||
// todo: maybe need to add numerics to edges?
|
|
||||||
} else {
|
|
||||||
// todo: replace node
|
|
||||||
$a = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform action on tree in in-order.
|
|
||||||
*
|
|
||||||
* @param Node $node Tree node
|
|
||||||
* @param \Closure $callback Task to perform on node
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function inOrder(Node $node, \Closure $callback) : void
|
|
||||||
{
|
|
||||||
$this->inOrder($this->getLeft($node), $callback);
|
|
||||||
$callback($node);
|
|
||||||
$this->inOrder($this->getRight($node), $callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get nodes in vertical order.
|
|
||||||
*
|
|
||||||
* @param Node $node Tree node
|
|
||||||
* @param int $horizontalDistance Horizontal distance
|
|
||||||
* @param Node[] $order Ordered nodes by horizontal distance
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
private function getVerticalOrder(Node $node, int $horizontalDistance, array &$order) : void
|
|
||||||
{
|
|
||||||
if (!isset($order[$horizontalDistance])) {
|
|
||||||
$order[$horizontalDistance] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$order[$horizontalDistance][] = $node;
|
|
||||||
$left = $this->getLeft($node);
|
|
||||||
$right = $this->getRight($node);
|
|
||||||
|
|
||||||
if ($left !== null) {
|
|
||||||
$this->getVerticalOrder($left, $horizontalDistance - 1, $order);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($right !== null) {
|
|
||||||
$this->getVerticalOrder($right, $horizontalDistance + 1, $order);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform action on tree in vertical-order.
|
|
||||||
*
|
|
||||||
* @param Node $node Tree node
|
|
||||||
* @param \Closure $callback Task to perform on node
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function verticalOrder(Node $node, \Closure $callback) : void
|
|
||||||
{
|
|
||||||
$order = [];
|
|
||||||
$this->getVerticalOrder($node, 0, $order);
|
|
||||||
|
|
||||||
foreach ($order as $level) {
|
|
||||||
foreach ($level as $node) {
|
|
||||||
$callback($node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if tree is symmetric.
|
|
||||||
*
|
|
||||||
* @param null|Node $node1 Tree node1
|
|
||||||
* @param null|Node $node2 Tree node2 (optional, can be different tree)
|
|
||||||
*
|
|
||||||
* @return bool True if tree is symmetric, false if tree is not symmetric
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function isSymmetric(Node $node1 = null, Node $node2 = null) : bool
|
|
||||||
{
|
|
||||||
if (($node1 === null && $node2 === null)
|
|
||||||
|| $node1->isEqual($node2)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
} elseif ($node1 === null || $node2 === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$left1 = $this->getLeft($node1);
|
|
||||||
$right1 = $this->getRight($node1);
|
|
||||||
|
|
||||||
$left2 = $node2 !== null ? $this->getLeft($node1) : null;
|
|
||||||
$right2 = $node2 !== null ? $this->getRight($node1) : null;
|
|
||||||
|
|
||||||
return $this->isSymmetric($left1, $right2) && $this->isSymmetric($right1, $left2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -21,6 +21,19 @@ namespace phpOMS\Stdlib\Graph;
|
||||||
* @license OMS License 1.0
|
* @license OMS License 1.0
|
||||||
* @link https://orange-management.org
|
* @link https://orange-management.org
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @todo Orange-Management/phpOMS#10
|
||||||
|
* * Count all paths between 2 nodes
|
||||||
|
* * Return all paths between 2 nodes
|
||||||
|
* * Find cycles using graph coloring
|
||||||
|
* * Find a negative cycle
|
||||||
|
* * Find cycles with n length
|
||||||
|
* * Find cycles with odd length
|
||||||
|
* * Find shortest path between 2 nodes
|
||||||
|
* * Find longest path between 2 nodes
|
||||||
|
* * Find islands
|
||||||
|
* * Find all unreachable nodes
|
||||||
|
* * Check if strongly connected
|
||||||
*/
|
*/
|
||||||
class Graph
|
class Graph
|
||||||
{
|
{
|
||||||
|
|
@ -32,6 +45,14 @@ class Graph
|
||||||
*/
|
*/
|
||||||
protected $nodes = [];
|
protected $nodes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directed
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
protected $isDirected = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set node to graph.
|
* Set node to graph.
|
||||||
*
|
*
|
||||||
|
|
@ -62,6 +83,20 @@ class Graph
|
||||||
return $this->nodes[$key] ?? null;
|
return $this->nodes[$key] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Graph has node
|
||||||
|
*
|
||||||
|
* @param mixed $key Node key
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function hasNode($key) : bool
|
||||||
|
{
|
||||||
|
return isset($this->nodes[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get graph nodes
|
* Get graph nodes
|
||||||
*
|
*
|
||||||
|
|
@ -74,6 +109,51 @@ class Graph
|
||||||
return $this->nodes;
|
return $this->nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is directed graph
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function isDirected() : bool
|
||||||
|
{
|
||||||
|
return $this->isDirected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set graph directed
|
||||||
|
*
|
||||||
|
* @param bool $directed Is directed?
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setDirected(bool $directed) : void
|
||||||
|
{
|
||||||
|
$this->isDirected = $directed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get graph/edge costs
|
||||||
|
*
|
||||||
|
* @return int|float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getCost()
|
||||||
|
{
|
||||||
|
$edges = $this->getEdges();
|
||||||
|
$costs = 0;
|
||||||
|
|
||||||
|
foreach ($edges as $edge) {
|
||||||
|
$costs += $edge->getWeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $costs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get graph edges
|
* Get graph edges
|
||||||
*
|
*
|
||||||
|
|
@ -180,37 +260,161 @@ class Graph
|
||||||
/**
|
/**
|
||||||
* Get minimal spanning tree using Kruskal's algorithm.
|
* Get minimal spanning tree using Kruskal's algorithm.
|
||||||
*
|
*
|
||||||
* @return Tree
|
* @return Graph
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function getKruskalMinimalSpanningTree() : Tree
|
public function getKruskalMinimalSpanningTree() : self
|
||||||
{
|
{
|
||||||
return new Tree();
|
$graph = new self();
|
||||||
|
$edges = $this->getEdges();
|
||||||
|
|
||||||
|
\usort($edges, Edge::class . '::compare');
|
||||||
|
|
||||||
|
foreach ($edges as $edge) {
|
||||||
|
if ($graph->hasNode($edge->getNode1()->getId())
|
||||||
|
&& $graph->hasNode($edge->getNode2()->getId())
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Node $node1 */
|
||||||
|
$node1 = $graph->hasNode($edge->getNode1()->getId()) ? $graph->getNode($edge->getNode1()->getId()) : clone $edge->getNode1();
|
||||||
|
/** @var Node $node2 */
|
||||||
|
$node2 = $graph->hasNode($edge->getNode2()->getId()) ? $graph->getNode($edge->getNode2()->getId()) : clone $edge->getNode2();
|
||||||
|
|
||||||
|
$node1->setNodeRelative($node2);
|
||||||
|
|
||||||
|
if (!$graph->hasNode($edge->getNode1()->getId())) {
|
||||||
|
$graph->setNode($node1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$graph->hasNode($edge->getNode2()->getId())) {
|
||||||
|
$graph->setNode($node2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get minimal spanning tree using Prim's algorithm
|
* Has cycle in graph.
|
||||||
*
|
*
|
||||||
* @return Tree
|
* @return bool
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function getPrimMinimalSpanningTree() : Tree
|
public function hasCycle() : bool
|
||||||
{
|
{
|
||||||
return new Tree();
|
return $this->isDirected ? $this->hasCycleDirected() : $this->hasCycleUndirected();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get circles in graph.
|
* Has cycle in directed graph.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return bool
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function getCircle() : array
|
private function hasCycleDirected() : bool
|
||||||
{
|
{
|
||||||
return [];
|
$visited = [];
|
||||||
|
$recStack = [];
|
||||||
|
|
||||||
|
foreach ($this->nodes as $node) {
|
||||||
|
if ($this->hasDirectedCyclicUtil($node->getId(), $visited, $recStack)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has cycle in directed graph.
|
||||||
|
*
|
||||||
|
* @param string $node Node name
|
||||||
|
* @param array $visited Visited nodes
|
||||||
|
* @param array $stack Recursion stack
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function hasDirectedCyclicUtil(string $node, array &$visited, array &$stack) : bool
|
||||||
|
{
|
||||||
|
if (isset($visited[$node]) && $visited[$node]) {
|
||||||
|
$stack[$node] = false;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$visited[$node] = true;
|
||||||
|
$stack[$node] = true;
|
||||||
|
|
||||||
|
$neighbors = $this->nodes[$node]->getNeighbors();
|
||||||
|
foreach ($neighbors as $neighbor) {
|
||||||
|
if ((!isset($visited[$neighbor->getId()]) || !$visited[$neighbor->getId()])
|
||||||
|
&& $this->hasDirectedCyclicUtil($neighbor->getId(), $visited, $stack)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} elseif (isset($stack[$neighbor->getId()]) && $stack[$neighbor->getId()]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stack[$node] = false;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has cycle in undirected graph.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function hasCycleUndirected() : bool
|
||||||
|
{
|
||||||
|
$visited = [];
|
||||||
|
|
||||||
|
foreach ($this->nodes as $node) {
|
||||||
|
if (!isset($visited[$node->getId()]) && $this->hasUndirectedCyclicUtil($node->getId(), $visited, null)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has cycle in undirected graph.
|
||||||
|
*
|
||||||
|
* @param string $node Node name
|
||||||
|
* @param array $visited Visited nodes
|
||||||
|
* @param null|string $parent Parent node
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function hasUndirectedCyclicUtil(string $node, array &$visited, ?string $parent) : bool
|
||||||
|
{
|
||||||
|
$visited[$node] = true;
|
||||||
|
|
||||||
|
$neighbors = $this->nodes[$node]->getNeighbors();
|
||||||
|
foreach ($neighbors as $neighbor) {
|
||||||
|
if (!isset($visited[$neighbor->getId()])) {
|
||||||
|
if ($this->hasUndirectedCyclicUtil($neighbor->getId(), $visited, $node)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} elseif ($neighbor->getId() !== $parent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -411,7 +615,6 @@ class Graph
|
||||||
*/
|
*/
|
||||||
public function isConnected() : bool
|
public function isConnected() : bool
|
||||||
{
|
{
|
||||||
// todo: implement
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -424,7 +627,6 @@ class Graph
|
||||||
*/
|
*/
|
||||||
public function getUnconnected() : array
|
public function getUnconnected() : array
|
||||||
{
|
{
|
||||||
// todo: implement
|
|
||||||
// get all unconnected sub graphs
|
// get all unconnected sub graphs
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -439,7 +641,6 @@ class Graph
|
||||||
*/
|
*/
|
||||||
public function isBipartite() : bool
|
public function isBipartite() : bool
|
||||||
{
|
{
|
||||||
// todo: implement
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -452,7 +653,6 @@ class Graph
|
||||||
*/
|
*/
|
||||||
public function isTriangleFree() : bool
|
public function isTriangleFree() : bool
|
||||||
{
|
{
|
||||||
// todo: implement
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -465,7 +665,6 @@ class Graph
|
||||||
*/
|
*/
|
||||||
public function isCircleFree() : bool
|
public function isCircleFree() : bool
|
||||||
{
|
{
|
||||||
// todo: implement
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,257 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Orange Management
|
|
||||||
*
|
|
||||||
* PHP Version 7.4
|
|
||||||
*
|
|
||||||
* @package phpOMS\Stdlib\Graph
|
|
||||||
* @copyright Dennis Eichhorn
|
|
||||||
* @license OMS License 1.0
|
|
||||||
* @version 1.0.0
|
|
||||||
* @link https://orange-management.org
|
|
||||||
*/
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace phpOMS\Stdlib\Graph;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tree class.
|
|
||||||
*
|
|
||||||
* @package phpOMS\Stdlib\Graph
|
|
||||||
* @license OMS License 1.0
|
|
||||||
* @link https://orange-management.org
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
class Tree extends Graph
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Root node.
|
|
||||||
*
|
|
||||||
* @var Node
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
private $root = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$root = new Node();
|
|
||||||
parent::addNode($root);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a note relative to a node.
|
|
||||||
*
|
|
||||||
* @param Node $base Base node
|
|
||||||
* @param Node $node Node to add
|
|
||||||
*
|
|
||||||
* @return Tree
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function addRelativeNode(Node $base, Node $node) : self
|
|
||||||
{
|
|
||||||
parent::addNode($node);
|
|
||||||
parent::addEdge(new Edge($base, $node));
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get maximum tree depth.
|
|
||||||
*
|
|
||||||
* @param Node $node Tree node
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getMaxDepth(Node $node = null) : int
|
|
||||||
{
|
|
||||||
$currentNode = $node ?? $this->root;
|
|
||||||
|
|
||||||
if ($currentNode === null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$depth = 1;
|
|
||||||
$neighbors = $this->getNeighbors($currentNode);
|
|
||||||
|
|
||||||
foreach ($neighbors as $neighbor) {
|
|
||||||
$depth = \max($depth, $depth + $this->getMaxDepth($neighbor));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get minimum tree path.
|
|
||||||
*
|
|
||||||
* @param Node $node Tree node
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getMinDepth(Node $node = null) : int
|
|
||||||
{
|
|
||||||
$currentNode = $node ?? $this->root;
|
|
||||||
|
|
||||||
if ($currentNode === null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$depth = [];
|
|
||||||
$neighbors = $this->getNeighbors($currentNode);
|
|
||||||
|
|
||||||
foreach ($neighbors as $neighbor) {
|
|
||||||
$depth[] = $this->getMaxDepth($neighbor);
|
|
||||||
}
|
|
||||||
|
|
||||||
$depth = empty($depth) ? 0 : $depth;
|
|
||||||
|
|
||||||
return \min($depth) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform task on tree nodes in level order.
|
|
||||||
*
|
|
||||||
* @param Node $node Tree node
|
|
||||||
* @param \Closure $callback Task to perform
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function levelOrder(Node $node, \Closure $callback) : void
|
|
||||||
{
|
|
||||||
$depth = $this->getMaxDepth();
|
|
||||||
|
|
||||||
for ($i = 1; $i < $depth; ++$i) {
|
|
||||||
$nodes = $this->getLevel($i);
|
|
||||||
callback($nodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if node is leaf.
|
|
||||||
*
|
|
||||||
* @param Node $node Tree node
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function isLeaf(Node $node) : bool
|
|
||||||
{
|
|
||||||
return \count($this->getEdgesOfNode($node)) === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all nodes of a specific level.
|
|
||||||
*
|
|
||||||
* @param int $level Level to retrieve
|
|
||||||
* @param Node $node Tree node
|
|
||||||
*
|
|
||||||
* @return Node[]
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getLevelNodes(int $level, Node $node) : array
|
|
||||||
{
|
|
||||||
--$level;
|
|
||||||
$neighbors = $this->getNeighbors($node);
|
|
||||||
$nodes = [];
|
|
||||||
|
|
||||||
if ($level === 1) {
|
|
||||||
return $neighbors;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($neighbors as $neighbor) {
|
|
||||||
\array_merge($nodes, $this->getLevelNodes($level, $neighbor));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the tree is full.
|
|
||||||
*
|
|
||||||
* @param int $type Child nodes per non-leaf node
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function isFull(int $type) : bool
|
|
||||||
{
|
|
||||||
if (\count($this->edges) % $type !== 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->nodes as $node) {
|
|
||||||
$neighbors = \count($this->getNeighbors($node));
|
|
||||||
|
|
||||||
if ($neighbors !== $type && $neighbors !== 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform action on tree in pre-order.
|
|
||||||
*
|
|
||||||
* @param Node $node Tree node
|
|
||||||
* @param \Closure $callback Task to perform on node
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function preOrder(Node $node, \Closure $callback) : void
|
|
||||||
{
|
|
||||||
if (\count($this->nodes) === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$callback($node);
|
|
||||||
$neighbors = $this->getNeighbors($node);
|
|
||||||
|
|
||||||
foreach ($neighbors as $neighbor) {
|
|
||||||
// todo: get neighbors needs to return in ordered way
|
|
||||||
$this->preOrder($neighbor, $callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform action on tree in post-order.
|
|
||||||
*
|
|
||||||
* @param Node $node Tree node
|
|
||||||
* @param \Closure $callback Task to perform on node
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function postOrder(Node $node, \Closure $callback) : void
|
|
||||||
{
|
|
||||||
if (\count($this->nodes) === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$neighbors = $this->getNeighbors($node);
|
|
||||||
|
|
||||||
foreach ($neighbors as $neighbor) {
|
|
||||||
// todo: get neighbors needs to return in ordered way
|
|
||||||
$this->postOrder($neighbor, $callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
$callback($node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -50,7 +50,6 @@ class GraphTest extends \PHPUnit\Framework\TestCase
|
||||||
self::assertTrue($graph->isCircleFree());
|
self::assertTrue($graph->isCircleFree());
|
||||||
|
|
||||||
self::assertEquals([], $graph->getBridges());
|
self::assertEquals([], $graph->getBridges());
|
||||||
self::assertEquals([], $graph->getCircle());
|
|
||||||
self::assertEquals([], $graph->getFloydWarshallShortestPath());
|
self::assertEquals([], $graph->getFloydWarshallShortestPath());
|
||||||
self::assertEquals([], $graph->getDijkstraShortestPath());
|
self::assertEquals([], $graph->getDijkstraShortestPath());
|
||||||
self::assertEquals([], $graph->depthFirstTraversal());
|
self::assertEquals([], $graph->depthFirstTraversal());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user