\datethis @i gb_types.w @s delete ident @*Intro. This program was written (somewhat hastily) in order to experiment with an algorithm that generates all spanning trees of a given graph, changing only one edge at a time. Most of the basic ideas are adapted from Malcolm Smith's M.S. thesis, ``Generating spanning trees'' (University of Victoria, 1997), which also contains more complex variations that guarantee better asymptotic performance. I intend to experiment with those additional bells and whistles later. The first command line argument is the name of a file that specifies an undirected graph in Stanford GraphBase {\mc SAVE\_GRAPH} format; the graph may have repeated edges, but it must not contain loops. Additional command line arguments are ignored except that they cause more verbose output. The least verbose output contains only overall statistics about the total number of spanning trees found and the total number of mems used. @d verbose (argc>2) @d extraverbose (argc>3) @d o mems++ @d oo mems+=2 @d ooo mems+=3 @d oooo mems+=4 @d ooooo mems+=5 @c #include "gb_graph.h" #include "gb_save.h" @h double mems; /* memory references made */ double count; /* trees found */ @@; main(int argc, char *argv[]) { @; @; @; @; printf("Altogether %.15g spanning trees, using %.15g mems.\n",count,mems); exit(0); } @ @= if (argc<2) { fprintf(stderr,"Usage: %s foo.gb [[gory] details]\n",argv[0]); exit(1); } g=restore_graph(argv[1]); if (!g) { fprintf(stderr, "Sorry, can't create the graph from file %s! (error code %d)\n", argv[1],panic_code); exit(-1); } n=g->n; @; @ @= register Graph *g; /* the graph we're dealing with */ register int n; /* the number of vertices */ register int k; /* current integer of interest */ register Vertex *u,*v, *w; /* current vertices of interest */ register Arc *e,*ee,*f,*ff; /* current edges of interest */ @* Graph preparation. While we're checking to see that the graph meets certain minimal standards, we might as well also compute the degree of each vertex, since our algorithm will be using that information. We also ensure that the SGB ``edge trick'' works on our computer. In this program we deviate from normal conventions of the Stanford GraphBase by using a {\it doubly\/} linked list of arcs from each vertex |v|. Namely, |v->arcs| points to a header node |h|, and the arcs from |v| are |h->next|, |h->next->next|, etc., until returning to |h| again. All arc nodes |e| in this list have |e->next->prev=e->prev->next=e|. The header node is distinguished by the property |h->tip=NULL|. The ``length'' of each edge is changed to an identifying number for use in printouts. @d deg u.I /* utility field |u| of each vertex holds its current degree */ @d prev a.A /* utility field |a| of each arc holds its backpointer */ @d mate(e) (edge_trick & (siz_t) (e)? (e)-1: (e)+1) @= if (verbose) printf("Graph %s has the following edges:\n",g->id); for (v=g->vertices,k=0;vvertices+n;v++) { f=gb_virgin_arc(); f->next=v->arcs; /* the new header node */ for (v->deg=0,e=v->arcs,v->arcs=f;e;v->deg++,f=e,e=e->next) { e->prev=f; u=e->tip; if (u==v) { fprintf(stderr,"Oops, there's a loop from %s to itself!\n",v->name); exit(-3); } if (mate(e)->tip!=v) { fprintf(stderr,"Oops: There's an arc from %s to %s,\n",u->name,v->name); fprintf(stderr," but the edge trick doesn't find the opposite arc!\n"); exit(-4); } if (u>v) { e->len=mate(e)->len=++k; if (verbose) printf(" %d: %s -- %s\n",k,v->name,u->name); } } v->arcs->prev=f, f->next=v->arcs; /* complete the double linking */ if (v->deg==0) { fprintf(stderr,"Graph %s has an isolated vertex %s!\n", g->id,v->name); exit(-5); } } @ Here's something I might like to use when debugging. @= void print_arcs(Vertex *v) { register Arc *a; printf("Arcs leading from %s:\n",v->name); for (a=v->arcs->next;a->tip;a=a->next) printf(" %d (to %s)\n",a->len,a->tip->name); } @*The method. Let $G$ be a graph with $n>1$ vertices. The basic idea of Smith's algorithm is to generate all spanning trees of~$G$ in such a way that the first one includes a given {\it near-tree}, namely a set of $n-2$ edges that don't contain a cycle. This task is easy if $n=2$: We simply list all the edges. If $n>2$ and if the near-tree is $\{e_1,\ldots,e_{n-2}\}$, we proceed as follows: First form the graph $G\cdot e_1$ by shrinking edge~$e_1$ (making its endpoints identical). All spanning trees of~$G$ that include~$e_1$ are obtained by appending $e_1$ to a spanning tree of $G\cdot e_1$; so we proceed recursively to generate all spanning trees of $G\cdot e_1$, beginning with the near-tree $\{e_2,\ldots,e_{n-2}\}$. If no such trees exist, we stop; in this case $G\cdot e_1$ is not connected, so $G$ is not connected and it has no spanning trees. Otherwise, suppose the last spanning tree found for $G\cdot e_1$ is $f_1\ldots f_{n-2}$. Then we complete our task by deleting edge $e_1$ and generating all spanning trees in the resulting graph $G\setminus e_1$, starting with the near-tree $\{f_1,\ldots,f_{n-2}\}$. @ This program implements the recursion directly by maintaining an array of edges $a_1\ldots a_n$. When we enter level~$l$, positions $a_1\ldots a_{l-1}$ contain edges to include in the tree, and those edges have been shrunk in the current graph. Position~$a_l$ is effectively blank, and the remaining positions $a_{l+1}\ldots a_{n-1}$ contain the edges of a near-tree that should be part of the next spanning tree generated. We don't delete an edge that is a ``bridge,'' whose removal would disconnect the current graph. When a non-bridge edge |e| is deleted at level~|l|, we set |change_e=e|. If the previously found spanning tree was $a_1\ldots a_{n-1}$, the next tree found will be $a_1\ldots a_{l-1}a_{l+1}\ldots a_{n-1}e'$ for some new edge~$e'$; thus it will differ from its predecessor by removing edge |change_e| and replacing it with~$e'$. It's convenient to keep array element $a_l$ in a utility field within the vertex array, represented by |aa(l)|. Another such utility field, |del(l)|, points to a stack of the edges deleted before coming to a bridge; edges on this list are linked together via a |link| field. Experienced readers will not be shocked by the fact that this part of the program has a |goto| leading from one loop into another. @d aa(l) (g->vertices+l)->z.A /* the edge $a_l$ */ @d del(l) (g->vertices+l)->y.A /* the most recent edge deleted on level |l| */ @d link b.A /* points from one edge to another */ @= change_e=NULL; v=g->vertices; /* this instruction needed only if $n=2$ */ for (l=1;ltip, v=mate(e)->tip; if (oo,u->deg>v->deg) v=u, e=mate(e), u=e->tip; @; o,aa(l)=e; } for (o,e=v->arcs->next;o,e->tip;o,e=e->next) { o,aa(l)=e; @; change_e=e; } for (l--;l;l--) { e=aa(l), u=e->tip, v=mate(e)->tip; @; @; @; } @ @= register int l; /* the current level */ Arc *change_e; /* edge that may change next */ @ @= count++; if (verbose) { if (!change_e || extraverbose) { printf("%.15g:",count); for (k=1;klen); if (extraverbose && change_e) printf(" (-%d+%d)\n",change_e->len,e->len); else printf("\n"); }@+else printf("%.15g: -%d+%d\n",count,change_e->len,e->len); } @ To shrink an edge between |u| and |v|, we insert |u|'s adjacency list into |v|'s, changing all references to |u| into references to~|v|; those references occur in |tip| fields of mates of the arcs in |u|'s list. We also delete all former edges between |u| and |v|, since those would otherwise become loops. Those former edges are linked together via their |link| fields, so that we can restore them later. Note that |e->tip=u|, so |e| appears in the |v| list while |mate(e)| appears in the |u| list. @d delete(e) ee=e, oooo, ee->prev->next=ee->next, ee->next->prev=ee->prev @= oo,k=u->deg+v->deg; for (o,f=u->arcs->next,ff=NULL; o,f->tip; o,f=f->next) if (f->tip==v) delete(f),delete(mate(f)),k-=2,o,f->link=ff,ff=f; else o,mate(f)->tip=v; oo,e->link=ff, v->deg=k; if (extraverbose) printf("level %d: Shrinking %d; now %s has degree %d\n", l,e->len,v->name,v->deg); o,ff=v->arcs; /* now |f=u->arcs|; we will merge the two lists */ oooo,f->prev->next=ff->next,ff->next->prev=f->prev; ooo,f->next->prev=ff,ff->next=f->next; @ Unshrinking uses the principle of ``dancing links,'' whereby we exploit the fact that previously deleted nodes still have good information in their |prev| and |next| fields, provided that we undelete in reverse order. @d undelete(e) ee=e, oooo, ee->next->prev=ee, ee->prev->next=ee @= oo,f=u->arcs, ff=v->arcs; ooo,ff->next=f->prev->next; o,ff->next->prev=ff; ooo,f->prev->next=f, f->next->prev=f; for (f=f->prev; o,f->tip; o,f=f->prev) o,mate(f)->tip=u; for (oo,f=e->link,k=v->deg; f; o,f=f->link) k+=2, undelete(mate(f)), undelete(f); oo,v->deg=k-u->deg; if (extraverbose) printf("level %d: Unshrinking %d; now %s has degree %d\n", l,e->len,v->name,v->deg); @ For bridge detection, we try a heuristic that often gives a quick answer when the graph is sparse (namely, to test if |u| has degree~1). Or if |e->link->link!=NULL|, there was another edge between |u| and |v|. Otherwise we resort to brute-force breadth-first, testing whether one can get from |u| to |v| without |e|. When I put this algorithm in a book, I'll probably leave out the two quick-try heuristics, in order to keep the algorithm shorter; breadth-first search will resolve both cases without too much additional calculation. But for now I'm trying to see how useful they are. @d bfs v.V /* link for the breadth-first search: nonnull if vertex seen */ @= if (o,u->deg==1) { if (extraverbose) printf("level %d: %d is a bridge with endpoint %s\n", l,e->len,u->name); goto bridge; } if (o,e->link->link) { if (extraverbose) printf("level %d: %d is parallel to %d\n", l,e->len,e->link->len!=e->len? e->link->len: e->link->link->len); goto nonbridge; } for (o,u->bfs=v,w=u;u!=v;o,u=u->bfs) { for (oo,f=u->arcs->next;o,f->tip;o,f=f->next) if (o,f->tip->bfs==NULL) { if (f->tip==v) { if (f!=mate(e)) @; }@+else oo,f->tip->bfs=v, w->bfs=f->tip, w=f->tip; } } if (extraverbose) printf("level %d: %d is a bridge\n",l,e->len); for (o,u=e->tip;u!=v;o,u->bfs=NULL,u=w) o,w=u->bfs; goto bridge; nonbridge: change_e=e; @; bridge:@; @ @= { for (o,u=e->tip;u!=v;o,u->bfs=NULL,u=w) o,w=u->bfs; goto nonbridge; } @ @= if (extraverbose) printf("level %d: deleting %d\n",l,e->len); ooo,e->link=del(l), del(l)=e; delete(e), delete(mate(e)), oo, e->tip->deg--, v->deg--; goto enter; @ @= for (o,e=del(l);e;o,e=e->link) { oooo,mate(e)->tip->deg++, e->tip->deg++, undelete(mate(e)), undelete(e); if (extraverbose) printf("undeleting %d\n",e->len); } @*Getting started. We're done, except for one embarrassing detail: It is necessary to prime the pump by setting up the original near-tree $a_2\ldots a_{n-1}$. For this purpose I'll use depth-first search, since it seems a bit faster than the alternatives. And I might as well check that the graph is connected. @d sentinel (g->vertices) @= for (v=g->vertices+1;vvertices+n;v++) v->bfs=NULL; for (k=n-1,o,w=v=g->vertices,w->bfs=sentinel;;o,v=w,w=w->bfs) { for (oo,e=v->arcs->next;o,u=e->tip;o,e=e->next) if (o,u->bfs==NULL) { o,aa(k)=e,k--; if (k==0) goto connected; o,u->bfs=w,w=u; } if (w==sentinel) break; } printf("Oops, the graph isn't connected!\n");@+exit(0); connected: for (u=g->vertices;uvertices+n;u++) o,u->bfs=NULL; if (extraverbose) { printf("Depth-first search yields the following spanning tree:\n"); print_a(g); } if (verbose) printf("(%.15g mems for initialization)\n",mems); @ One final debugging aid. @= void print_a(register Graph *g) { register int k; for (k=1;kn;k++) printf(" a%d=%d (%s -- %s)\n", k,aa(k)->len, aa(k)->tip->name, mate(aa(k))->tip->name); } @*Index.