1 /** 2 This module contains graph algorithms. 3 4 Copyright: © 2021 Arne Ludwig <arne.ludwig@posteo.de> 5 License: Subject to the terms of the MIT license, as written in the 6 included LICENSE file. 7 Authors: Arne Ludwig <arne.ludwig@posteo.de> 8 */ 9 module dalicious.algorithm.graph; 10 11 import dalicious.math : NaturalNumberSet; 12 import dalicious.container : RingBuffer; 13 import std.algorithm; 14 import std.functional; 15 import std.range; 16 import std.traits; 17 18 19 struct HopcroftKarpImpl(node_t, nodes_u_it, nodes_v_it, adjacency_t, count_t = size_t) 20 if ( 21 isIntegral!node_t && isUnsigned!node_t && 22 isForwardRange!nodes_u_it && is(ElementType!nodes_u_it == node_t) && 23 isForwardRange!nodes_v_it && is(ElementType!nodes_v_it == node_t) && 24 isRandomAccessRange!adjacency_t && isForwardRange!(ElementType!adjacency_t) && 25 is(ElementType!(ElementType!adjacency_t) == node_t) && 26 isIntegral!count_t && isUnsigned!count_t 27 ) 28 { 29 struct nil_t { } 30 enum NIL = nil_t(); 31 enum inf = count_t.max; 32 33 private 34 { 35 nodes_u_it U; 36 nodes_v_it V; 37 adjacency_t Adj; 38 node_t[node_t] Pair_U; 39 node_t[node_t] Pair_V; 40 count_t[node_t] _Dist; 41 count_t _DistNIL; 42 RingBuffer!node_t Q; 43 } 44 45 46 this(nodes_u_it U, nodes_v_it V, adjacency_t adjacency) 47 { 48 this.U = U; 49 this.V = V; 50 this.Adj = adjacency; 51 // TODO allow user to pass a reusable buffer (node_t[]) 52 this.Q = RingBuffer!node_t(adjacency.length); 53 } 54 55 56 count_t opCall() 57 { 58 count_t matching; 59 60 while (BFS()) 61 foreach (const node_t u; U) 62 { 63 if (u !in Pair_U) 64 if (DFS(u)) 65 ++matching; 66 } 67 68 return matching; 69 } 70 71 private: 72 73 bool BFS() 74 { 75 foreach (const node_t u; U) 76 { 77 if (u !in Pair_U) 78 { 79 Dist(u, 0u); 80 Q.pushBack(u); 81 } 82 else 83 { 84 Dist(u, inf); 85 } 86 } 87 88 Dist(NIL, inf); 89 90 while (!Q.empty) 91 { 92 const auto u = Q.front; 93 Q.popFront(); 94 95 if (Dist(u) < Dist(NIL)) 96 foreach (const node_t v; Adj[u]) 97 { 98 if (Dist(Pair_V, v) == inf) 99 { 100 Dist(Pair_V, v) = Dist(u) + 1; 101 if (v in Pair_V) 102 Q.pushBack(Pair_V[v]); 103 } 104 } 105 } 106 107 return Dist(NIL) < inf; 108 } 109 110 111 bool DFS(const node_t u) 112 { 113 foreach (const node_t v; Adj[u]) 114 { 115 if (Dist(Pair_V, v) == Dist(u) + 1) 116 { 117 if(v !in Pair_V || DFS(Pair_V[v])) 118 { 119 Pair_V[v] = u; 120 Pair_U[u] = v; 121 122 return true; 123 } 124 } 125 } 126 127 Dist(u) = inf; 128 129 return false; 130 } 131 132 133 ref count_t Dist(node_t n) 134 { 135 return _Dist[n]; 136 } 137 138 139 void Dist(node_t n, count_t c) 140 { 141 _Dist[n] = c; 142 } 143 144 145 ref count_t Dist(nil_t) 146 { 147 return _DistNIL; 148 } 149 150 151 void Dist(nil_t, count_t c) 152 { 153 _DistNIL = c; 154 } 155 156 157 ref count_t Dist(ref node_t[node_t] Pair, node_t n) 158 { 159 if (n in Pair) 160 return Dist(Pair[n]); 161 else 162 return Dist(NIL); 163 } 164 } 165 166 // test typing 167 unittest 168 { 169 uint[][] adjacency; 170 uint[] U; 171 uint[] V; 172 cast(void) HopcroftKarpImpl!( 173 uint, 174 typeof(U), 175 typeof(V), 176 typeof(adjacency), 177 )(U, V, adjacency); 178 } 179 180 // test typing 181 unittest 182 { 183 import std.algorithm; 184 185 auto adjacency = iota(0u).map!(i => iota(0u)); 186 auto U = repeat(0u).take(0u); 187 auto V = repeat(0u).take(0u); 188 cast(void) HopcroftKarpImpl!( 189 uint, 190 typeof(U), 191 typeof(V), 192 typeof(adjacency), 193 )(U, V, adjacency); 194 } 195 196 197 198 /// Takes as input a bipartite graph and produces as output a maximum 199 /// cardinality matching – a set of as many edges as possible with the 200 /// property that no two edges share an endpoint. 201 /// 202 /// Calling this function with a non-bipartite graph results in undefined 203 /// behaviour. 204 count_t hopcroftKarp( 205 count_t = size_t, 206 nodes_u_it, 207 nodes_v_it, 208 adjacency_t, 209 )(nodes_u_it U, nodes_v_it V, adjacency_t adjacency) 210 if ( 211 isForwardRange!nodes_u_it && isForwardRange!nodes_v_it && 212 is(ElementType!nodes_u_it == ElementType!nodes_v_it) && 213 isIntegral!(ElementType!nodes_u_it) && isUnsigned!(ElementType!nodes_u_it) && 214 isRandomAccessRange!adjacency_t && isForwardRange!(ElementType!adjacency_t) && 215 is(ElementType!(ElementType!adjacency_t) == ElementType!nodes_u_it) && 216 isIntegral!count_t && isUnsigned!count_t 217 ) 218 { 219 alias node_t = ElementType!nodes_u_it; 220 auto impl = HopcroftKarpImpl!( 221 node_t, 222 nodes_u_it, 223 nodes_v_it, 224 adjacency_t, 225 count_t, 226 )(U, V, adjacency); 227 228 return impl(); 229 } 230 231 /// ditto 232 count_t hopcroftKarp( 233 count_t = size_t, 234 nodes_u_it, 235 nodes_v_it, 236 adjacency_t, 237 node_t = ElementType!nodes_u_it, 238 )(nodes_u_it U, nodes_v_it V, adjacency_t adjacency, out node_t[node_t] pairU, out node_t[node_t] pairV) 239 if ( 240 isForwardRange!nodes_u_it && isForwardRange!nodes_v_it && 241 is(ElementType!nodes_u_it == ElementType!nodes_v_it) && 242 isIntegral!(ElementType!nodes_u_it) && isUnsigned!(ElementType!nodes_u_it) && 243 isRandomAccessRange!adjacency_t && isForwardRange!(ElementType!adjacency_t) && 244 is(ElementType!(ElementType!adjacency_t) == ElementType!nodes_u_it) && 245 isIntegral!count_t && isUnsigned!count_t 246 ) 247 { 248 auto impl = HopcroftKarpImpl!( 249 node_t, 250 nodes_u_it, 251 nodes_v_it, 252 adjacency_t, 253 count_t, 254 )(U, V, adjacency); 255 256 auto count = impl(); 257 pairU = impl.Pair_U; 258 pairV = impl.Pair_V; 259 260 return count; 261 } 262 263 /// Example 264 unittest 265 { 266 // ____ 267 // / \. 268 // 0 1 2 | 3 4 269 // | / \ /| | |\ | 270 // | / X | / | \ | 271 // |/ / \|/ | \| 272 // 5 6 7 8 9 273 // 274 uint[][] adjacency; 275 adjacency.length = 10; 276 277 alias connect = (from, uint[] neighbors...) { 278 adjacency[from] ~= neighbors; 279 }; 280 281 connect(0u, 5u); 282 connect(1u, 5u, 7u); 283 connect(2u, 6u, 7u); 284 connect(3u, 8u, 9u); 285 connect(4u, 7u, 9u); 286 connect(5u, 0u, 1u); 287 connect(6u, 2u); 288 connect(7u, 2u, 4u); 289 connect(8u, 3u); 290 connect(9u, 3u, 4u); 291 292 auto count = hopcroftKarp(iota(5u), iota(5u, 10u), adjacency); 293 294 assert(count == 5); 295 } 296 297 298 /** 299 Calculate connected components of the graph defined by `hasEdge`. 300 301 Params: 302 hasEdge = Binary predicate taking two nodes of type `size_t` which is 303 true iff the first node is adjacent to the second node. 304 n = Number of nodes in the graph. `hasEdge` must be defined for 305 every pair of integer in `0 .. n`. 306 Returns: Array of components represented as arrays of node indices. 307 */ 308 size_t[][] connectedComponents(alias hasEdge)(size_t n) 309 { 310 alias _hasEdge = binaryFun!hasEdge; 311 312 auto unvisitedNodes = NaturalNumberSet(n, Yes.addAll); 313 auto nodesBuffer = new size_t[n]; 314 auto components = appender!(size_t[][]); 315 316 while (!unvisitedNodes.empty) 317 { 318 // discover startNode's component by depth-first search 319 auto component = discoverComponent!_hasEdge(unvisitedNodes); 320 // copy component indices to buffer 321 auto restNodesBuffer = component 322 .elements 323 .copy(nodesBuffer); 324 // append component to result 325 components ~= nodesBuffer[0 .. $ - restNodesBuffer.length]; 326 // reduce node buffer 327 nodesBuffer = restNodesBuffer; 328 329 } 330 331 return components.data; 332 } 333 334 /// 335 unittest 336 { 337 import std.algorithm : 338 equal, 339 min; 340 341 // _____________ 342 // / \ 343 // (0) --- (1) --- (2) (3) --- (4) 344 enum n = 5; 345 alias connect = (u, v, x, y) => (u == x && v == y) || (u == y && v == x); 346 alias hasEdge = (u, v) => connect(u, v, 0, 1) || 347 connect(u, v, 1, 2) || 348 connect(u, v, 2, 0) || 349 connect(u, v, 3, 4); 350 351 auto components = connectedComponents!hasEdge(n); 352 353 assert(equal(components, [ 354 [0, 1, 2], 355 [3, 4], 356 ])); 357 } 358 359 /// 360 unittest 361 { 362 import std.algorithm : 363 equal, 364 min; 365 366 // _____________ 367 // / \ 368 // (0) --- (1) --- (2) (3) --- (4) 369 // \_____________________/ 370 enum n = 5; 371 alias connect = (u, v, x, y) => (u == x && v == y) || (u == y && v == x); 372 alias hasEdge = (u, v) => connect(u, v, 0, 1) || 373 connect(u, v, 1, 2) || 374 connect(u, v, 2, 0) || 375 connect(u, v, 0, 3) || 376 connect(u, v, 3, 4); 377 378 auto components = connectedComponents!hasEdge(n); 379 380 import std.stdio; 381 assert(equal(components, [ 382 [0, 1, 2, 3, 4], 383 ])); 384 } 385 386 387 private NaturalNumberSet discoverComponent(alias hasEdge)(ref NaturalNumberSet nodes) 388 { 389 assert(!nodes.empty, "cannot discoverComponent of an empty graph"); 390 391 // prepare component 392 auto component = NaturalNumberSet(nodes.maxElement); 393 // select start node 394 auto currentNode = nodes.minElement; 395 396 discoverComponent!hasEdge(nodes, currentNode, component); 397 398 return component; 399 } 400 401 402 private void discoverComponent(alias hasEdge)(ref NaturalNumberSet nodes, size_t currentNode, ref NaturalNumberSet component) 403 { 404 // move currentNode from available nodes to the component 405 component.add(currentNode); 406 nodes.remove(currentNode); 407 408 // try to find successor of current node 409 foreach (nextNode; nodes.elements) 410 { 411 if (hasEdge(currentNode, nextNode)) 412 { 413 assert( 414 hasEdge(nextNode, currentNode), 415 "connectedComponents may be called only on an undirected graph", 416 ); 417 // found successor -> recurse 418 419 discoverComponent!hasEdge(nodes, nextNode, component); 420 } 421 } 422 } 423 424 425 /// 426 struct SingleSourceShortestPathsSolution(weight_t) if (isNumeric!weight_t) 427 { 428 static if (isFloatingPoint!weight_t) 429 enum unconnectedWeight = weight_t.infinity; 430 else 431 enum unconnectedWeight = weight_t.max; 432 enum noPredecessor = size_t.max; 433 434 /// 435 size_t startNode; 436 437 /// 438 size_t[] topologicalOrder; 439 private weight_t[] _distance; 440 private size_t[] _predecessor; 441 442 443 /// 444 @property size_t numNodes() const pure nothrow @safe 445 { 446 return topologicalOrder.length; 447 } 448 449 450 private size_t originalNode(size_t u) const pure nothrow @safe 451 { 452 return topologicalOrder[u]; 453 } 454 455 456 /// 457 @property const(weight_t)[] distances() const pure nothrow @safe 458 { 459 return _distance[]; 460 } 461 462 463 /// 464 @property ref weight_t distance(size_t u) pure nothrow @safe 465 { 466 return _distance[u]; 467 } 468 469 470 /// 471 @property weight_t distance(size_t u) const pure nothrow @safe 472 { 473 return _distance[u]; 474 } 475 476 477 /// 478 @property bool isConnected(size_t u) const pure nothrow @safe 479 { 480 return distance(u) < unconnectedWeight; 481 } 482 483 484 /// 485 @property ref size_t predecessor(size_t u) pure nothrow @safe 486 { 487 return _predecessor[u]; 488 } 489 490 491 /// 492 @property size_t predecessor(size_t u) const pure nothrow @safe 493 { 494 return _predecessor[u]; 495 } 496 497 498 /// 499 @property bool hasPredecessor(size_t u) const pure nothrow @safe 500 { 501 return predecessor(u) != noPredecessor; 502 } 503 504 505 /// 506 static struct ReverseShortestPath 507 { 508 private const(SingleSourceShortestPathsSolution!weight_t)* _solution; 509 private size_t _to; 510 private size_t _current; 511 512 513 private this(const(SingleSourceShortestPathsSolution!weight_t)* solution, size_t to) 514 { 515 this._solution = solution; 516 this._to = to; 517 this._current = solution !is null && solution.isConnected(to) 518 ? to 519 : noPredecessor; 520 } 521 522 523 @property const(SingleSourceShortestPathsSolution!weight_t) solution() pure nothrow @safe 524 { 525 return *_solution; 526 } 527 528 529 /// 530 @property size_t from() const pure nothrow @safe 531 { 532 return _solution.startNode; 533 } 534 535 536 /// 537 @property size_t to() const pure nothrow @safe 538 { 539 return _to; 540 } 541 542 543 /// 544 @property bool empty() const pure nothrow @safe 545 { 546 return _current == noPredecessor; 547 } 548 549 550 /// 551 @property size_t front() const pure nothrow @safe 552 { 553 assert( 554 !empty, 555 "Attempting to fetch the front of an empty SingleSourceShortestPathsSolution.ReverseShortestPath", 556 ); 557 558 return _current; 559 } 560 561 562 /// 563 void popFront() pure nothrow @safe 564 { 565 assert(!empty, "Attempting to popFront an empty SingleSourceShortestPathsSolution.ReverseShortestPath"); 566 567 this._current = _solution !is null 568 ? solution.predecessor(_current) 569 : noPredecessor; 570 } 571 572 573 /// 574 @property ReverseShortestPath save() const pure nothrow @safe @nogc 575 { 576 typeof(return) copy; 577 copy._solution = this._solution; 578 copy._to = this._to; 579 copy._current = this._current; 580 581 return copy; 582 } 583 } 584 585 586 /// Traverse shortest path from dest to startNode; empty if `!isConnected(dest)`. 587 ReverseShortestPath reverseShortestPath(size_t dest) const pure nothrow 588 { 589 return ReverseShortestPath(&this, dest); 590 } 591 } 592 593 594 /** 595 Calculate all shortest paths in DAG starting at `start`. The 596 functions `hasEdge` and `weight` define the graphs structure and 597 weights, respectively. Nodes are represented as `size_t` integers. 598 The graph must be directed and acyclic (DAG). 599 600 Params: 601 hasEdge = Binary predicate taking two nodes of type `size_t` which is 602 true iff the first node is adjacent to the second node. 603 weight = Binary function taking two nodes of type `size_t` which 604 returns the weight of the edge between the first and the 605 second node. The function may be undefined if `hasEdge` 606 returns false for the given arguments. 607 n = Number of nodes in the graph. `hasEdge` must be defined for 608 every pair of integer in `0 .. n`. 609 Throws: NoDAG if a cycle is detected. 610 Returns: SingleSourceShortestPathsSolution 611 */ 612 auto dagSingleSourceShortestPaths(alias hasEdge, alias weight)(size_t start, size_t n) 613 { 614 import std.experimental.checkedint; 615 616 alias _hasEdge = binaryFun!hasEdge; 617 alias _weight = binaryFun!weight; 618 alias weight_t = typeof(_weight(size_t.init, size_t.init)); 619 alias saturated = Checked!(weight_t, Saturate); 620 621 SingleSourceShortestPathsSolution!weight_t result; 622 623 with (result) 624 { 625 // sort topological 626 topologicalOrder = topologicalSort!_hasEdge(n); 627 alias N = (u) => originalNode(u); 628 629 _distance = uninitializedArray!(weight_t[])(n); 630 _distance[] = result.unconnectedWeight; 631 _distance[start] = 0; 632 _predecessor = uninitializedArray!(size_t[])(n); 633 _predecessor[] = size_t.max; 634 635 foreach (u; topologicalOrder.countUntil(start) .. n) 636 foreach (v; u + 1 .. n) 637 if (_hasEdge(N(u), N(v))) 638 { 639 auto vDistance = saturated(distance(N(v))); 640 auto uDistance = saturated(distance(N(u))) + saturated(_weight(N(u), N(v))); 641 642 if (vDistance > uDistance) 643 { 644 distance(N(v)) = uDistance.get(); 645 predecessor(N(v)) = N(u); 646 } 647 } 648 } 649 650 return result; 651 } 652 653 /// 654 unittest 655 { 656 import std.algorithm : equal; 657 658 // _____________ _____________ 659 // / v / v 660 // (0) --> (1) --> (2) (3) --> (4) 661 enum n = 5; 662 alias hasEdge = (u, v) => (u + 1 == v && u != 2) || 663 (u + 2 == v && u % 2 == 0); 664 alias weight = (u, v) => 1; 665 666 auto shortestPaths = dagSingleSourceShortestPaths!(hasEdge, weight)(0, n); 667 668 assert(equal(shortestPaths.reverseShortestPath(4), [4, 2, 0])); 669 assert(shortestPaths.distance(4) == 2); 670 assert(equal(shortestPaths.reverseShortestPath(2), [2, 0])); 671 assert(shortestPaths.distance(2) == 1); 672 assert(equal(shortestPaths.reverseShortestPath(1), [1, 0])); 673 assert(shortestPaths.distance(1) == 1); 674 assert(equal(shortestPaths.reverseShortestPath(3), size_t[].init)); 675 assert(!shortestPaths.isConnected(3)); 676 } 677 678 679 /** 680 Sort nodes of a DAG topologically. The graph structure is defined by 681 `hasEdge` and `n`. Nodes are represented as `size_t` integers. The graph 682 must be directed and acyclic (DAG). 683 684 Params: 685 hasEdge = Binary predicate taking two nodes of type `size_t` which is 686 true iff the first node is adjacent to the second node. 687 n = Number of nodes in the graph. `hasEdge` must be defined for 688 every pair of integer in `0 .. n`. 689 Throws: NoDAG if a cycle is detected. 690 Returns: SingleSourceShortestPathsSolution 691 */ 692 auto topologicalSort(alias hasEdge)(size_t n) 693 { 694 alias _hasEdge = binaryFun!hasEdge; 695 696 // list that will contain the sorted nodes 697 auto sortedNodes = new size_t[n]; 698 699 auto sortedNodesHead = sortedNodes[]; 700 void enqueueNode(size_t node) 701 { 702 sortedNodesHead[$ - 1] = node; 703 --sortedNodesHead.length; 704 } 705 706 // keep track which nodes have been visited 707 auto unvisitedNodes = NaturalNumberSet(n, Yes.addAll); 708 auto temporaryVisitedNodes = NaturalNumberSet(n); 709 710 void visit(size_t node) 711 { 712 if (node !in unvisitedNodes) 713 // already visited 714 return; 715 716 if (node in temporaryVisitedNodes) 717 // cycle detected 718 throw new NoDAG(); 719 720 temporaryVisitedNodes.add(node); 721 722 foreach (nextNode; unvisitedNodes.elements) 723 if (_hasEdge(node, nextNode)) 724 visit(nextNode); 725 726 temporaryVisitedNodes.remove(node); 727 unvisitedNodes.remove(node); 728 enqueueNode(node); 729 } 730 731 foreach (node; unvisitedNodes.elements) 732 visit(node); 733 734 return sortedNodes; 735 } 736 737 /// 738 unittest 739 { 740 import std.algorithm : equal; 741 742 // _____________ _____________ 743 // / v / v 744 // (0) --> (1) --> (2) (3) --> (4) 745 enum n = 5; 746 alias hasEdge = (u, v) => (u + 1 == v && u != 2) || 747 (u + 2 == v && u % 2 == 0); 748 749 auto topologicalOrder = topologicalSort!hasEdge(n); 750 751 assert(equal(topologicalOrder, [3, 0, 1, 2, 4])); 752 } 753 754 /// 755 unittest 756 { 757 import std.exception : assertThrown; 758 759 // _____________ _____________ 760 // / v / v 761 // (0) --> (1) --> (2) (3) --> (4) 762 // ^_____________________________/ 763 enum n = 5; 764 alias hasEdge = (u, v) => (u + 1 == v && u != 2) || 765 (u + 2 == v && u % 2 == 0) || 766 u == 4 && v == 0; 767 768 assertThrown!NoDAG(topologicalSort!hasEdge(n)); 769 } 770 771 772 /// Thrown if a cycle was detected. 773 class NoDAG : Exception 774 { 775 this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) 776 { 777 super("not a DAG: graph has cycles", file, line, next); 778 } 779 } 780