Open
Graph Drawing
Framework

 v. 2023.09 (Elderberry)
 

Loading...
Searching...
No Matches
MinSteinerTreeShore.h
Go to the documentation of this file.
1
33#pragma once
34
35// enable this to print messages
36// for all branches (edge inclusion or exclusion)
37// additionally a SVG image is generated for each
38// recursion depth
39//#define OGDF_MINSTEINERTREE_SHORE_LOGGING
40
42#include <ogdf/basic/List.h>
44#include <ogdf/basic/NodeSet.h>
47
48#include <memory>
49
50namespace ogdf {
51
62template<typename T>
64public:
67
68protected:
84 virtual T computeSteinerTree(const EdgeWeightedGraph<T>& G, const List<node>& terminals,
86
87 const T MAX_WEIGHT;
88
89private:
92
94 std::unique_ptr<NodeSet<>> m_terminals;
100
109 bool validateMapping() const;
110
119 T weightOf(edge e) const;
120
138
147
158 edge newEdge(node source, node target, const edge originalEdge);
159
168 void moveSource(edge e, node v);
169
178 void moveTarget(edge e, node v);
179
191 void setTerminal(const node v, bool makeTerminal);
192
205 bool isTerminal(const node v) const;
206
219 void setEdgeLookup(const node u, const node v, const edge e);
220
233 edge lookupEdge(const node u, const node v) const;
234
249
258
263 void printSVG();
264};
265
266template<typename T>
268 const List<node>& terminals, const NodeArray<bool>& isTerminal,
270 m_originalGraph = &G;
271 m_originalTerminals = &terminals;
272
273 m_upperBound = MAX_WEIGHT;
274 m_graph = Graph();
275 m_mapping.init(m_graph);
276 m_terminals.reset(new NodeSet<>(m_graph));
277 int nodeCount = m_originalGraph->numberOfNodes();
278 m_edges = Array2D<edge>(0, nodeCount, 0, nodeCount, nullptr);
279
280 NodeArray<node> copiedNodes(*m_originalGraph);
281
282 for (node v : m_originalGraph->nodes) {
283 node copiedNode = m_graph.newNode();
285 }
286
287 for (edge e : m_originalGraph->edges) {
288 node source = copiedNodes[e->source()], target = copiedNodes[e->target()];
289
290 newEdge(source, target, e);
291 }
292
293 for (node v : *m_originalTerminals) {
294 setTerminal(copiedNodes[v], true);
295 }
296
298 T result = solve(chosenEdges);
299
301 finalSteinerTree->createEmpty(*m_originalGraph);
302
303 for (edge e : chosenEdges) {
304 node v = e->source();
305 node w = e->target();
306
307 OGDF_ASSERT(v != nullptr);
308 OGDF_ASSERT(w != nullptr);
309 OGDF_ASSERT(e->graphOf() == m_originalGraph);
310 OGDF_ASSERT(v->graphOf() == m_originalGraph);
311 OGDF_ASSERT(w->graphOf() == m_originalGraph);
312
313 if (finalSteinerTree->copy(v) == nullptr) {
314 finalSteinerTree->newNode(v);
315 }
316 if (finalSteinerTree->copy(w) == nullptr) {
317 finalSteinerTree->newNode(w);
318 }
319 finalSteinerTree->newEdge(e, m_originalGraph->weight(e));
320 }
321
322 return result;
323}
324
325template<typename T>
327 T result = MAX_WEIGHT;
328
329 if (e != nullptr) {
330 OGDF_ASSERT(e->graphOf() == &m_graph);
331 OGDF_ASSERT(m_mapping[e] != nullptr);
332 OGDF_ASSERT(m_mapping[e]->graphOf() == m_originalGraph);
333 result = m_originalGraph->weight(m_mapping[e]);
334 }
335
336 return result;
337}
338
339template<typename T>
341 for (edge e : m_graph.edges) {
342 OGDF_ASSERT(m_mapping[e] != nullptr);
343 OGDF_ASSERT(m_mapping[e]->graphOf() == m_originalGraph);
344 }
345 return true;
346}
347
348template<typename T>
350 return m_edges(u->index(), v->index());
351}
352
353template<typename T>
354void MinSteinerTreeShore<T>::setEdgeLookup(const node u, const node v, const edge e) {
355 m_edges(u->index(), v->index()) = m_edges(v->index(), u->index()) = e;
356}
357
358template<typename T>
360 edge result = m_mapping[e];
361 setEdgeLookup(e->source(), e->target(), nullptr);
362 m_graph.delEdge(e);
363 e->~EdgeElement();
364
365 return result;
366}
367
368template<typename T>
370 edge result = m_graph.newEdge(source, target);
371 m_mapping[result] = e;
372 setEdgeLookup(source, target, result);
373
374 return result;
375}
376
377template<typename T>
379 OGDF_ASSERT(e != nullptr);
380 setEdgeLookup(e->source(), e->target(), nullptr);
381 setEdgeLookup(v, e->target(), e);
382 m_graph.moveSource(e, v);
383}
384
385template<typename T>
387 OGDF_ASSERT(e != nullptr);
388 setEdgeLookup(e->source(), e->target(), nullptr);
389 setEdgeLookup(e->source(), v, e);
390 m_graph.moveTarget(e, v);
391}
392
393template<typename T>
395 return m_terminals->isMember(v);
396}
397
398template<typename T>
400 if (makeTerminal) {
401 m_terminals->insert(v);
402 } else {
403 m_terminals->remove(v);
404 }
405}
406
407template<typename T>
409 m_chosenEdges.clear();
410
411 m_recursionDepth = 0;
413 T result = bnbInternal(0, tmp);
414
415 for (edge e : m_chosenEdges) {
416 chosenEdges.pushFront(e);
417 }
418
419 return result;
420}
421
422template<typename T>
424 edge result = nullptr;
425 T maxPenalty = -1;
426
427 // calculate penalties for nodes
428 T sumOfMinWeights = 0; // b
429 T sumOfMinTermWeights = 0; // c
430 T absoluteMinTermWeight = MAX_WEIGHT;
431 for (ListConstIterator<node> it = m_terminals->nodes().begin();
432 sumOfMinWeights < MAX_WEIGHT && it.valid(); ++it) {
433 const node t = *it;
434 T minTermWeight = MAX_WEIGHT, minWeight = MAX_WEIGHT, secondMinWeight = MAX_WEIGHT;
435 edge minEdge = nullptr;
436
437 // investigate all edges of each terminal
438 // calculate lower boundary and find branching edge
439 for (adjEntry adj = t->firstAdj(); adj; adj = adj->succ()) {
440 edge e = adj->theEdge();
441 if (weightOf(e) < minWeight) {
443 minWeight = weightOf(e);
444 minEdge = e;
445 } else {
446 if (weightOf(e) < secondMinWeight) {
447 secondMinWeight = weightOf(e);
448 }
449 }
450
451 if (isTerminal(adj->twinNode()) && weightOf(e) < minTermWeight) {
452 minTermWeight = weightOf(e);
455 }
456 }
457 }
458
459 if (sumOfMinTermWeights < MAX_WEIGHT && minTermWeight < MAX_WEIGHT) {
461 } else {
462 sumOfMinTermWeights = MAX_WEIGHT;
463 }
465
466 // is terminal isolated or has only one edge?
467 // if so we can break here
468 if (minWeight == MAX_WEIGHT || secondMinWeight == MAX_WEIGHT) {
469 result = minEdge;
470 if (minWeight == MAX_WEIGHT) {
471 sumOfMinWeights = MAX_WEIGHT;
472 }
473 } else {
475 // update branching edge if need be
477 if (penalty > maxPenalty) {
479 result = minEdge;
480 }
481 }
482 }
483
484 // compare bounds for this graph
485 if (result != nullptr) {
486 T maxCost = m_upperBound - prevCost;
487 if (sumOfMinTermWeights < MAX_WEIGHT) {
489 }
491 result = nullptr;
492 }
493 }
494
495 return result;
496}
497
498template<typename T>
500 T result = MAX_WEIGHT;
501 m_recursionDepth++;
502
503#ifdef OGDF_MINSTEINERTREE_SHORE_LOGGING
504 printSVG();
505#endif
506
507 if (prevCost <= m_upperBound) {
508 if (m_terminals->size() < 2) {
509 // update currently chosen edges
510 if (prevCost != m_upperBound || m_chosenEdges.empty()) {
511 m_chosenEdges = List<edge>(currentEdges);
512 }
513 // all terminals are connected
514 m_upperBound = prevCost;
515 result = prevCost;
516 } else {
517 edge branchingEdge = determineBranchingEdge(prevCost);
519
520#ifdef OGDF_MINSTEINERTREE_SHORE_LOGGING
521 for (int i = 0; i < m_recursionDepth; i++) {
522 std::cout << " ";
523 }
524 std::cout << "branching on edge: " << branchingEdge << std::endl;
525#endif
526 // branching edge has been found or there is no feasible solution
527 if (branchingEdgeWeight < MAX_WEIGHT) {
528 // chose node to remove
529 node nodeToRemove = branchingEdge->source();
530 node targetNode = branchingEdge->target();
531
532 // This seems to speed up things.
533 if (nodeToRemove->degree() < targetNode->degree()) {
534 nodeToRemove = branchingEdge->target();
535 targetNode = branchingEdge->source();
536 }
537
538 // remove branching edge
540
541 List<node> delEdges, movedEdges;
543
544 // first branch: Inclusion of the edge
545 // remove source node of edge and calculate new edge weights
546 OGDF_ASSERT(targetNode != nullptr);
547 OGDF_ASSERT(nodeToRemove != nullptr);
548
549 // remove edges in case of multigraph
550 while (m_graph.searchEdge(targetNode, nodeToRemove) != nullptr) {
551 edge e = m_graph.searchEdge(targetNode, nodeToRemove);
552 delEdges.pushFront(e->target());
553 delEdges.pushFront(e->source());
554 origDelEdges.pushFront(m_mapping[e]);
555 deleteEdge(e);
556 }
557 while (m_graph.searchEdge(nodeToRemove, targetNode) != nullptr) {
558 edge e = m_graph.searchEdge(targetNode, nodeToRemove);
559 delEdges.pushFront(e->target());
560 delEdges.pushFront(e->source());
561 origDelEdges.pushFront(m_mapping[e]);
562 deleteEdge(e);
563 }
564
565 OGDF_ASSERT(m_graph.searchEdge(targetNode, nodeToRemove) == nullptr);
566 OGDF_ASSERT(m_graph.searchEdge(nodeToRemove, targetNode) == nullptr);
567
569 for (adjEntry adj = nodeToRemove->firstAdj(); adj; adj = adjNext) {
570 adjNext = adj->succ();
571 edge e = adj->theEdge();
572
575 OGDF_ASSERT(adj->twinNode() != targetNode);
576
577 edge f = lookupEdge(targetNode, adj->twinNode());
578 bool deletedEdgeE = false;
579 if (f != nullptr) {
580 if (weightOf(f) < weightOf(e)) {
581 delEdges.pushFront(e->target());
582 delEdges.pushFront(e->source());
583 origDelEdges.pushFront(m_mapping[e]);
584 deleteEdge(e);
585
586 deletedEdgeE = true;
587 } else {
588 delEdges.pushFront(f->target());
589 delEdges.pushFront(f->source());
590 origDelEdges.pushFront(m_mapping[f]);
591 deleteEdge(f);
592 }
593 }
594 if (!deletedEdgeE) {
595 if (e->target() == nodeToRemove) {
596 OGDF_ASSERT(e->source() != targetNode);
597 movedEdges.pushFront(e->source());
598 moveTarget(e, targetNode);
599 } else {
601 OGDF_ASSERT(e->target() != targetNode);
602 movedEdges.pushFront(e->target());
603 moveSource(e, targetNode);
604 }
605 }
606 }
607 // nodeToRemove is isolated at this point
608 // thus no need to actually remove it
609 // (easier to keep track of CopyGraph mapping)
610
611 // remove node from terminals too
612 bool targetNodeIsTerminal = isTerminal(targetNode),
614
616 setTerminal(nodeToRemove, false);
617 setTerminal(targetNode, true);
618
619#ifdef OGDF_MINSTEINERTREE_SHORE_LOGGING
620 for (int i = 0; i < m_recursionDepth; i++) {
621 std::cout << " ";
622 }
623 std::cout << "inclusion branch" << std::endl;
624#endif
625 // calculate result on modified graph
627 result = bnbInternal(branchingEdgeWeight + prevCost, currentEdges);
629 currentEdges.popFront();
630
631 // restore previous graph
632
633 // restore terminals
635 setTerminal(targetNode, targetNodeIsTerminal);
636
637 // restore moved edges
638 while (!movedEdges.empty()) {
639 node v = movedEdges.popFrontRet();
640
641 edge e = lookupEdge(v, targetNode);
642 OGDF_ASSERT(e != nullptr);
643 OGDF_ASSERT(e->opposite(targetNode) != nodeToRemove);
644
645 if (e->source() == v) {
646 moveTarget(e, nodeToRemove);
647 } else {
648 moveSource(e, nodeToRemove);
649 }
650 }
651
652 // restore deleted edges
653 while (!delEdges.empty()) {
654 OGDF_ASSERT(!origDelEdges.empty());
655
656 node source = delEdges.popFrontRet();
657 node target = delEdges.popFrontRet();
658
659 newEdge(source, target, origDelEdges.popFrontRet());
660 }
661 OGDF_ASSERT(origDelEdges.empty());
662
663#ifdef OGDF_MINSTEINERTREE_SHORE_LOGGING
664 for (int i = 0; i < m_recursionDepth; i++) {
665 std::cout << " ";
666 }
667 std::cout << "exclusion branch" << std::endl;
668#endif
669 // sencond branch: Exclusion of the edge
670 T exEdgeResult = bnbInternal(prevCost, currentEdges);
671
672 // decide which branch returned best result
673 if (exEdgeResult < result) {
674 result = exEdgeResult;
675 }
676
677 // finally: restore the branching edge
678 newEdge(nodeToRemove, targetNode, origBranchingEdge);
679 }
680 }
681 OGDF_ASSERT(validateMapping());
682 }
683 m_recursionDepth--;
684 return result;
685}
686
687template<typename T>
690 copiedGraph.createEmpty(m_graph);
691 List<node> nodes;
692 m_graph.allNodes(nodes);
694 for (node v : nodes) {
695 copiedGraph.newNode(v);
696 copiedIsTerminal[v] = isTerminal(v);
697 }
698 List<edge> edges;
699 m_graph.allEdges(edges);
700 for (edge e : edges) {
701 copiedGraph.newEdge(e, weightOf(e));
702 }
703 std::stringstream filename;
704 filename << "bnb_internal_" << m_recursionDepth << ".svg";
705 this->drawSteinerTreeSVG(copiedGraph, copiedIsTerminal, filename.str().c_str());
706}
707
708}
Declaration and implementation of EdgeArray class.
Extends the GraphCopy concept to weighted graphs.
Declaration of doubly linked lists and iterators.
Declaration of ogdf::MinSteinerTreeModule.
Declaration and implementation of NodeArray class.
Declaration and implementation of ogdf::NodeSet.
Class for adjacency list elements.
Definition Graph_d.h:79
The parameterized class Array2D implements dynamic two-dimensional arrays.
Definition Array2D.h:47
Dynamic arrays indexed with edges.
Definition EdgeArray.h:125
Class for the representation of edges.
Definition Graph_d.h:300
node opposite(node v) const
Returns the adjacent node different from v.
Definition Graph_d.h:350
node target() const
Returns the target node of the edge.
Definition Graph_d.h:338
node source() const
Returns the source node of the edge.
Definition Graph_d.h:335
Data type for general directed graphs (adjacency list representation).
Definition Graph_d.h:521
Doubly linked lists (maintaining the length of the list).
Definition List.h:1435
iterator pushFront(const E &x)
Adds element x at the beginning of the list.
Definition List.h:1518
E popFrontRet()
Removes the first element from the list and returns it.
Definition List.h:1575
Encapsulates a pointer to a list element.
Definition List.h:103
bool empty() const
Returns true iff the list is empty.
Definition List.h:270
Serves as an interface for various methods to compute or approximate minimum Steiner trees on undirec...
Implementation of Shore, Foulds and Gibbons exact branch and bound algorithm for solving Steiner tree...
void moveSource(edge e, node v)
Moves the source of the edge to the specified node.
bool validateMapping() const
Used to validate the current mapping of edges to orignal edges Used solely for debugging.
T solve(List< edge > &chosenEdges)
Solves the current STP instance.
const EdgeWeightedGraph< T > * m_originalGraph
bool isTerminal(const node v) const
Returns whether this node is a terminal or a Steiner node.
void setEdgeLookup(const node u, const node v, const edge e)
Sets the edge incident to both node u and v.
edge lookupEdge(const node u, const node v) const
Retrieves the edge incident to both node u and v.
void setTerminal(const node v, bool makeTerminal)
Updates the status of the given node to either terminal or Steiner node.
edge deleteEdge(edge e)
Removes the specified edge from the graph.
virtual T computeSteinerTree(const EdgeWeightedGraph< T > &G, const List< node > &terminals, const NodeArray< bool > &isTerminal, EdgeWeightedGraphCopy< T > *&finalSteinerTree) override
Builds a minimum Steiner tree given a weighted graph and a list of terminals.
T bnbInternal(T prevCost, List< edge > &currentEdges)
Calculates the optimal Steinter tree recursivly.
const List< node > * m_originalTerminals
void moveTarget(edge e, node v)
Moves the target of the edge to the specified node.
edge newEdge(node source, node target, const edge originalEdge)
Creates a new edge.
std::unique_ptr< NodeSet<> > m_terminals
edge determineBranchingEdge(T prevCost) const
Decides which edge to branch on.
void printSVG()
Prints the current recursion status as a SVG image of the current reduced STP.
T weightOf(edge e) const
Returns the cost of the specified edge.
Dynamic arrays indexed with nodes.
Definition NodeArray.h:125
Class for the representation of nodes.
Definition Graph_d.h:177
int degree() const
Returns the degree of the node (indegree + outdegree).
Definition Graph_d.h:220
int index() const
Returns the (unique) node index.
Definition Graph_d.h:211
Node sets.
Definition NodeSet.h:54
#define OGDF_ASSERT(expr)
Assert condition expr. See doc/build.md for more information.
Definition basic.h:41
static MultilevelBuilder * getDoubleFactoredZeroAdjustedMerger()
The namespace for all OGDF objects.
Definition GML.h:110