You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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);
}
}