Skip to content

Commit 6d931fb

Browse files
committed
Add Tree algorithms (skeleton)
1 parent 937ca8e commit 6d931fb

File tree

5 files changed

+381
-0
lines changed

5 files changed

+381
-0
lines changed

src/Tree/Base.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace Fhaculty\Graph\Algorithm\Tree;
4+
5+
use Fhaculty\Graph\Algorithm\BaseGraph;
6+
use Fhaculty\Graph\Graph;
7+
use Fhaculty\Graph\Vertex;
8+
use Fhaculty\Graph\Exception\UnderflowException;
9+
use Fhaculty\Graph\Exception\UnexpectedValueException;
10+
use Fhaculty\Graph\Algorithm\Search\StrictDepthFirst;
11+
12+
/**
13+
*
14+
* @link http://en.wikipedia.org/wiki/Tree_%28graph_theory%29
15+
* @see OutTree
16+
* @see InTree
17+
*/
18+
abstract class Base extends BaseGraph
19+
{
20+
/**
21+
* checks whether the given graph is actually a tree
22+
*
23+
* @return boolean
24+
*/
25+
abstract public function isTree();
26+
27+
/**
28+
* checks if the given $vertex is a leaf (outermost vertex / leaf node / external node / terminal node)
29+
*
30+
* @param Vertex $vertex
31+
* @return boolean
32+
*/
33+
abstract public function isVertexLeaf(Vertex $vertex);
34+
35+
/**
36+
* checks if the given $vertex is an internal vertex (inner node / inode / branch node / somewhere in the "middle" of the tree)
37+
*
38+
* @param Vertex $vertex
39+
* @return boolean
40+
*/
41+
abstract public function isVertexInternal(Vertex $vertex);
42+
43+
/**
44+
* get array of leaf vertices (outermost vertices with no children)
45+
*
46+
* @return Vertex[]
47+
* @uses Graph::getVertices()
48+
* @uses self::isVertexLeaf()
49+
*/
50+
public function getVerticesLeaf()
51+
{
52+
return array_filter($this->graph->getVertices(), array($this, 'isVertexLeaf'));
53+
}
54+
55+
/**
56+
* get array of internal vertices
57+
*
58+
* @return Vertex[]
59+
* @uses Graph::getVertices()
60+
* @uses self::isVertexInternal()
61+
*/
62+
public function getVerticesInternal()
63+
{
64+
return array_filter($this->graph->getVertices(), array($this, 'isVertexInternal'));
65+
}
66+
}

src/Tree/BaseDirected.php

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
<?php
2+
3+
namespace Fhaculty\Graph\Algorithm\Tree;
4+
5+
use Fhaculty\Graph\Algorithm\Tree\Base as Tree;
6+
use Fhaculty\Graph\Exception\UnderflowException;
7+
use Fhaculty\Graph\Exception\UnexpectedValueException;
8+
use Fhaculty\Graph\Algorithm\Search\Base as Search;
9+
use Fhaculty\Graph\Algorithm\Search\StrictDepthFirst;
10+
use Fhaculty\Graph\Vertex;
11+
12+
/**
13+
*
14+
* @link http://en.wikipedia.org/wiki/Spaghetti_stack
15+
* @see OutTree
16+
*/
17+
abstract class BaseDirected extends Tree
18+
{
19+
const DIRECTION_CHILDREN = -1;
20+
21+
/**
22+
* get root vertex for this in-tree
23+
*
24+
* @return Vertex
25+
* @throws UnderflowException if given graph is empty
26+
* @throws UnexpectedValueException if given graph is not a tree
27+
*/
28+
public function getVertexRoot()
29+
{
30+
$root = $this->getVertexPossibleRoot();
31+
32+
$search = new StrictDepthFirst($root);
33+
$search->setDirection(static::DIRECTION_CHILDREN);
34+
35+
$num = $search->getNumberOfVertices();
36+
37+
if ($num !== $this->graph->getNumberOfVertices()) {
38+
throw new UnexpectedValueException();
39+
}
40+
41+
return $root;
42+
}
43+
44+
/**
45+
* checks if this is a tree
46+
*
47+
* @return boolean
48+
* @uses Graph::isEmpty()
49+
* @uses self::getVertexRoot() to actually check tree
50+
*/
51+
public function isTree()
52+
{
53+
if (!$this->graph->isEmpty()) {
54+
try {
55+
$this->getVertexRoot();
56+
}
57+
catch (UnexpectedValueException $e) {
58+
return false;
59+
}
60+
}
61+
return true;
62+
}
63+
64+
/**
65+
* get parent vertex for given $vertex
66+
*
67+
* @param Vertex $vertex
68+
* @throws UnderflowException if vertex has no parent (is a root vertex)
69+
* @throws UnexpectedValueException if vertex has more than one possible parent (check isTree()!)
70+
* @return Vertex
71+
*/
72+
public function getVertexParent(Vertex $vertex)
73+
{
74+
$parents = $this->getVerticesParent($vertex);
75+
if (count($parents) !== 1) {
76+
if (!$parents) {
77+
throw new UnderflowException('No parents for given vertex found');
78+
} else {
79+
throw new UnexpectedValueException('More than one parent');
80+
}
81+
}
82+
return current($parents);
83+
}
84+
85+
/**
86+
* get array of child vertices for given $vertex
87+
*
88+
* @param Vertex $vertex
89+
* @return Vertex[]
90+
*/
91+
abstract public function getVerticesChildren(Vertex $vertex);
92+
93+
abstract protected function getVerticesParent(Vertex $vertex);
94+
95+
protected function isVertexPossibleRoot(Vertex $vertex)
96+
{
97+
return (count($this->getVerticesParent($vertex)) === 0);
98+
}
99+
100+
protected function getVertexPossibleRoot()
101+
{
102+
foreach ($this->graph->getVertices() as $vertex) {
103+
if ($this->isVertexPossibleRoot($vertex)) {
104+
return $vertex;
105+
}
106+
}
107+
throw new UnderflowException('No possible root found. Either empty graph or no Vertex with proper degree found.');
108+
}
109+
110+
/**
111+
* checks if the given $vertex is a leaf (outermost vertex with no children)
112+
*
113+
* @param Vertex $vertex
114+
* @return boolean
115+
* @uses self::getVerticesChildren()
116+
*/
117+
public function isVertexLeaf(Vertex $vertex)
118+
{
119+
return (count($this->getVerticesChildren($vertex)) === 0);
120+
}
121+
122+
public function isVertexInternal(Vertex $vertex)
123+
{
124+
return ($this->getVerticesParent($vertex) && $this->getVerticesChildren($vertex));
125+
}
126+
127+
/**
128+
* get degree of tree (maximum number of children)
129+
*
130+
* @return int
131+
* @throws UnderflowException for empty graphs
132+
* @uses Graph::getVertices()
133+
* @uses self::getVerticesChildren()
134+
*/
135+
public function getDegree()
136+
{
137+
$max = null;
138+
foreach ($this->graph->getVertices() as $vertex) {
139+
$num = count($this->getVerticesChildren($vertex));
140+
if ($max === null || $num > $max) {
141+
$max = $num;
142+
}
143+
}
144+
if ($max === null) {
145+
throw new UnderflowException('No vertices found');
146+
}
147+
return $max;
148+
}
149+
150+
/**
151+
* get depth of given $vertex (number of edges between root vertex)
152+
*
153+
* root has depth zero
154+
*
155+
* @param Vertex $vertex
156+
* @return int
157+
* @throws UnderflowException for empty graphs
158+
* @throws UnexpectedValueException if there's no path to root node (check isTree()!)
159+
* @uses self::getVertexRoot()
160+
* @uses self::getVertexParent() for each step
161+
*/
162+
public function getDepthVertex(Vertex $vertex)
163+
{
164+
$root = $this->getVertexRoot();
165+
166+
$depth = 0;
167+
while ($vertex !== $root) {
168+
$vertex = $this->getVertexParent($vertex);
169+
++$depth;
170+
}
171+
return $depth;
172+
}
173+
174+
/**
175+
* get height of this tree (longest downward path to a leaf)
176+
*
177+
* a single vertex graph has height zero
178+
*
179+
* @return int
180+
* @throws UnderflowException for empty graph
181+
* @uses self::getVertexRoot()
182+
* @uses self::getHeightVertex()
183+
*/
184+
public function getHeight()
185+
{
186+
return $this->getHeightVertex($this->getVertexRoot());
187+
}
188+
189+
/**
190+
* get height of given vertex (longest downward path to a leaf)
191+
*
192+
* leafs has height zero
193+
*
194+
* @param Vertex $vertex
195+
* @return int
196+
* @uses self::getVerticesChildren() to get children of given vertex
197+
* @uses self::getHeightVertex() to recurse into sub-children
198+
*/
199+
public function getHeightVertex(Vertex $vertex)
200+
{
201+
$max = 0;
202+
foreach ($this->getVerticesChildren($vertex) as $vertex) {
203+
$height = $this->getHeightVertex($vertex) + 1;
204+
if ($height > $max) {
205+
$max = $height;
206+
}
207+
}
208+
return $max;
209+
}
210+
}

src/Tree/InTree.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Fhaculty\Graph\Algorithm\Tree;
4+
5+
use Fhaculty\Graph\Algorithm\Tree\BaseDirected as DirectedTree;
6+
use Fhaculty\Graph\Exception\UnexpectedValueException;
7+
use Fhaculty\Graph\Algorithm\Search\Base as Search;
8+
use Fhaculty\Graph\Vertex;
9+
10+
/**
11+
*
12+
* @link http://en.wikipedia.org/wiki/Spaghetti_stack
13+
* @see OutTree
14+
*/
15+
class InTree extends DirectedTree
16+
{
17+
const DIRECTION_CHILDREN = Search::DIRECTION_REVERSE;
18+
19+
public function getVerticesChildren(Vertex $vertex)
20+
{
21+
return $vertex->getVerticesEdgeFrom();
22+
}
23+
24+
protected function getVerticesParent(Vertex $vertex)
25+
{
26+
return $vertex->getVerticesEdgeTo();
27+
}
28+
}

src/Tree/OutTree.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Fhaculty\Graph\Algorithm\Tree;
4+
5+
use Fhaculty\Graph\Algorithm\Tree\BaseDirected as DirectedTree;
6+
use Fhaculty\Graph\Exception\UnexpectedValueException;
7+
use Fhaculty\Graph\Algorithm\Search\StrictDepthFirst;
8+
use Fhaculty\Graph\Vertex;
9+
10+
/**
11+
* A rooted tree with the "away from root" direction (a more narrow term is an "arborescence"), meaning:
12+
*
13+
* @link http://en.wikipedia.org/wiki/Arborescence_%28graph_theory%29
14+
* @see InTree
15+
*/
16+
class OutTree extends DirectedTree
17+
{
18+
const DIRECTION_CHILDREN = StrictDepthFirst::DIRECTION_FORWARD;
19+
20+
public function getVerticesChildren(Vertex $vertex)
21+
{
22+
return $vertex->getVerticesEdgeTo();
23+
}
24+
25+
protected function getVerticesParent(Vertex $vertex)
26+
{
27+
return $vertex->getVerticesEdgeFrom();
28+
}
29+
}

src/Tree/Undirected.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Fhaculty\Graph\Algorithm\Tree;
4+
5+
use Fhaculty\Graph\Algorithm\Tree\Base as Tree;
6+
use Fhaculty\Graph\Exception\UnderflowException;
7+
use Fhaculty\Graph\Exception\UnexpectedValueException;
8+
use Fhaculty\Graph\Algorithm\Search\Base as Search;
9+
use Fhaculty\Graph\Algorithm\Search\StrictDepthFirst;
10+
use Fhaculty\Graph\Vertex;
11+
12+
/**
13+
*
14+
*/
15+
class Undirected extends Tree
16+
{
17+
public function isTree()
18+
{
19+
if ($this->graph->isEmpty()) {
20+
return true;
21+
}
22+
23+
// every vertex can represent a root vertex, so just pick one
24+
$root = $this->graph->getVertexFirst();
25+
26+
// TODO: recurse $root to get sub-vertices
27+
$vertices = array();
28+
29+
return (count($vertices) === $this->graph->getNumberOfVertices());
30+
}
31+
32+
/**
33+
* checks if the given $vertex is a leaf (outermost vertex with exactly one edge)
34+
*
35+
* @param Vertex $vertex
36+
* @return boolean
37+
* @uses Vertex::getDegree()
38+
*/
39+
public function isVertexLeaf(Vertex $vertex)
40+
{
41+
return ($vertex->getDegree() === 1);
42+
}
43+
44+
public function isVertexInternal(Vertex $vertex)
45+
{
46+
return ($vertex->getDegree() >= 2);
47+
}
48+
}

0 commit comments

Comments
 (0)