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.

382 lines
11 KiB
PHP

<?php
namespace Graphp\Algorithms\Property;
use Fhaculty\Graph\Walk;
use Fhaculty\Graph\Set\Edges;
use Graphp\Algorithms\Base as BaseAlgorithm;
use Graphp\Algorithms\Loop as AlgorithmLoop;
/**
* Simple algorithms for working with Walk properties
*
* @see GraphProperty
*/
class WalkProperty extends BaseAlgorithm
{
/**
* the Walk to operate on
*
* @var Walk
*/
protected $walk;
/**
* instantiate new WalkProperty algorithm
*
* @param Walk $walk
*/
public function __construct(Walk $walk)
{
$this->walk = $walk;
}
/**
* checks whether walk is a cycle (i.e. source vertex = target vertex)
*
* A cycle is also known as a closed path, a walk that is NOT a cycle is
* also known as an open path.
*
* A walk with no edges is not considered a cycle. The shortest possible
* cycle is a single loop edge:
*
* 1--\
* ^ |
* \--/
*
* The following Walk is also considered a valid cycle:
*
* /->3--\
* | |
* 1 -> 2 -\ |
* ^ ^ | |
* | \--/ |
* | |
* \----------/
*
* @return bool
* @link http://en.wikipedia.org/wiki/Cycle_%28graph_theory%29
* @see self::isCircuit()
* @see self::isLoop()
*/
public function isCycle()
{
$vertices = $this->walk->getVertices();
return ($vertices->getVertexFirst() === $vertices->getVertexLast() && !$this->walk->getEdges()->isEmpty());
}
/**
* checks whether this walk is a circuit (i.e. a cycle with no duplicate edges)
*
* A circuit is also known as a closed (=cycle) trail (=path), that has at
* least one edge.
*
* The following Walk is considered both a valid cycle and a valid circuit:
*
* 1 -> 2 -> 3 -\
* ^ |
* | |
* \------------/
*
* The following Walk is also considered both a valid cycle and a valid circuit:
*
* /->3--\
* | |
* 1 -> 2 -\ |
* ^ ^ | |
* | \--/ |
* | |
* \----------/
*
* The later circuit walk can be expressed by its Vertex IDs as
* "1, 2, 2, 3, 1". If however, the inner loop would be "walked along"
* several times, the resulting walk would be expressed as
* "1, 2, 2, 2, 3, 1", which would still be a valid cycle, but NOT a valid
* circuit anymore.
*
* @return boolean
* @link http://www.proofwiki.org/wiki/Definition:Circuit
* @uses self::isCycle()
* @uses self::isPath()
*/
public function isCircuit()
{
return ($this->isCycle() && $this->isPath());
}
/**
* checks whether walk is a path (i.e. does not contain any duplicate edges)
*
* A path Walk is also known as a trail.
*
* @return bool
* @uses self::hasArrayDuplicates()
* @link http://www.proofwiki.org/wiki/Definition:Trail
*/
public function isPath()
{
return !$this->hasArrayDuplicates($this->walk->getEdges()->getVector());
}
/**
* checks whether walk contains a cycle (i.e. contains a duplicate vertex)
*
* A walk that CONTAINS a cycle does not neccessarily have to BE a cycle.
* Conversely, a Walk that *is* a cycle, automatically always *contains* a
* cycle.
*
* The following Walk is NOT a cycle, but it *contains* a valid cycle:
*
* /->4
* |
* 1 -> 2 -> 3 -\
* ^ |
* \-------/
*
* @return bool
* @uses self::hasArrayDuplicates()
* @see self::isCycle()
*/
public function hasCycle()
{
return $this->hasArrayDuplicates($this->walk->getVertices()->getVector());
}
/**
* checks whether this walk IS a loop (single edge connecting vertex A with vertex A again)
*
* A loop is the simplest possible cycle. As such, each loop is also a
* cycle. Accordingly, every Walk that *is* a loop, automatically also *is*
* a cycle and automatically *contains* a loop and automatically *contains*
* a cycle.
*
* The following Walk represents a simple (directed) loop:
*
* 1--\
* ^ |
* \--/
*
* @return boolean
* @uses self::isCycle()
* @see self::hasLoop()
*/
public function isLoop()
{
return (count($this->walk->getEdges()) === 1 && $this->isCycle());
}
/**
* checks whether this walk HAS a loop (single edge connecting vertex A with vertex A again)
*
* The following Walk is NOT a valid loop, but it contains a valid loop:
*
* /->3
* |
* 1 -> 2 -\
* ^ |
* \--/
*
* @return boolean
* @uses AlgorithmLoop::hasLoop()
* @see self::isLoop()
*/
public function hasLoop()
{
$alg = new AlgorithmLoop($this->walk);
return $alg->hasLoop();
}
/**
* checks whether this walk is a digon (a pair of parallel edges in a multigraph or a pair of antiparallel edges in a digraph)
*
* A digon is a cycle connecting exactly two distinct vertices with exactly
* two distinct edges.
*
* The following Graph represents a digon in an undirected Graph:
*
* /--\
* 1 2
* \--/
*
* The following Graph represents a digon as a set of antiparallel directed
* Edges in a directed Graph:
*
* 1 -> 2
* ^ |
* | |
* \----/
*
* @return boolean
* @uses self::hasArrayDuplicates()
* @uses self::isCycle()
*/
public function isDigon()
{
// exactly 2 edges
return (count($this->walk->getEdges()) === 2 &&
// no duplicate edges
!$this->hasArrayDuplicates($this->walk->getEdges()->getVector()) &&
// exactly two distinct vertices
count($this->walk->getVertices()->getVerticesDistinct()) === 2 &&
// this is actually a cycle
$this->isCycle());
}
/**
* checks whether this walk is a triangle (a simple cycle with exactly three distinct vertices)
*
* The following Graph is a valid directed triangle:
*
* 1->2->3
* ^ |
* \-----/
*
* @return boolean
* @uses self::isCycle()
*/
public function isTriangle()
{
// exactly 3 (implicitly distinct) edges
return (count($this->walk->getEdges()) === 3 &&
// exactly three distinct vertices
count($this->walk->getVertices()->getVerticesDistinct()) === 3 &&
// this is actually a cycle
$this->isCycle());
}
/**
* check whether this walk is simple
*
* contains no duplicate/repeated vertices (and thus no duplicate edges either)
* other than the starting and ending vertices of cycles.
*
* A simple Walk is also known as a chain.
*
* The term "simple walk" is somewhat related to a walk with no cycles. If
* a Walk has a cycle, it is not simple - with one single exception: a Walk
* that IS a cycle automatically also contains a cycle, but if it contains
* no "further" additional cycles, it is considered a simple cycle.
*
* The following Graph represents a (very) simple Walk:
*
* 1 -- 2
*
* The following Graph IS a cycle and is simple:
*
* 1 -> 2
* ^ |
* \----/
*
* The following Graph contains a cycle and is NOT simple:
*
* /->4
* |
* 1 -> 2 -> 3 -\
* ^ |
* \-------/
*
* The following Graph IS a cycle and thus automatically contains a cycle.
* Due to the additional "inner" cycle (loop at vertex 2), it is NOT simple:
*
* /->3--\
* | |
* 1 -> 2 -\ |
* ^ ^ | |
* | \--/ |
* | |
* \----------/
*
* @return boolean
* @uses self::isCycle()
* @uses self::hasArrayDuplicates()
* @see self::hasCycle()
*/
public function isSimple()
{
$vertices = $this->walk->getVertices()->getVector();
// ignore starting vertex for cycles as it's always the same as ending vertex
if ($this->isCycle()) {
unset($vertices[0]);
}
return !$this->hasArrayDuplicates($vertices);
}
/**
* checks whether walk is hamiltonian (i.e. walk over ALL VERTICES of the graph)
*
* A hamiltonian Walk is also known as a spanning walk.
*
* @return boolean
* @see self::isEulerian() if you want to check for all EDGES instead of VERTICES
* @uses self::isArrayContentsEqual()
* @link http://en.wikipedia.org/wiki/Hamiltonian_path
*/
public function isHamiltonian()
{
$vertices = $this->walk->getVertices()->getVector();
// ignore starting vertex for cycles as it's always the same as ending vertex
if ($this->isCycle()) {
unset($vertices[0]);
}
return $this->isArrayContentsEqual($vertices, $this->walk->getGraph()->getVertices()->getVector());
}
/**
* checks whether walk is eulerian (i.e. a walk over ALL EDGES of the graph)
*
* @return boolean
* @see self::isHamiltonian() if you want to check for all VERTICES instead of EDGES
* @uses self::isArrayContentsEqual()
* @link http://en.wikipedia.org/wiki/Eulerian_path
*/
public function isEulerian()
{
return $this->isArrayContentsEqual($this->walk->getEdges()->getVector(), $this->walk->getGraph()->getEdges()->getVector());
}
/**
* checks whether ths given array contains duplicate identical entries
*
* @param array $array
* @return bool
*/
private function hasArrayDuplicates($array)
{
$compare = array();
foreach ($array as $element) {
// duplicate element found
if (in_array($element, $compare, true)) {
return true;
} else {
// add element to temporary array to check for duplicates
$compare [] = $element;
}
}
return false;
}
/**
* checks whether the contents of array a equals those of array b (ignore keys and order but otherwise strict check)
*
* @param array $a
* @param array $b
* @return boolean
*/
private function isArrayContentsEqual($a, $b)
{
foreach ($b as $one) {
$pos = array_search($one, $a, true);
if ($pos === false) {
return false;
} else {
unset($a[$pos]);
}
}
return $a ? false : true;
}
}