Scippy

    SCIP

    Solving Constraint Integer Programs

    GomoryHuTree.cpp
    Go to the documentation of this file.
    1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    2/* */
    3/* This file is part of the program and library */
    4/* SCIP --- Solving Constraint Integer Programs */
    5/* */
    6/* Copyright (c) 2002-2025 Zuse Institute Berlin (ZIB) */
    7/* */
    8/* Licensed under the Apache License, Version 2.0 (the "License"); */
    9/* you may not use this file except in compliance with the License. */
    10/* You may obtain a copy of the License at */
    11/* */
    12/* http://www.apache.org/licenses/LICENSE-2.0 */
    13/* */
    14/* Unless required by applicable law or agreed to in writing, software */
    15/* distributed under the License is distributed on an "AS IS" BASIS, */
    16/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
    17/* See the License for the specific language governing permissions and */
    18/* limitations under the License. */
    19/* */
    20/* You should have received a copy of the Apache-2.0 license */
    21/* along with SCIP; see the file LICENSE. If not visit scipopt.org. */
    22/* */
    23/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    24
    25/**@file GomoryHuTree.cpp
    26 * @brief generator for global cuts in undirected graphs
    27 * @author Georg Skorobohatyj
    28 * @author Timo Berthold
    29 */
    30
    31/*---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0----+----1----+----2*/
    32
    33
    34#include <stdio.h>
    35#include <assert.h>
    36
    37#include "objscip/objscip.h"
    38#include "GomoryHuTree.h"
    39
    40/** epsilon value for numerical comparisons */
    41#define EPS 1.0E-10
    42
    43/* static variables */
    45static long* number;
    46static long max_dist;
    47static long bound;
    49
    50
    51/** create a graph */
    53 int n, /**< number of nodes */
    54 int m, /**< number of edges */
    55 GRAPH** gr /**< pointer to store graph */
    56 )
    57{
    58 assert( gr != NULL );
    59
    61 if( *gr == NULL )
    62 return FALSE;
    63
    64 BMSallocMemoryArray(&(*gr)->nodes, n);
    65 if( (*gr)->nodes == NULL )
    66 {
    67 BMSfreeMemory(gr);
    68 return FALSE;
    69 }
    70
    71 BMSallocMemoryArray(&(*gr)->edges, m);
    72 if( (*gr)->edges == NULL )
    73 {
    74 BMSfreeMemoryArray(&(*gr)->nodes);
    75 BMSfreeMemory(gr);
    76 return FALSE;
    77 }
    78 (*gr)->nuses = 1;
    79 (*gr)->nnodes = n;
    80 (*gr)->nedges = m/2;
    81 (*gr)->nedgesnonzero = m/2;
    82
    83 return TRUE;
    84}
    85
    86/** free a graph */
    87static
    89 GRAPH** gr /**< pointer a graph */
    90 )
    91{
    92 assert(gr != NULL);
    93 assert(*gr != NULL);
    94 assert((*gr)->nuses == 0);
    95
    96 BMSfreeMemory(&(*gr)->nodes);
    97 BMSfreeMemory(&(*gr)->edges);
    98 BMSfreeMemory(gr);
    99}
    100
    101/** capture graph */
    103 GRAPH* gr /**< graph */
    104 )
    105{
    106 assert(gr != NULL);
    107
    108 ++gr->nuses;
    109}
    110
    111/** release graph */
    113 GRAPH** gr /**< graph */
    114 )
    115{
    116 assert(gr != NULL);
    117 assert(*gr != NULL);
    118
    119 --(*gr)->nuses;
    120
    121 if( (*gr)->nuses == 0 )
    122 free_graph(gr);
    123 *gr = NULL;
    124}
    125
    126/** initialize maximum flow computation */
    127static
    129 long n /**< number of nodes */
    130 )
    131{
    132 active = (GRAPHNODE**) malloc((n+1L) * sizeof (GRAPHNODE*));
    133
    134 /* holds stacks of active nodes arranged by distances */
    135 if ( active == (GRAPHNODE **) 0 )
    136 {
    137 printf ("Unable to allocate memory\n");
    138 return FALSE;
    139 }
    140
    141 number = (long *) malloc ((n+1L) * sizeof (long));
    142
    143 /* counts occurences of node distances in set of alive nodes, i.e., nodes not contained in the set of nodes
    144 * disconnected from the sink */
    145 if ( number == (long *) 0 )
    146 {
    147 printf ("Unable to allocate memory\n");
    148 return FALSE;
    149 }
    150 co_check = TRUE;
    151
    152 return TRUE;
    153}
    154
    155/** free initialization data structures */
    156static
    157void fini_maxflow(void)
    158{
    159 free(active);
    160 free(number);
    161}
    162
    163/** global relabel operation */
    164static
    166 GRAPH* gr, /**< graph */
    167 GRAPHNODE* tptr /**< node to be relabeled */
    168 )
    169{
    170 /* breadth first search to get exact distance labels from sink with reordering of stack of active nodes */
    171 GRAPHNODE* front;
    172 GRAPHNODE* rear;
    173 GRAPHNODE* nptr;
    174 GRAPHNODE** ptr;
    175 GRAPHEDGE* eptr;
    176 long n;
    177 long level;
    178 long count;
    179 long i;
    180
    181 n = gr->nnodes;
    182 for ( nptr = &(gr->nodes[n-1L]); nptr >= gr->nodes; nptr-- )
    183 {
    184 nptr->unmarked = TRUE;
    185 nptr->stack_link = NULL;
    186 nptr->scan_ptr = nptr->first_edge;
    187 while( nptr->scan_ptr != NULL && nptr->scan_ptr->cap <= EPS )
    188 nptr->scan_ptr = nptr->scan_ptr->next;
    189 }
    190 tptr->unmarked = FALSE;
    191
    192 /* initialize stack of active nodes */
    193 for ( ptr = &(active[n]); ptr >= active; ptr-- )
    194 *ptr = NULL;
    195
    196 for ( i = 0L; i <= n; i++ )
    197 number[i] = 0L;
    198
    199 max_dist = 0L;
    200 count = 1L; /* number of alive nodes */
    201 front = tptr;
    202 rear = front;
    203
    204 bfs_next:
    205 level = rear->dist + 1L;
    206 eptr = rear->first_edge;
    207 while ( eptr != NULL )
    208 {
    209 nptr = eptr->adjac;
    210 if ( nptr->alive && nptr->unmarked && eptr->back->rcap > EPS )
    211 {
    212 assert(eptr->cap > EPS);
    213 assert(eptr->back->cap > EPS);
    214
    215 nptr->unmarked = FALSE;
    216 nptr->dist = (int) level;
    217 ++count;
    218 ++number[level];
    219
    220 if ( nptr->excess > EPS )
    221 {
    222 nptr->stack_link = active[level];
    223 active[level] = nptr;
    224 max_dist = level;
    225 }
    226 front->bfs_link = nptr;
    227 front = nptr;
    228 }
    229 eptr = eptr->next;
    230 }
    231
    232 if ( front == rear )
    233 goto bfs_ready;
    234
    235 rear = rear->bfs_link;
    236 goto bfs_next;
    237
    238 bfs_ready:
    239
    240 if ( count < bound )
    241 {
    242 /* identify nodes that are marked alive but have not been reached by BFS and mark them as dead */
    243 for ( nptr = &(gr->nodes[n-1L]); nptr >= gr->nodes; nptr-- )
    244 {
    245 if ( nptr->unmarked && nptr->alive )
    246 {
    247 nptr->dist = (int) n;
    248 nptr->alive = FALSE;
    249 }
    250 }
    251 bound = count;
    252 }
    253}
    254
    255/** compute maximum flow
    256 *
    257 * Determines maximum flow and minimum cut between nodes s (= *s_ptr) and t (= *t_ptr) in an undirected graph
    258 *
    259 * References:
    260 * A. Goldberg/ E. Tarjan: "A New Approach to the Maximum Flow Problem", in Proc. 18th ACM Symp. on Theory of Computing, 1986.
    261 */
    262static
    263double maxflow(
    264 GRAPH* gr, /**< graph */
    265 GRAPHNODE* s_ptr, /**< start node */
    266 GRAPHNODE* t_ptr /**< target node */
    267 )
    268{
    269 GRAPHNODE* aptr;
    270 GRAPHNODE* nptr;
    271 GRAPHNODE* q_front;
    272 GRAPHNODE* q_rear;
    273 GRAPHEDGE* eptr;
    274 long n;
    275 long m;
    276 long m0;
    277 long level;
    278 long i;
    279 long n_discharge;
    280 double incre;
    281 long dmin;
    282 double cap;
    283
    284 /* node ids range from 1 to n, node array indices range from 0 to n-1 */
    285 n = gr->nnodes;
    286 for ( nptr = &(gr->nodes[n-1L]); nptr >= gr->nodes; nptr-- )
    287 {
    288 nptr->scan_ptr = nptr->first_edge;
    289 while( nptr->scan_ptr != NULL && nptr->scan_ptr->cap <= EPS )
    290 nptr->scan_ptr = nptr->scan_ptr->next;
    291
    292 if ( nptr->scan_ptr == NULL )
    293 {
    294 fprintf(stderr, "isolated node in input graph\n");
    295 return FALSE;
    296 }
    297
    298 nptr->excess = 0.0;
    299 nptr->stack_link = NULL;
    300 nptr->alive = TRUE;
    301 nptr->unmarked = TRUE;
    302 }
    303
    304 m = gr->nedgesnonzero;
    305 m0 = gr->nedges;
    306 for ( eptr = &(gr->edges[m-1L]); eptr >= gr->edges; eptr-- )
    307 eptr->rcap = eptr->cap;
    308
    309 for ( eptr = &(gr->edges[m0+m-1L]); eptr >= &(gr->edges[m0]); eptr-- )
    310 eptr->rcap = eptr->cap;
    311
    312 for ( i = n; i >= 0L; i-- )
    313 {
    314 number[i] = 0L;
    315 active[i] = NULL;
    316 }
    317 t_ptr->dist = 0L;
    318
    319 /* breadth first search to get exact distances from sink and for test of graph connectivity */
    320 t_ptr->unmarked = FALSE;
    321 q_front = t_ptr;
    322 q_rear = q_front;
    323
    324 bfs_next:
    325 level = q_rear->dist + 1L;
    326 eptr = q_rear->first_edge;
    327
    328 while ( eptr != NULL )
    329 {
    330 assert(eptr->back->cap == eptr->back->rcap); /*lint !e777*/
    331 if ( eptr->adjac->unmarked && eptr->back->rcap > EPS )
    332 {
    333 nptr = eptr->adjac;
    334 nptr->unmarked = FALSE;
    335 nptr->dist = (int) level;
    336 ++number[level];
    337 q_front->bfs_link = nptr;
    338 q_front = nptr;
    339 }
    340 eptr = eptr->next;
    341 }
    342
    343 if ( q_rear == q_front )
    344 goto bfs_ready;
    345
    346 q_rear = q_rear->bfs_link;
    347 goto bfs_next;
    348
    349 bfs_ready:
    350 if ( co_check )
    351 {
    352 co_check = FALSE;
    353 for ( nptr = &(gr->nodes[n-1]); nptr >= gr->nodes; --nptr )
    354 {
    355 if ( nptr->unmarked )
    356 return (-1.0);
    357 }
    358 }
    359
    360 s_ptr->dist = (int) n; /* number[0] and number[n] not required */
    361 t_ptr->dist = 0L;
    362 t_ptr->excess = 1.0; /* to be subtracted again */
    363
    364 /* initial preflow push from source node */
    365 max_dist = 0L; /* = max_dist of active nodes */
    366 eptr = s_ptr->first_edge;
    367
    368 while ( eptr != NULL )
    369 {
    370 if( eptr->cap > EPS )
    371 {
    372 nptr = eptr->adjac;
    373 cap = eptr->rcap;
    374 nptr->excess += cap;
    375 s_ptr->excess -= cap;
    376 eptr->back->rcap += cap;
    377 eptr->rcap = 0.0;
    378
    379 if ( nptr != t_ptr && nptr->excess <= cap + EPS )
    380 {
    381 /* push node nptr onto stack for nptr->dist, but only once in case of double edges */
    382 nptr->stack_link = active[nptr->dist];
    383 active[nptr->dist] = nptr;
    384 if ( nptr->dist > max_dist )
    385 max_dist = nptr->dist;
    386 }
    387 }
    388 eptr = eptr->next;
    389 }
    390
    391 s_ptr->alive = FALSE;
    392 bound = n;
    393 n_discharge = 0L;
    394
    395 /* main loop */
    396 do
    397 {
    398 /* get maximum distance active node */
    399 aptr = active[max_dist];
    400 while ( aptr != NULL )
    401 {
    402 active[max_dist] = aptr->stack_link;
    403 eptr = aptr->scan_ptr;
    404 assert(eptr != NULL);
    405 assert(eptr->back != NULL);
    406 assert(eptr->cap > EPS);
    407
    408 edge_scan: /* for current active node */
    409 assert(eptr != NULL);
    410 assert(eptr->back != NULL);
    411 assert(eptr->cap > EPS);
    412
    413 nptr = eptr->adjac;
    414 assert(nptr != NULL);
    415 if ( nptr->dist == aptr->dist - 1L && eptr->rcap > EPS )
    416 {
    417 incre = aptr->excess;
    418 if ( incre <= eptr->rcap )
    419 {
    420 /* perform a non saturating push */
    421 eptr->rcap -= incre;
    422 eptr->back->rcap += incre;
    423 aptr->excess = 0.0;
    424 nptr->excess += incre;
    425
    426 if ( nptr->excess <= incre + EPS )
    427 {
    428 /* push nptr onto active stack */
    429 nptr->stack_link = active[nptr->dist];
    430 active[nptr->dist] = nptr;
    431 }
    432
    433 assert(eptr->cap > EPS);
    434 aptr->scan_ptr = eptr;
    435
    436 goto node_ready;
    437 }
    438 else
    439 {
    440 /* perform a saturating push */
    441 incre = eptr->rcap;
    442 eptr->back->rcap += incre;
    443 aptr->excess -= incre;
    444 nptr->excess += incre;
    445 eptr->rcap = 0.0;
    446
    447 if ( nptr->excess <= incre + EPS )
    448 {
    449 /* push nptr onto active stack */
    450 nptr->stack_link = active[nptr->dist];
    451 active[nptr->dist] = nptr;
    452 }
    453
    454 if ( aptr->excess <= EPS )
    455 {
    456 assert(eptr->cap > EPS);
    457 aptr->scan_ptr = eptr;
    458
    459 goto node_ready;
    460 }
    461 }
    462 }
    463
    464 /* go to the next non-zero edge */
    465 do
    466 {
    467 eptr = eptr->next;
    468 }
    469 while( eptr != NULL && eptr->cap <= EPS );
    470
    471 if ( eptr == NULL )
    472 {
    473 /* all incident arcs scanned, but node still has positive excess, check if for all nptr
    474 * nptr->dist != aptr->dist */
    475 if ( number[aptr->dist] == 1L )
    476 {
    477 /* put all nodes v with dist[v] >= dist[a] into the set of "dead" nodes since they are disconnected from
    478 * the sink */
    479 for ( nptr = &(gr->nodes[n-1L]); nptr >= gr->nodes; nptr-- )
    480 {
    481 if ( nptr->alive && nptr->dist > aptr->dist )
    482 {
    483 --number[nptr->dist];
    484 active[nptr->dist] = NULL;
    485 nptr->alive = FALSE;
    486 nptr->dist = (int) n;
    487 --bound;
    488 }
    489 }
    490 --number[aptr->dist];
    491 active[aptr->dist] = NULL;
    492 aptr->alive = FALSE;
    493 aptr->dist = (int) n;
    494 --bound;
    495
    496 goto node_ready;
    497 }
    498 else
    499 {
    500 /* determine new label value */
    501 dmin = n;
    502 aptr->scan_ptr = NULL;
    503 eptr = aptr->first_edge;
    504 while ( eptr != NULL )
    505 {
    506 assert(eptr->rcap <= EPS || eptr->cap > EPS);
    507 if ( eptr->adjac->dist < dmin && eptr->rcap > EPS )
    508 {
    509 assert(eptr->cap > EPS);
    510 dmin = eptr->adjac->dist;
    511 if ( aptr->scan_ptr == NULL )
    512 aptr->scan_ptr = eptr;
    513 }
    514 eptr = eptr->next;
    515 }
    516
    517 if ( ++dmin < bound )
    518 {
    519 /* ordinary relabel operation */
    520 --number[aptr->dist];
    521 aptr->dist = (int) dmin;
    522 ++number[dmin];
    523 max_dist = dmin;
    524 eptr = aptr->scan_ptr;
    525 assert(eptr != NULL);
    526 assert(eptr->cap > EPS);
    527
    528 goto edge_scan;
    529 }
    530 else
    531 {
    532 aptr->alive = FALSE;
    533 --number[aptr->dist];
    534 aptr->dist = (int) n;
    535 --bound;
    536
    537 goto node_ready;
    538 }
    539 }
    540 }
    541 else
    542 goto edge_scan;
    543
    544 node_ready:
    545 ++n_discharge;
    546 if ( n_discharge == n )
    547 {
    548 n_discharge = 0L;
    549 global_relabel (gr, t_ptr);
    550 }
    551 aptr = active[max_dist];
    552 }
    553 --max_dist;
    554 }
    555 while ( max_dist > 0L );
    556
    557 return (int) (t_ptr->excess - 1.0L);
    558}
    559
    560/** determine whether node i is on the path to the root starting from j */
    561static
    563 GRAPH* gr, /**< graph */
    564 int i, /**< node to search for */
    565 int j /**< starting node */
    566 )
    567{
    568 while( i != j && j != 0 )
    569 {
    570 j = gr->nodes[j].parent->id;
    571 }
    572
    573 if( i == j )
    574 return TRUE;
    575 else
    576 return FALSE;
    577}
    578
    579/** constructs a list of cuts for a TSP relaxation polytope from a Gomory Hu Tree
    580 *
    581 * If the non-zero-edges of a TSP relaxation induce a non-connected graph, an according cut is generated, using
    582 * information from BFS in method maxflow.
    583 */
    584static
    586 GRAPH* gr, /**< graph */
    587 SCIP_Bool** cuts, /**< array of arrays to store cuts */
    588 int* ncuts, /**< pointer to store number of cuts */
    589 double minviol /**< minimal violation of a cut to be returned */
    590 )
    591{
    592 int k = 0;
    593
    594 for( int i = 1; i < gr->nnodes; i++ )
    595 {
    596 if( gr->nodes[i].mincap < 2.0 - minviol )
    597 {
    598 cuts[k][0] = FALSE;
    599 for( int j = 1 ; j < gr->nnodes; j++ )
    600 cuts[k][j] = nodeOnRootPath(gr, i, j);
    601 k++;
    602 }
    603 }
    604 *ncuts = k;
    605}
    606
    607/** construct a single cut */
    608static
    610 GRAPH* gr, /**< graph */
    611 SCIP_Bool** cuts /**< array of arrays to store cuts */
    612 )
    613{
    614 cuts[0][0] = FALSE;
    615 for( int i = 1; i < gr->nnodes; i++ )
    616 cuts[0][i]=gr->nodes[i].unmarked;
    617}
    618
    619/** Determines Gomory/Hu cut tree for input graph with capacitated edges
    620 *
    621 * The tree structures is represented by parent pointers which are part of the node structure, the capacity of a tree
    622 * edge is stored at the child node, the root of the cut tree is the first node in the list of graph nodes
    623 * (&gr->nodes[0]). The implementation is described in [1].
    624 *
    625 * References:
    626 * 1) D. Gusfield: "Very Simple Algorithms and Programs for All Pairs Network Flow Analysis",
    627 * Computer Science Division, University of California, Davis, 1987.
    628 *
    629 * 2) R.E. Gomory and T.C. Hu: "Multi-Terminal Network Flows",
    630 * SIAM J. Applied Math. 9 (1961), 551-570.
    631 */
    633 GRAPH* gr, /**< graph */
    634 SCIP_Bool** cuts, /**< array of arrays to store cuts */
    635 int* ncuts, /**< pointer to store number of cuts */
    636 double minviol /**< minimal violation of a cut to be returned */
    637 )
    638{
    639 GRAPHNODE* nptr;
    640 GRAPHNODE* nptr1;
    641 GRAPHNODE* nptrn;
    642 GRAPHNODE* sptr;
    643 GRAPHNODE* tptr;
    644 double maxfl;
    645 long n;
    646
    647 n = gr->nnodes;
    648
    649 if ( ! init_maxflow(n) )
    650 return FALSE;
    651
    652 nptr1 = gr->nodes;
    653 nptrn = &(gr->nodes[n-1L]);
    654 for ( nptr = nptrn; nptr >= nptr1; nptr-- )
    655 nptr->parent = nptr1;
    656
    657 for ( sptr = &(gr->nodes[1L]); sptr <= nptrn; sptr++ )
    658 {
    659 tptr = sptr->parent;
    660 maxfl = maxflow(gr, sptr, tptr);
    661
    662 /* maxfl < 0 <=> graph is not connected => generate cut */
    663 if ( maxfl < 0L )
    664 {
    665 constructSingleCut(gr,cuts);
    666 *ncuts = 1;
    667 fini_maxflow();
    668 return TRUE;
    669 }
    670
    671 sptr->mincap = maxfl;
    672 for ( nptr = &(gr->nodes[1L]); nptr <= nptrn; nptr++ )
    673 {
    674 if ( nptr != sptr && ! nptr->alive && nptr->parent == tptr )
    675 nptr->parent = sptr;
    676 }
    677
    678 if ( ! tptr->parent->alive )
    679 {
    680 sptr->parent = tptr->parent;
    681 tptr->parent = sptr;
    682 sptr->mincap = tptr->mincap;
    683 tptr->mincap = maxfl;
    684 }
    685 }
    686 fini_maxflow();
    687 constructCutList(gr, cuts, ncuts, minviol);
    688
    689 return TRUE;
    690}
    static long bound
    static void constructSingleCut(GRAPH *gr, SCIP_Bool **cuts)
    static void global_relabel(GRAPH *gr, GRAPHNODE *tptr)
    void capture_graph(GRAPH *gr)
    static SCIP_Bool nodeOnRootPath(GRAPH *gr, int i, int j)
    SCIP_Bool create_graph(int n, int m, GRAPH **gr)
    static GRAPHNODE ** active
    static double maxflow(GRAPH *gr, GRAPHNODE *s_ptr, GRAPHNODE *t_ptr)
    static long * number
    static void fini_maxflow(void)
    #define EPS
    static void constructCutList(GRAPH *gr, SCIP_Bool **cuts, int *ncuts, double minviol)
    static SCIP_Bool co_check
    SCIP_Bool ghc_tree(GRAPH *gr, SCIP_Bool **cuts, int *ncuts, double minviol)
    void release_graph(GRAPH **gr)
    static SCIP_Bool init_maxflow(long n)
    static long max_dist
    static void free_graph(GRAPH **gr)
    generator for global cuts in undirected graphs
    #define NULL
    Definition: def.h:248
    #define SCIP_Bool
    Definition: def.h:91
    #define TRUE
    Definition: def.h:93
    #define FALSE
    Definition: def.h:94
    #define BMSfreeMemory(ptr)
    Definition: memory.h:145
    #define BMSallocMemoryArray(ptr, num)
    Definition: memory.h:123
    #define BMSfreeMemoryArray(ptr)
    Definition: memory.h:147
    #define BMSallocMemory(ptr)
    Definition: memory.h:118
    C++ wrapper classes for SCIP.
    struct GraphEdge * back
    Definition: GomoryHuTree.h:70
    double rcap
    Definition: GomoryHuTree.h:66
    double cap
    Definition: GomoryHuTree.h:65
    GRAPHNODE * adjac
    Definition: GomoryHuTree.h:72
    struct GraphEdge * next
    Definition: GomoryHuTree.h:69
    struct GraphEdge * first_edge
    Definition: GomoryHuTree.h:53
    struct GraphNode * bfs_link
    Definition: GomoryHuTree.h:56
    double mincap
    Definition: GomoryHuTree.h:48
    SCIP_Bool unmarked
    Definition: GomoryHuTree.h:50
    SCIP_Bool alive
    Definition: GomoryHuTree.h:51
    double excess
    Definition: GomoryHuTree.h:47
    struct GraphEdge * scan_ptr
    Definition: GomoryHuTree.h:54
    struct GraphNode * parent
    Definition: GomoryHuTree.h:58
    struct GraphNode * stack_link
    Definition: GomoryHuTree.h:57
    GRAPHNODE * nodes
    Definition: GomoryHuTree.h:86
    int nedges
    Definition: GomoryHuTree.h:83
    int nedgesnonzero
    Definition: GomoryHuTree.h:84
    int nnodes
    Definition: GomoryHuTree.h:82
    int nuses
    Definition: GomoryHuTree.h:81
    GRAPHEDGE * edges
    Definition: GomoryHuTree.h:87