151 lines
4.6 KiB
PHP
151 lines
4.6 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace Graphp\Algorithms\Tree;
|
||
|
|
||
|
use Graphp\Algorithms\Tree\Base as Tree;
|
||
|
use Fhaculty\Graph\Exception\UnderflowException;
|
||
|
use Fhaculty\Graph\Exception\UnexpectedValueException;
|
||
|
use Graphp\Algorithms\Search\Base as Search;
|
||
|
use Graphp\Algorithms\Search\StrictDepthFirst;
|
||
|
use Fhaculty\Graph\Vertex;
|
||
|
use Fhaculty\Graph\Set\Vertices;
|
||
|
use Fhaculty\Graph\Edge\Base as Edge;
|
||
|
use Fhaculty\Graph\Edge\Undirected as UndirectedEdge;
|
||
|
use Graphp\Algorithms\Degree;
|
||
|
|
||
|
/**
|
||
|
* Undirected tree implementation
|
||
|
*
|
||
|
* An undirected tree is a connected Graph (single component) with no cycles.
|
||
|
* Every undirected Tree is an undirected Graph, but not every undirected Graph
|
||
|
* is an undirected Tree.
|
||
|
*
|
||
|
* A
|
||
|
* / \
|
||
|
* B C
|
||
|
* / \
|
||
|
* D E
|
||
|
*
|
||
|
* Undirected trees do not have special root Vertices (like the above picture
|
||
|
* might suggest). The above tree Graph can also be equivalently be pictured
|
||
|
* like this:
|
||
|
*
|
||
|
* C
|
||
|
* /|\
|
||
|
* / | \
|
||
|
* A D E
|
||
|
* /
|
||
|
* B
|
||
|
*
|
||
|
* If you're looking for a tree with a designated root Vertex, use directed,
|
||
|
* rooted trees (BaseDirected).
|
||
|
*
|
||
|
* @link http://en.wikipedia.org/wiki/Tree_%28graph_theory%29
|
||
|
* @see BaseDirected if you're looking for directed, rooted trees
|
||
|
*/
|
||
|
class Undirected extends Tree
|
||
|
{
|
||
|
/**
|
||
|
* checks if this is a tree
|
||
|
*
|
||
|
* @return boolean
|
||
|
* @uses Vertices::isEmpty() to skip null Graphs (a Graph with no Vertices is *NOT* a valid tree)
|
||
|
* @uses Vertices::getVertexFirst() to get get get random "root" Vertex to start search from
|
||
|
* @uses self::getVerticesSubtreeRecursive() to count number of vertices connected to root
|
||
|
*/
|
||
|
public function isTree()
|
||
|
{
|
||
|
if ($this->graph->getVertices()->isEmpty()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// every vertex can represent a root vertex, so just pick one
|
||
|
$root = $this->graph->getVertices()->getVertexFirst();
|
||
|
|
||
|
$vertices = array();
|
||
|
try {
|
||
|
$this->getVerticesSubtreeRecursive($root, $vertices, null);
|
||
|
}
|
||
|
catch (UnexpectedValueException $e) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return (count($vertices) === count($this->graph->getVertices()));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* checks if the given $vertex is a leaf (outermost vertex with exactly one edge)
|
||
|
*
|
||
|
* @param Vertex $vertex
|
||
|
* @return boolean
|
||
|
* @uses Degree::getDegreeVertex()
|
||
|
*/
|
||
|
public function isVertexLeaf(Vertex $vertex)
|
||
|
{
|
||
|
return ($this->degree->getDegreeVertex($vertex) === 1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* checks if the given $vertex is an internal vertex (inner vertex with at least 2 edges)
|
||
|
*
|
||
|
* @param Vertex $vertex
|
||
|
* @return boolean
|
||
|
* @uses Degree::getDegreeVertex()
|
||
|
*/
|
||
|
public function isVertexInternal(Vertex $vertex)
|
||
|
{
|
||
|
return ($this->degree->getDegreeVertex($vertex) >= 2);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* get subtree for given Vertex and ignore path to "parent" ignoreVertex
|
||
|
*
|
||
|
* @param Vertex $vertex
|
||
|
* @param Vertex[] $vertices
|
||
|
* @param Vertex|null $ignore
|
||
|
* @throws UnexpectedValueException for cycles or directed edges (check isTree()!)
|
||
|
* @uses self::getVerticesNeighbor()
|
||
|
* @uses self::getVerticesSubtreeRecursive() to recurse into sub-subtrees
|
||
|
*/
|
||
|
private function getVerticesSubtreeRecursive(Vertex $vertex, array &$vertices, Vertex $ignore = null)
|
||
|
{
|
||
|
if (isset($vertices[$vertex->getId()])) {
|
||
|
// vertex already visited => must be a cycle
|
||
|
throw new UnexpectedValueException('Vertex already visited');
|
||
|
}
|
||
|
$vertices[$vertex->getId()] = $vertex;
|
||
|
|
||
|
foreach ($this->getVerticesNeighbor($vertex) as $vertexNeighboor) {
|
||
|
if ($vertexNeighboor === $ignore) {
|
||
|
// ignore source vertex only once
|
||
|
$ignore = null;
|
||
|
continue;
|
||
|
}
|
||
|
$this->getVerticesSubtreeRecursive($vertexNeighboor, $vertices, $vertex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* get neighbor vertices for given start vertex
|
||
|
*
|
||
|
* @param Vertex $vertex
|
||
|
* @throws UnexpectedValueException for directed edges
|
||
|
* @return Vertices (might include possible duplicates)
|
||
|
* @uses Vertex::getEdges()
|
||
|
* @uses Edge::getVertexToFrom()
|
||
|
* @see Vertex::getVerticesEdge()
|
||
|
*/
|
||
|
private function getVerticesNeighbor(Vertex $vertex)
|
||
|
{
|
||
|
$vertices = array();
|
||
|
foreach ($vertex->getEdges() as $edge) {
|
||
|
/* @var Edge $edge */
|
||
|
if (!($edge instanceof UndirectedEdge)) {
|
||
|
throw new UnexpectedValueException('Directed edge encountered');
|
||
|
}
|
||
|
$vertices[] = $edge->getVertexToFrom($vertex);
|
||
|
}
|
||
|
return new Vertices($vertices);
|
||
|
}
|
||
|
}
|