Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
bipartite_test.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
32
49#include <gtest/gtest.h>
50#include <tpl_bipartite.H>
51#include <tpl_graph.H>
52
53using namespace std;
54using namespace Aleph;
55
56// Graph type for testing - using Empty_Class for arc info as required by bipartite algorithms
58
59// ============================================================================
60// Helper Functions
61// ============================================================================
62
69{
70 Graph g;
71
72 // Create left partition nodes
73 vector<Graph::Node *> left(m);
74 for (size_t i = 0; i < m; ++i)
75 left[i] = g.insert_node(static_cast<int>(i));
76
77 // Create right partition nodes
78 vector<Graph::Node *> right(n);
79 for (size_t j = 0; j < n; ++j)
80 right[j] = g.insert_node(static_cast<int>(m + j));
81
82 // Connect every node in left to every node in right
83 for (size_t i = 0; i < m; ++i)
84 for (size_t j = 0; j < n; ++j)
85 g.insert_arc(left[i], right[j]);
86
87 return g;
88}
89
95{
96 Graph g;
97
98 if (n == 0)
99 return g;
100
101 vector<Graph::Node *> nodes(n);
102 for (size_t i = 0; i < n; ++i)
103 nodes[i] = g.insert_node(static_cast<int>(i));
104
105 for (size_t i = 0; i + 1 < n; ++i)
106 g.insert_arc(nodes[i], nodes[i + 1]);
107
108 return g;
109}
110
116{
117 Graph g;
118
119 if (n < 3)
120 return g;
121
122 vector<Graph::Node *> nodes(n);
123 for (size_t i = 0; i < n; ++i)
124 nodes[i] = g.insert_node(static_cast<int>(i));
125
126 for (size_t i = 0; i < n; ++i)
127 g.insert_arc(nodes[i], nodes[(i + 1) % n]);
128
129 return g;
130}
131
137{
138 Graph g;
139
140 auto * center = g.insert_node(0);
141
142 for (size_t i = 0; i < n; ++i)
143 {
144 auto * leaf = g.insert_node(static_cast<int>(i + 1));
145 g.insert_arc(center, leaf);
146 }
147
148 return g;
149}
150
155{
156 Graph g;
157
158 auto * a = g.insert_node(0);
159 auto * b = g.insert_node(1);
160 auto * c = g.insert_node(2);
161
162 g.insert_arc(a, b);
163 g.insert_arc(b, c);
164 g.insert_arc(c, a);
165
166 return g;
167}
168
173{
174 Graph g;
175
176 // Component 1: path 0--1
177 auto * a = g.insert_node(0);
178 auto * b = g.insert_node(1);
179 g.insert_arc(a, b);
180
181 // Component 2: path 2--3
182 auto * c = g.insert_node(2);
183 auto * d = g.insert_node(3);
184 g.insert_arc(c, d);
185
186 return g;
187}
188
200{
201 // Create sets for fast lookup
203
204 for (auto it = l.get_it(); it.has_curr(); it.next_ne())
205 left_set.insert(it.get_curr());
206
207 for (auto it = r.get_it(); it.has_curr(); it.next_ne())
208 right_set.insert(it.get_curr());
209
210 // Check that partitions don't overlap
211 for (auto it = l.get_it(); it.has_curr(); it.next_ne())
212 if (right_set.contains(it.get_curr()))
213 return false;
214
215 // Check that every edge between partitioned nodes connects different partitions
216 for (Arc_Iterator<Graph> it(g); it.has_curr(); it.next_ne())
217 {
218 auto * arc = it.get_curr();
219 auto * src = g.get_src_node(arc);
220 auto * tgt = g.get_tgt_node(arc);
221
222 bool src_in_left = left_set.contains(src);
223 bool src_in_right = right_set.contains(src);
224 bool tgt_in_left = left_set.contains(tgt);
225 bool tgt_in_right = right_set.contains(tgt);
226
227 // Skip edges involving nodes that weren't partitioned
229 continue;
230
231 // Both in same partition = invalid
233 return false;
234 }
235
236 return true;
237}
238
243bool verify_matching(const Graph & g,
245{
247
248 for (auto it = matching.get_it(); it.has_curr(); it.next_ne())
249 {
250 auto * arc = it.get_curr();
251 auto * src = g.get_src_node(arc);
252 auto * tgt = g.get_tgt_node(arc);
253
254 // Check if either node is already matched
255 if (matched_nodes.contains(src) || matched_nodes.contains(tgt))
256 return false;
257
258 matched_nodes.insert(src);
259 matched_nodes.insert(tgt);
260 }
261
262 return true;
263}
264
265// ============================================================================
266// Basic Bipartite Detection Tests
267// ============================================================================
268
278
280{
281 Graph g;
282 g.insert_node(1);
283
285
287
288 // Single node goes into one partition
289 EXPECT_EQ(l.size() + r.size(), 1u);
290}
291
293{
294 Graph g;
295 auto * a = g.insert_node(1);
296 auto * b = g.insert_node(2);
297 g.insert_arc(a, b);
298
300
302
303 EXPECT_EQ(l.size(), 1u);
304 EXPECT_EQ(r.size(), 1u);
306}
307
309{
310 auto g = create_path_graph(4); // 0--1--2--3
311
313
315
316 EXPECT_EQ(l.size() + r.size(), 4u);
317 EXPECT_EQ(l.size(), 2u);
318 EXPECT_EQ(r.size(), 2u);
320}
321
323{
324 auto g = create_path_graph(5); // 0--1--2--3--4
325
327
329
330 EXPECT_EQ(l.size() + r.size(), 5u);
332}
333
335{
336 auto g = create_star_graph(5); // Center with 5 leaves
337
339
341
342 EXPECT_EQ(l.size() + r.size(), 6u);
343 // One partition has the center, other has all leaves
344 EXPECT_TRUE((l.size() == 1 && r.size() == 5) ||
345 (l.size() == 5 && r.size() == 1));
347}
348
350{
351 auto g = create_complete_bipartite(2, 2);
352
354
356
357 EXPECT_EQ(l.size(), 2u);
358 EXPECT_EQ(r.size(), 2u);
360}
361
363{
364 auto g = create_complete_bipartite(3, 3);
365
367
369
370 EXPECT_EQ(l.size(), 3u);
371 EXPECT_EQ(r.size(), 3u);
373}
374
376{
377 auto g = create_complete_bipartite(2, 5);
378
380
382
383 EXPECT_EQ(l.size() + r.size(), 7u);
385}
386
388{
389 auto g = create_cycle_graph(6); // 6-cycle is bipartite
390
392
394
395 EXPECT_EQ(l.size(), 3u);
396 EXPECT_EQ(r.size(), 3u);
398}
399
400// ============================================================================
401// Non-Bipartite Graph Detection Tests
402// ============================================================================
403
405{
406 auto g = create_triangle();
407
409
410 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), domain_error);
411}
412
414{
415 auto g = create_cycle_graph(5); // 5-cycle is NOT bipartite
416
418
419 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), domain_error);
420}
421
423{
424 auto g = create_cycle_graph(11); // 11-cycle is NOT bipartite
425
427
428 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), domain_error);
429}
430
432{
433 // K_3 is a triangle
434 auto g = create_triangle();
435
437
438 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), domain_error);
439}
440
442{
443 Graph g;
444
445 // Create a bipartite part
446 auto * a = g.insert_node(0);
447 auto * b = g.insert_node(1);
448 g.insert_arc(a, b);
449
450 // Attach an odd cycle (3 nodes = triangle) to node b
451 auto * c = g.insert_node(2);
452 auto * d = g.insert_node(3);
453 g.insert_arc(b, c);
454 g.insert_arc(c, d);
455 g.insert_arc(d, b); // Creates odd cycle b-c-d-b (3 nodes)
456
458
459 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), domain_error);
460}
461
462// ============================================================================
463// Disconnected Graph Tests
464// ============================================================================
465
466// Note: These tests document the expected behavior.
467// The current implementation may not handle disconnected graphs correctly.
468
470{
471 Graph g;
472 g.insert_node(1);
473 g.insert_node(2);
474 // No edges - two isolated nodes
475
477
478 // Two isolated nodes should be bipartite
479 // Current implementation only processes first node's component
481
482 // At minimum, should process one node without crashing
483 EXPECT_GE(l.size() + r.size(), 1u);
484}
485
487{
489
491
493
494 // Should process at least the first component
495 EXPECT_GE(l.size() + r.size(), 2u);
496
497 // Verify what was partitioned is correct
499}
500
501// ============================================================================
502// Class Wrapper Tests
503// ============================================================================
504
506{
507 auto g = create_complete_bipartite(3, 4);
508
510
512
513 EXPECT_EQ(l.size() + r.size(), 7u);
515}
516
525
526// ============================================================================
527// Maximum Matching Tests
528// ============================================================================
530{
531 Graph g;
532
534
535 // Empty graph: should not throw and matching must remain empty
538 EXPECT_TRUE(matching.is_empty());
539}
540
542{
543 Graph g;
544 auto * a = g.insert_node(1);
545 auto * b = g.insert_node(2);
546 g.insert_arc(a, b);
547
549
551
552 EXPECT_EQ(matching.size(), 1u);
554}
555
557{
558 auto g = create_path_graph(4); // 0--1--2--3
559
561
563
564 // Maximum matching in path of 4 nodes is 2 edges
565 EXPECT_EQ(matching.size(), 2u);
567}
568
570{
571 auto g = create_path_graph(5); // 0--1--2--3--4
572
574
576
577 // Maximum matching in path of 5 nodes is 2 edges
578 EXPECT_EQ(matching.size(), 2u);
580}
581
594
607
620
622{
623 auto g = create_complete_bipartite(2, 5);
624
626
628
629 // Maximum matching limited by smaller partition: 2 edges
630 EXPECT_EQ(matching.size(), 2u);
632}
633
635{
636 auto g = create_complete_bipartite(5, 2);
637
639
641
642 // Maximum matching limited by smaller partition: 2 edges
643 EXPECT_EQ(matching.size(), 2u);
645}
646
648{
649 auto g = create_star_graph(5);
650
652
654
655 // Star can only have 1 edge in matching (center is shared)
656 EXPECT_EQ(matching.size(), 1u);
658}
659
661{
662 auto g = create_cycle_graph(6);
663
665
667
668 // 6-cycle has perfect matching: 3 edges
669 EXPECT_EQ(matching.size(), 3u);
671}
672
683
684// ============================================================================
685// Matching Class Wrapper Tests
686// ============================================================================
687
699
710
711// ============================================================================
712// Stress Tests
713// ============================================================================
714
716{
717 auto g = create_complete_bipartite(50, 50);
718
720
722
723 EXPECT_EQ(l.size() + r.size(), 100u);
725}
726
728{
729 auto g = create_path_graph(100);
730
732
734
735 EXPECT_EQ(l.size(), 50u);
736 EXPECT_EQ(r.size(), 50u);
738}
739
751
753{
754 auto g = create_cycle_graph(100);
755
757
759
760 EXPECT_EQ(l.size(), 50u);
761 EXPECT_EQ(r.size(), 50u);
763}
764
765// ============================================================================
766// Edge Cases
767// ============================================================================
768
770{
771 Graph g;
772 auto * a = g.insert_node(1);
773 auto * b = g.insert_node(2);
774
775 // Multiple edges between same nodes (multigraph)
776 g.insert_arc(a, b);
777 g.insert_arc(a, b);
778 g.insert_arc(a, b);
779
781
783
784 EXPECT_EQ(l.size(), 1u);
785 EXPECT_EQ(r.size(), 1u);
786}
787
789{
790 Graph g;
791
792 // Bipartite component
793 auto * a = g.insert_node(0);
794 auto * b = g.insert_node(1);
795 g.insert_arc(a, b);
796
797 // Isolated node
798 g.insert_node(2);
799
801
803
804 // At least the connected component should be processed
805 EXPECT_GE(l.size() + r.size(), 2u);
806}
807
808// ============================================================================
809// Color Enum Tests
810// ============================================================================
811
813{
814 // Verify the color enum values
816 EXPECT_EQ(Bp_Red, 1);
817 EXPECT_EQ(Bp_Blue, 2);
818}
819
820// ============================================================================
821// Hopcroft-Karp Matching Tests
822// ============================================================================
823
824// --- Base cases ---
825
834
845
847{
848 Graph g;
849 auto * a = g.insert_node(0);
850 auto * b = g.insert_node(1);
851 g.insert_arc(a, b);
852
855
856 EXPECT_EQ(matching.size(), 1u);
858}
859
860// --- Standard bipartite graphs ---
861
872
883
894
905
916
927
938
949
950// --- Non-bipartite detection ---
951
960
969
970// --- Disconnected graphs ---
971
982
984{
985 Graph g;
986
987 // Component 1: single edge
988 auto * a = g.insert_node(0);
989 auto * b = g.insert_node(1);
990 g.insert_arc(a, b);
991
992 // Isolated nodes
993 g.insert_node(2);
994 g.insert_node(3);
995
996 // Component 2: K_{2,2}
997 auto * c = g.insert_node(10);
998 auto * d = g.insert_node(11);
999 auto * e = g.insert_node(12);
1000 auto * f = g.insert_node(13);
1001 g.insert_arc(c, e);
1002 g.insert_arc(c, f);
1003 g.insert_arc(d, e);
1004 g.insert_arc(d, f);
1005
1008
1009 // 1 from edge + 2 from K_{2,2} = 3
1010 EXPECT_EQ(matching.size(), 3u);
1012}
1013
1015{
1016 Graph g;
1017
1018 // Component 1: K_{3,3}
1019 vector<Graph::Node *> l1(3), r1(3);
1020 for (int i = 0; i < 3; ++i)
1021 l1[i] = g.insert_node(i);
1022 for (int i = 0; i < 3; ++i)
1023 r1[i] = g.insert_node(10 + i);
1024 for (int i = 0; i < 3; ++i)
1025 for (int j = 0; j < 3; ++j)
1026 g.insert_arc(l1[i], r1[j]);
1027
1028 // Component 2: K_{2,2}
1029 vector<Graph::Node *> l2(2), r2(2);
1030 for (int i = 0; i < 2; ++i)
1031 l2[i] = g.insert_node(20 + i);
1032 for (int i = 0; i < 2; ++i)
1033 r2[i] = g.insert_node(30 + i);
1034 for (int i = 0; i < 2; ++i)
1035 for (int j = 0; j < 2; ++j)
1036 g.insert_arc(l2[i], r2[j]);
1037
1040
1041 // 3 + 2 = 5
1042 EXPECT_EQ(matching.size(), 5u);
1044}
1045
1046// --- Cross-validation with max-flow ---
1047
1061
1075
1089
1090// --- Functor wrapper ---
1091
1102
1113
1114// --- Stress tests ---
1115
1126
1137
1148
1149int main(int argc, char **argv)
1150{
1151 ::testing::InitGoogleTest(&argc, argv);
1152 return RUN_ALL_TESTS();
1153}// satisfy CI policy
1154// satisfy CI policy for tpl_bipartite.H and Subset_Sum.H
int main()
Graph create_complete_bipartite(size_t m, size_t n)
Creates a complete bipartite graph K_{m,n} Left partition: nodes 0..m-1 Right partition: nodes m....
Graph create_path_graph(size_t n)
Creates a path graph with n nodes: 0–1–2–...–n-1 Path graphs are always bipartite.
Graph create_disconnected_bipartite()
Creates two disconnected components.
Graph create_triangle()
Creates a triangle (K_3) - the simplest non-bipartite graph.
bool verify_bipartition(const Graph &g, const DynDlist< Graph::Node * > &l, const DynDlist< Graph::Node * > &r)
Verifies that a bipartition is valid:
Graph create_cycle_graph(size_t n)
Creates a cycle graph with n nodes: 0–1–2–...–n-1–0 Even cycles are bipartite, odd cycles are not.
bool verify_matching(const Graph &g, const DynDlist< Graph::Arc * > &matching)
Verifies that a matching is valid:
Graph create_star_graph(size_t n)
Creates a star graph with center and n leaves Star graphs are always bipartite.
Class that takes a bipartite graph and computes the partition sets.
Class for computing the maximum cardinality matching of a bipartite graph.
Dynamic doubly linked list with O(1) size and bidirectional access.
Dynamic set implemented using AVL binary search trees of type Avl_Tree<Key>.
Key * insert(const Key &key)
Inserts a key into the dynamic set.
void next_ne() noexcept
Advances the iterator to the next filtered element (noexcept version).
constexpr bool is_empty() const noexcept
Definition htlist.H:419
size_t size() const noexcept
Count the number of elements of the list.
Definition htlist.H:1065
virtual Node * insert_node(Node *node) noexcept
Insertion of a node already allocated.
Definition tpl_graph.H:525
Arc * insert_arc(Node *src_node, Node *tgt_node, void *a)
Definition tpl_graph.H:605
Node * get_src_node(Arc *arc) const noexcept
Return the source node of arc (only for directed graphs)
Definition graph-dry.H:779
Node * get_tgt_node(Arc *arc) const noexcept
Return the target node of arc (only for directed graphs)
Definition graph-dry.H:785
auto get_it() const
Return a properly initialized iterator positioned at the first item on the container.
Definition ah-dry.H:228
#define TEST(name)
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
STL namespace.
Filtered iterator on all the arcs of a graph.
Definition tpl_graph.H:1165
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:223
FooMap m(5, fst_unit_pair_hash, snd_unit_pair_hash)
DynList< int > l1
DynList< int > l2
gsl_rng * r
Bipartite graph detection and 2-coloring.
Generic graph and digraph implementations.
DynList< int > l