This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
<?php
namespace Graphp\Algorithms\MinimumSpanningTree;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use Graphp\Algorithms\Base as AlgorithmBase;
use SplPriorityQueue;
/**
* Abstract base class for minimum spanning tree (MST) algorithms
*
* A minimum spanning tree of a graph is a subgraph that is a tree and connects
* all the vertices together while minimizing the total sum of all edges'
* weights.
*
* A spanning tree thus requires a connected graph (single connected component),
* otherwise we can span multiple trees (spanning forest) within each component.
* Because a null graph (a Graph with no vertices) is not considered connected,
* it also can not contain a spanning tree.
*
* Most authors demand that the input graph has to be undirected, whereas this
* library supports also directed and mixed graphs. The actual direction of the
* edge will be ignored, only its incident vertices will be checked. This is
* done in order to be consistent to how ConnectedComponents are checked.
*
* @link http://en.wikipedia.org/wiki/Minimum_Spanning_Tree
* @link http://en.wikipedia.org/wiki/Spanning_Tree
* @link http://mathoverflow.net/questions/120536/is-the-empty-graph-a-tree
*/
abstract class Base extends AlgorithmBase
{
/**
* create new resulting graph with only edges on minimum spanning tree
*
* @return Graph
* @uses self::getGraph()
* @uses self::getEdges()
* @uses Graph::createGraphCloneEdges()
*/
public function createGraph()
{
return $this->getGraph()->createGraphCloneEdges($this->getEdges());
}
/**
* get all edges on minimum spanning tree
*
* @return Edges
*/
abstract public function getEdges();
/**
* return reference to current Graph
*
* @return Graph
*/
abstract protected function getGraph();
/**
* get total weight of minimum spanning tree
*
* @return float
*/
public function getWeight()
{
return $this->getEdges()->getSumCallback(function (Edge $edge) {
return $edge->getWeight();
});
}
/**
* helper method to add a set of Edges to the given set of sorted edges
*
* @param Edges $edges
* @param SplPriorityQueue $sortedEdges
*/
protected function addEdgesSorted(Edges $edges, SplPriorityQueue $sortedEdges)
{
// For all edges
foreach ($edges as $edge) {
assert($edge instanceof Edge);
// ignore loops (a->a)
if (!$edge->isLoop()) {
// Add edges with negative weight because of order in stl
$sortedEdges->insert($edge, -$edge->getWeight());
}
}
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace Graphp\Algorithms\MinimumSpanningTree;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Edges;
use SplPriorityQueue;
class Kruskal extends Base
{
/**
* @var Graph
*/
private $graph;
public function __construct(Graph $inputGraph)
{
$this->graph = $inputGraph;
}
protected function getGraph()
{
return $this->graph;
}
/**
* @return Edges
*/
public function getEdges()
{
// Sortiere Kanten im Graphen
$sortedEdges = new SplPriorityQueue();
// For all edges
$this->addEdgesSorted($this->graph->getEdges(), $sortedEdges);
$returnEdges = array();
// next color to assign
$colorNext = 0;
// array(color1 => array(vid1, vid2, ...), color2=>...)
$colorVertices = array();
// array(vid1 => color1, vid2 => color1, ...)
$colorOfVertices = array();
// Füge billigste Kanten zu neuen Graphen hinzu und verschmelze teilgragen wenn es nötig ist (keine Kreise)
// solange ich mehr als einen Graphen habe mit weniger als n-1 kanten (bei n knoten im original)
foreach ($sortedEdges as $edge) {
assert($edge instanceof Edge);
// Gucke Kante an:
$vertices = $edge->getVertices()->getIds();
$aId = $vertices[0];
$bId = $vertices[1];
$aColor = isset($colorOfVertices[$aId]) ? $colorOfVertices[$aId] : NULL;
$bColor = isset($colorOfVertices[$bId]) ? $colorOfVertices[$bId] : NULL;
// 1. weder start noch end gehört zu einem graphen
// => neuer Graph mit kanten
if ($aColor === NULL && $bColor === NULL) {
$colorOfVertices[$aId] = $colorNext;
$colorOfVertices[$bId] = $colorNext;
$colorVertices[$colorNext] = array($aId, $bId);
++$colorNext;
// connect both vertices
$returnEdges[] = $edge;
}
// 4. start xor end gehören zu einem graphen
// => erweitere diesesn Graphen
// Only b has color
else if ($aColor === NULL && $bColor !== NULL) {
// paint a in b's color
$colorOfVertices[$aId] = $bColor;
$colorVertices[$bColor][]=$aId;
$returnEdges[] = $edge;
// Only a has color
} elseif ($aColor !== NULL && $bColor === NULL) {
// paint b in a's color
$colorOfVertices[$bId] = $aColor;
$colorVertices[$aColor][]=$bId;
$returnEdges[] = $edge;
}
// 3. start und end gehören zu unterschiedlichen graphen
// => vereinigung
// Different color
else if ($aColor !== $bColor) {
$betterColor = $aColor;
$worseColor = $bColor;
// more vertices with color a => paint all in b in a's color
if (\count($colorVertices[$bColor]) > \count($colorVertices[$aColor])) {
$betterColor = $bColor;
$worseColor = $aColor;
}
// search all vertices with color b
foreach ($colorVertices[$worseColor] as $vid) {
$colorOfVertices[$vid] = $betterColor;
// repaint in a's color
$colorVertices[$betterColor][]=$vid;
}
// delete old color
unset($colorVertices[$worseColor]);
$returnEdges[] = $edge;
}
// 2. start und end gehören zum gleichen graphen => zirkel
// => nichts machen
}
// definition of spanning tree: number of edges = number of vertices - 1
// above algorithm does not check isolated edges or may otherwise return multiple connected components => force check
if (\count($returnEdges) !== (\count($this->graph->getVertices()) - 1)) {
throw new UnexpectedValueException('Graph is not connected');
}
return new Edges($returnEdges);
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Graphp\Algorithms\MinimumSpanningTree;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Exception\UnexpectedValueException;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Vertex;
use SplPriorityQueue;
class Prim extends Base
{
/**
* @var Vertex
*/
private $startVertex;
public function __construct(Vertex $startVertex)
{
$this->startVertex = $startVertex;
}
/**
* @return Edges
*/
public function getEdges()
{
// Initialize algorithm
$edgeQueue = new SplPriorityQueue();
$vertexCurrent = $this->startVertex;
$markInserted = array();
$returnEdges = array();
// iterate n-1 times (per definition, resulting MST MUST have n-1 edges)
for ($i = 0, $n = \count($this->startVertex->getGraph()->getVertices()) - 1; $i < $n; ++$i) {
$markInserted[$vertexCurrent->getId()] = true;
// get unvisited vertex of the edge and add edges from new vertex
// Add all edges from $currentVertex to priority queue
$this->addEdgesSorted($vertexCurrent->getEdges(), $edgeQueue);
do {
if ($edgeQueue->isEmpty()) {
throw new UnexpectedValueException('Graph has more than one component');
}
// Get next cheapest edge
$cheapestEdge = $edgeQueue->extract();
assert($cheapestEdge instanceof Edge);
// Check if edge is between unmarked and marked edge
$vertices = $cheapestEdge->getVertices();
$vertexA = $vertices->getVertexFirst();
$vertexB = $vertices->getVertexLast();
} while (!(isset($markInserted[$vertexA->getId()]) XOR isset($markInserted[$vertexB->getId()])));
// Cheapest Edge found, add edge to returnGraph
$returnEdges[] = $cheapestEdge;
// set current vertex for next iteration in order to add its edges to queue
if (isset($markInserted[$vertexA->getId()])) {
$vertexCurrent = $vertexB;
} else {
$vertexCurrent = $vertexA;
}
}
return new Edges($returnEdges);
}
protected function getGraph()
{
return $this->startVertex->getGraph();
}
}