10

There are some algorithms, like Edmond's Algorithm, or Boruvka's Algorithm which require the programmer to create a graph which is obtained by contraction of some nodes into a single node, and later expanding it back.

A formal description of contraction is as follows:

Let G be a graph with vertices V and edges E. Let C be a connected component of G. A contraction of G with respect to C is defined as the graph on V - nodes(C) + C*, where C* is a "supernode" representing the contracted component. The edges which do not involve vertices in C are as is. The edges which had an endpoint in C are now connected to C*.

For example, an intermediate step in Edmond's Algorithm might require contracting a cycle, operating on the contracted graph, and then expanding it back again

It is not clear to me how to implement such algorithmic primitives using representations like adjacency lists.

What would be an elegant and efficient way to represent graphs so that they can be contracted, while remembering the relevant data for expanding them?

  • Can a "contracted" node then be part of a yet-larger contracted node? That is, can "supernodes" be nested? – Eric Lippert Apr 01 '18 at 11:53
  • @EricLippert Yes. Such a construct might be necessary in implementing the Edmond's Algorithm, in which there might be multiple recursive calls. – Agnishom Chattopadhyay Apr 01 '18 at 12:55
  • 1
    Usually there's a very simple way that depends on the other details of the implementation, i.e., the best way is not usually a generic approach that applies generally to most algorithms or most implementations of them. – Matt Timmermans Apr 01 '18 at 14:39
  • For the general case, the closest thing that comes to mind are data structures for connectivity queries in dynamic graphs (https://en.wikipedia.org/wiki/Dynamic_connectivity#General_graphs_2). Wikipedia says dynamic connectivity queries (with edge deletions and insertions in undirected graphs) can be supported in poly-log time per operation. – Neal Young Apr 01 '18 at 18:26

2 Answers2

4

I would use disjoint-set data structure also called a union–find data structure . Imagine each vertex as a set initially. Now the working goes like this:

For contraction: Take the union of all the vertices participating in all the contraction. All the vertices in a set are represented by single vertex called parent of all vertices, which you can call your supernode. The link has all the details of how to do that.

For expansion just do the reverse, in the worst case you would have to make each vertex represent a single set. So basically this approach works for non-overlapping set operations.

Sumeet
  • 8,086
  • 3
  • 25
  • 45
  • In the union-find data structures that I know, Union is not easily invertible. Find operations modify the data structure (e.g. along the path traced for the find, the find changes each pointer to point to the current root.) This is necessary for efficiency. Given that Find operations modify the data structure and lose information, how do you have in mind doing expansion efficiently? – Neal Young Apr 01 '18 at 18:05
  • @NealYoung What you are talking about is path compression heuristics? However, I on the other hand just pointed in the direction of feasible data structures that can be used. But even the literature does not speak of inversion and therefore,I have left it to the OP for the implementation details. – Sumeet Apr 01 '18 at 18:12
  • Yep, path compression, as required by the data structure described in your wikipedia link.. Does anybody know the implementation details that you have left to OP? Not as far as I know. – Neal Young Apr 01 '18 at 18:29
  • @NealYoung When you are actually working with sets, you do work with find often enough to reach optimization but here you will either contract or expand. Like I said i have only pointed in a direction given the input. – Sumeet Apr 01 '18 at 18:39
3

First of all, I like the idea of Sumeet Singh's answer, and you might explore that first. I have a similar idea but the details are slightly different.

Unfortunately I'm not at a place right now where I can draw a diagram, which would really help here. Let me try to describe what I have in mind clearly.

The solution involves creating two new kinds of node:

  • a "supernode" represents a contracted set
  • a "forwarding node" is a proxy for a supernode that knows how to "undo" its creation.
  • A forwarding node has three references: to its supernode, to its "exterior" node, and to its "interior" node.
  • A supernode has a list of all its forwarding nodes.

Contraction:

Consider your connected component G.

  • Create a "supernode" that represents this connected component.

  • For each node in G, there might be edges that are connected to a node not in G. Call those edges e1, e2, e3, ...

  • Create a forwarding node F1, F2, F3... for each of those edges.

  • Now for each of those edges, suppose e1 is from A1 (not in G) to B1 (in G). Remove the A1-B1 edge from the graph, add the A1-F1 edge. A1 becomes the exterior node of F1, and B1 becomes the interior node of F1.

Expansion is just the reverse:

  • For each forwarding node F in the supernode, remove the edge from the exterior node, add the edge from the exterior node to the interior node back, and delete all the forwarding nodes.

  • Delete the supernode


The tricky bit will come in implementing the graph operations. If you ask "what are your neighbours" of a forwarding node, it has to forward that request to the supernode, and the supernode has to say "all the exterior nodes of all my forwarding nodes". And so on.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I wonder if it would be simpler to have the forwarding nodes internal to the supernode rather than sitting around it. That way all the other nodes would see themselves as being connected to the supernode and you no longer have the operation forwarding issue. You still need the forwarding nodes to connect the internal node to the external edges but they only need to come into play if you are expanding the supernode or transitioning from internal to external (Or the reverse). – Wearwolf Apr 03 '18 at 18:42
  • @Wearwolf: Sure, there are lots of ways to solve this problem. But the crux is: (1) somewhere there is a data structure that knows what the links used to be, and can restore them, and (2) whatever that thing is looks like a vertex from the point of view of the incoming edges. – Eric Lippert Apr 03 '18 at 19:10