\datethis
@i gb_types.w
\def\adj{\mathrel{\!\mathrel-\mkern-8mu\mathrel-\mkern-8mu\mathrel-\!}}
@*Intro. I'm implementing a pretty algorithm due to
Hagauer, Imrich, and Klav\v{z}ar
[{\sl Theoretical Computer Science\/ \bf215} (1999), 123--136], which
embeds a median graph in a hypercube.
I don't have time to implement the more complex algorithm by which
they test a given graph for medianhood. In order to obtain the
time bounds they claim, rather elaborate data structures are needed,
and from a practical standpoint there is no gain until the
graphs are pretty huge.
(But from a theoretical standpoint, their asymptotic running time of only
$O(n^{3/2}\log n)$ for the more difficult problem is quite startling.
They probably wouldn't have discovered the $O(m\log n)$ embedding
algorithm described here if they hadn't been stimulated by
that much more challenging task.)
For convenience, I assume that the hypercube has dimension at most~32,
so that each vertex is represented by a bitstring that fits in a
single computer word.
The actual dimension of the hypercube is the number of equivalence
classes of edges, where the equivalence relation is generated by
saying that edge $w\adj x$ is equivalent to edge $y\adj z$
whenever $x\adj y$ and $z\adj w$. (In other words, the ``opposite''
edges of every 4-cycle $w\adj x\adj y\adj z\adj w$ are equivalent.)
Caution: This program may fail disastrously if the input graph isn't a
median graph. No attempt is made to accommodate non-median inputs
in any graceful way.
@d start_vertex g->vertices
@d nmax 100 /* at most this many vertices */
@d cmax 32 /* at most this many edge classes */
@c
#include "gb_graph.h"
#include "gb_save.h"
@h
Graph *g; /* the GraphBase graph we're given */
@@;
@@;
main(int argc,char *argv[])
{
register int j,k;
register Vertex *u,*v,*w;
register Arc *a, *b, *c;
if (argc<2) {
fprintf(stderr,"Usage: %s foo.gb [verbose]\n",argv[0]);
exit(-1);
}
verbose=argc-2; /* extra info will be printed if desired */
g=restore_graph(argv[1]);
if (!g) {
fprintf(stderr,"Sorry, I couldn't input the graph %s!\n",argv[1]);
exit(-2);
}
if (g->n>nmax) {
fprintf(stderr,
"Sorry, the graph has more vertices (%d) than I can handle (%d)!\n",@|
g->n,nmax);
exit(-3);
}
@;
embed(start_vertex); /* launch the recursion */
@;
}
@* Data structures. One part of the algorithm will behoove us
to find the arc from
vertex~$u$ to vertex~$v$, given $u$ and $v$. For this random-access
task, it's easiest
to allocate an $n\times n$ array |adjmat|, with an entry in row~|u|
and column~|v| that points to the arc in question.
@d the_arc(u,v) adjmat[u-g->vertices][v-g->vertices]
@=
Arc *adjmat[nmax][nmax]; /* adjacency matrix */
@ We want the adjacency lists to be doubly linked, so that
it will be easy to remove an arbitrary edge. The doubly linked adjacency
lists in this program have a header node |b=v->arcs| for every vertex,
recognizable by the fact that |b->tip=NULL|. Every arc node |a| in
an adjacency list will have |a->next->prev=a->prev->next=a|.
@d prev a.A /* utility field |a| in an |Arc| node is the inverse of |next| */
@=
for (u=g->vertices;uvertices+g->n;u++) {
b=gb_virgin_arc(), b->tip=NULL; /* create the header */
for (a=u->arcs, u->arcs=b, b->next=a; a; b=a,a=b->next)
the_arc(u,a->tip)=a, a->prev=b;
b->next=u->arcs, u->arcs->prev=b; /* complete the cycle */
}
@ We also want to compute the distances $d(v)$ from |start_vertex| to
each vertex~$v$, representing $d(v)$ in memory as |v->dist|.
The graph is bipartite, so we know that $d(u)-d(v)=\pm1$ whenever $u\adj v$.
We will order the adjacency list of $u$ so that all of its neighbors~$v$ with
$d(v)d(u)$. The former~$v$ are
called ``early neighbors'' and the latter are ``late neighbors.''
@^{early neighbors}@>
@^{late neighbors}@>
(I originally called them ``down-neighbors'' and ``up-neighbors''.
But that terminology was confusing, because an
``up-neighbor'' actually appears {\it lower\/} in the breadth-first
search tree rooted at |start_vertex|, when we follow the typical
computer-science convention of putting the root at the top.)
@d dist z.I
/* distance from |start_vertex| to |v| is stored in utility field~$z$ */
@d link y.V /* utility field $y$ holds a pointer */
@=
for (u=g->vertices;uvertices+g->n;u++) u->dist=-1, u->link=NULL;
start_vertex->dist=0;
for (u=v=start_vertex;u;u=u->link) { /* ye olde breadth-first search */
for (b=u->arcs,a=b->next;a->tip;a=a->next)
if (a->tip->dist<0) a->tip->dist=u->dist+1, v->link=a->tip, v=a->tip;
else if (a->tip->distdist) { /* |a->tip| is a early neighbor */
c=a->prev;
if (c!=b) {
c->next=a->next, c->next->prev=c; /* move |a| to the beginning */
a->next=b->next, a->next->prev=a;
b->next=a, a->prev=b;
a=c;
}
}
}
@ We use a typical union-find algorithm to handle the equivalence
classes: Each edge is part of a circular list containing all edges
that are currently in its class. Each edge also points to the
``leader'' of the class, and the leader knows the class size.
When two classes merge, the leader of the smaller class resigns her
post and defers to the leader of the larger class.
Classes eventually acquire a serial number from 1 to~$c$, where $c$ is
the final number of classes. This serial number is acquired only when
we are sure that the class won't be merged with another whose serial
number was already assigned.
Utility fields in |Arc| nodes, called |elink|, |leader|, |size|, and |serial|,
handle these aspects of the data structure. Since each edge $u\adj v$
appears in memory as two consecutive |Arc| nodes,
one for $u->v$ and another for $v->u$, we
store |leader| and |size| in the smaller of these nodes,
|elink| and |serial| in the larger.
@d edge_trick (sizeof(Arc)&-sizeof(Arc)) /* improve the old |edge_trick| */
@d lower(a) (edge_trick&(unsigned long)(a)? (a)-1: (a))
@d upper(a) (edge_trick&(unsigned long)(a)? (a): (a)+1)
@d other(a) (edge_trick&(unsigned long)(a)? (a)-1: (a)+1)
@d elink(a) upper(a)->b.A /* pointer to an equivalent edge */
@d leader(a) lower(a)->b.A /* pointer to the class representative */
@d size(a) lower(a)->len /* size of this equivalence class */
@d serial(a) upper(a)->len /* serial number of this equivalence class */
@=
int classes; /* the number of equivalence classes with assigned numbers */
@ Here's a routine that might help when debugging.
@=
void print_edge(Arc *e)
{
register Arc *f;
if (!e->tip) printf("(header)");
else {
f=leader(e);
printf("Edge %s -- %s",e->tip->name,other(e)->tip->name);
if (serial(f)) printf(", class %d",serial(f));
else printf(" (0x%x)",(unsigned long)f);
printf(", size %d\n",size(f));
}
}
@ The serial number is stored only with the leader.
@=
void serialize(Arc *e) /* assign a serial number */
{
e=leader(e);
if (serial(e)==0) {
serial(e)=++classes;
if (classes>cmax) {
fprintf(stderr,"Overflow: more than %d classes!\n",cmax);
exit(-5);
}
}
}
@ @=
void unionize(Arc *e, Arc *f) /* merge two classes */
{
register Arc *g;
e=leader(e), f=leader(f);
if (e!=f) {
if (serial(e) && serial(f) && serial(e)!=serial(f)) {
fprintf(stderr,"I goofed (merging two serialize classes)!\n");
exit(-69);
}
if (size(e)>size(f)) g=e,e=f,f=g; /* make |e| the smaller class */
leader(e)=f, size(f)+=size(e);
if (serial(e)) serial(f)=serial(e);
for (g=elink(e);g!=e;g=elink(g)) leader(g)=f;
g=elink(e), elink(e)=elink(f), elink(f)=g;
}
}
@ @=
for (u=g->vertices;uvertices+g->n;u++)
for (a=u->arcs->next; a->tip; a=a->next)
if (a==lower(a)) elink(a)=leader(a)=a, size(a)=1, serial(a)=0;
@ A few more data structures will be introduced after we get
inside the main algorithm itself, but we have now discussed all
of the necessary preprocessing.
@=
@;
@;
@;
@*The main algorithm. The heart of this program is a recursive procedure
called |embed(r)|. Vertex~|r| is the ``root'' of all
vertices currently reachable from~it; in fact,
|r|~lies on a shortest path from every such vertex
to the original |start_vertex|, in the original graph. Edges have gradually
been pruned away, cutting |r| off from those closer to the start.
Procedure |embed(r)| runs through all of |r|'s neighbors~|s|,
making additional cuts so that each |s| will play the role of root
in its own (smaller and smaller) subgraph.
@=
void embed(Vertex *r)
{
register Vertex *s, *u, *v, *w, *vv;
register Arc *aa, *a, *b;
if (verbose) printf("Beginning to embed subgraph %s:\n",r->name);
for (aa=r->arcs->next;(s=aa->tip)!=NULL;aa=aa->next) {
@;
@;
embed(s);
serialize(aa);
if (verbose)
printf(" Edge %s -- %s is in class %d\n",
r->name,s->name,serial(leader(aa)));
}
if (verbose) printf("Done with %s.\n",r->name);
}
@ The edges $r\adj s$ reflected in successive calls of |embed|
form a spanning tree of the original graph. In other words, every
vertex |s| (except for |start_vertex|) is chosen exactly once,
by its unique parent~|r|.
The final embedding code for |s| will be the code for~|r| plus a bit in the
position corresponding to the serial number of the edge $r\adj s$.
Serial numbers are assigned bottom-up to spanning tree edges,
but our definition of embedding codes is top-down. So we record the
edges in two tables |rtab| and |stab|; the actual codes will be assigned
after all embedding has been completed.
@d code y.I /* we no longer need the |link| field when |code| is used */
@=
Vertex *rtab[nmax], *stab[nmax]; /* edges of the spanning tree */
int tabptr; /* number of spanning edges recorded so far */
@ @=
rtab[tabptr]=r, stab[tabptr]=s, tabptr++;
@ @=
printf("The codewords are:\n %s = 00000000\n",start_vertex->name);
start_vertex->code=0x0;
for (k=0;kcode&j) {
fprintf(stderr,"I goofed (class %d used twice)!\n",serial(leader(a)));
exit(-8);
}
v->code=u->code|j;
printf(" %s = %08x\n",v->name,v->code);
}
@ Now we get to real meat. Vertices of the current graph can be partitioned
into {\it left vertices}, which are closer to $r$ than to~$s$, and
{\it right vertices}, which are closer to~$s$ than to~$r$. Each right
vertex~$v$ has a {\it rank}, which is the shortest distance from~$v$ to
a left vertex. Similarly, each left vertex~$u$ has rank $1-d$, where
$d$ is the shortest distance from $u$ to a right vertex. Thus, $u$ has
rank zero if it is adjacent to a right vertex, otherwise its rank is
negative. We will be particularly interested in vertices of rank 0, 1,
or~2. Clearly $r$ has rank~0 and $s$~has rank~1.
A median graph has the property that its vertices of rank~1 form a
convex set. In other words, all shortest paths between two such
vertices lie entirely within the set.
% Similarly, the set of all rank-0 vertices is convex.
Furthermore every vertex $v$ of rank~1 is adjacent to exactly one
vertex~$u$ of rank~0, which we will represent by |v->mate|.
Whenever $v\adj v'$ is an edge between vertices of rank~1, there's a
corresponding edge $u\adj u'$ between their mates. Therefore
every path $v=v_0\adj v_1\adj\cdots\adj v_k=s$ involving vertices
of rank~1 corresponds to a path $u=u_0\adj u_1\adj\cdots\adj u_k=r$
involving vertices of rank~0. The edges $u_j\adj v_j$ are
equivalent to $r\adj s$, by our definition of equivalence, because
of the 4-cycles $u_j\adj u_{j\pm1}\adj v_{j\pm1}\adj v_j\adj u_j$;
so they are among the edges we will be
removing from the graph in this part of the algorithm.
The codeword for~$v_j$ will be the same as the codeword for~$u_j$,
except for a~1 in the component that corresponds to the serial
number of class $r\adj s$.
Hagauer, Imrich, and Klav\v{z}ar noticed that a simple breath-first
search will suffice to identify all vertices of ranks 0, 1, and~2,
because of the special structure of median graphs. For example, if
$v$ has rank~1, its ``early neighbors'' must have rank 0 or~1, by
convexity. And its ``late neighbors'' must have rank 1 or~2.
To implement their method, I'll form a sequential list of all
vertices that have rank~1, starting at~$s$ and following the
|link| fields. I'll set |v->mark=s| whenever the rank of~$v$ is
1 or~2, and in that case I'll also assign the appropriate
value to |v->rank|. (This marking technique ensures that
previous |rank| values needn't be cleared.)
Breadth-first search in the following program is implemented as
a queue that runs from |v| to |vv|, via |link| fields.
@d mate x.V /* the rank-0 mate of a rank-1 vertex */
@d mark w.V /* a specific vertex or a special Boolean code */
@d rank v.I /* 1 or 2 */
@=
s->mark=s, s->rank=1, s->link=NULL;
for (v=vv=s; v; v=v->link) {
@;
for (a=v->arcs->next; a->tip; a=a->next) {
u=a->tip;
if (u->dist>v->dist) @@;
else @;
}
}
@ In this step, vertex |v| has rank 1, so it
should have exactly one early neighbor~|u| that does not
have rank~1; this vertex will be |v->mate|.
Since early neighbors precede
late neighbors in an adjacency list, we'll find |u| quickly.
When we do, we want to delete the edge $v\adj u$ and make it equivalent to
edge $r\adj s$.
@=
for (a=v->arcs->next;;a=a->next) {
u=a->tip;
if (!(u->mark==s && u->rank==1)) break;
}
v->mate=u;
a->next->prev=a->prev, a->prev->next=a->next; /* delete |a| */
b=other(a); /* |other(a)=the_arc(u,v)| */
b->next->prev=b->prev, b->prev->next=b->next; /* delete |a|'s inverse */
unionize(a,aa);
@ In this step, |u| is a late neighbor of~|v|, and |v| has
rank~1. Vertex~|u| itself has at least one early neighbor,
namely~|v|. If |u| has rank~1, we know that it will also have a (unique)
early neighbor of rank~0.
Suppose |w| is an early neighbor and |w!=v|. Then |w| has distance~2
from~|v|, and the median~|x| of $\{s,v,w\}$ will have rank~1
by convexity. Several cases arise: If |w| has rank~0, then |x=v|;
otherwise we will have classified~|w| properly when processing~|x|.
It follows that vertex~|u| has rank~2 if and only if
vertex~|w| is known to have rank~2.
@=
{
if (u->mark==s && u->rank==1) continue; /* rank 1 is correct */
u->mark=s, u->rank=2; /* tentatively assign rank 2 */
b=u->arcs->next, w=b->tip;
if (w==v) {
w=b->next->tip;
if (w==NULL || w->dist>u->dist) continue; /* rank 2 is correct */
}
if (!(w->mark==s && w->rank==2))
u->rank=1, u->link=0, vv->link=u, vv=u; /* enqueue |u| */
}
@ In this step, |u| is an early neighbor of~|v|, and |v| has
rank~1. If |u|~also has rank~1, the edge $v\adj u$ is equivalent
to the edge from |v->mate| to |u->mate|, so we need to record
that fact.
@=
if (u->mark==s && u->rank==1)
unionize(a,the_arc(v->mate,u->mate));
@*Index.