February 2, 2010

A tree is a connected, acyclic graph (V,E). In a tree, |E| = |V |- 1 (intuitively, each edge connects one ‘non-root’ vertex to its ‘parent’). The trees we have considered earlier have been rooted trees; one vertex is distinguished as the root, and the tree is drawn dangling from this vertex. Choosing a root vertex provides a direction for each edge—conventionally, pointing away from the root. Undirected trees have the properties (a) that, if a new edge is added, the graph is no longer acyclic, and (b) that there is a path between every pair of vertices but, if any edge is deleted, this is no longer true.

A collection of edges that provides a path from the start vertex to all other vertices in the graph, is a spanning tree, for the graph. Given a connected, weighted, undirected graph, we want to find a minimal spanning tree—we seek to minimise the sum of the weights of the edges in the tree. We will present two algorithms based on a common idea; both algorithms construct a spanning tree by incrementally adding to a set of edges, and maintain the invariant that this set of edges is a subset of some minimal spanning tree.

We say two vertices of the graph are linked by this set if there is a path between them consisting only of edges in the set. Each new edge joins two previously unlinked vertices; this ensures that the collection of edges is acyclic. The algorithms are known by the names of two of their discoverers, Kruskal and Prim.

We will argue argue that, at each step in the construction,,

- If the original set of edges is contained in some minimal spanning tree, then so is the augmented set.
- Two vertices are linked if there is some path between them via previously considered edges.

In both cases, we start with an empty set of edges, which is certainly contained in a minimal spanning tree. When we have considered all edges, we can conclude that we have a set of edges, linking all nodes, and contained in some minimal spanning tree. It must be a minimal spanning tree.

Prim’s algorithm grows a tree from an arbitrary root vertex. To extend the tree we select an edge of minimal weight from among those that join a new node to the tree. We maintain a priority queue of edges. When we link a node to the tree, we add to the queue all edges leading from that node. Prim’s algorithm is a variation of of our generic graph search; a node is visited when it is linked to the tree.

Suppose the tree A is contained in some minimal spanning tree T. We form a new tree A′ by adding an edge (u,v) of minimal weight linking some node u in A to a node v not in A. The edge If (u,v) belongs to T, our new tree, A′ is contained in T. Otherwise, T must contain a path linking u, which is in A, and v, which is not. This path must contain some edge (u′,v′) for which u′ is in A, and v′ is not. Remove (u′,v′) from T, and add (u,v), to form a new set of edges, T′, which is a spanning tree containing A′. As the weight of (u,v) is minimal, T′ is a minimal spanning tree.

An implementation of Prim’s algorithm is given by the functor PRIM. The auxiliary function grow is called O(|E|) times, each with a call to member; the priority queue provided by PQ is used to process O(|E|) edges. The else branch of the recursion is called O(|V |) times, once for each vertex, each with two calls to insert. The complexity of the algorithm will depend on the implementation the sets and queue.

If the graph is not connected, Prim’s algorithm returns a spanning tree for the component of the initial vertex selected.

Kruskal’s algorithm grows a forest of trees. Edges of the graph are considered in turn; an edge is added to the forest if it joins two previously unconnected vertices. Edges are considered in order of decreasing weight.

The argument for Kruskal’s algorithm is just like that for Prim’s. When we extend a forest F to form a forest F′, we ensure that any minimal spanning tree T extending F can be modified by minor surgery, to form a minimal spanning tree T′ containing F′.

Implementing Kruskal’s algorithm efficiently requires a special data-structure, called a partition, to keep track of which vertices are linked. This data-structure represents the partitioning of a set into disjoint subsets. It supports two operations: union, which forms a new partition in which two subsets have been merged, and find, which produces a representative of the subset containing a given element. Two elements, x, and y, are in the same subset of the partition, p, if and only if find(p)(x) = find(p)(y). A signature is given in the code for Kruskal’s algorithm. Implementations of this signature will be discussed later in the course.

The implementation of Kruskal’s algorithm, given by the functor KRUSKAL, also makes O(|E|) calls to its auxiliary function, grow; each call includes two calls to find. Again, a priority queue is used to process O(|E|) edges. Since we are building a spanning tree, the else branch is executed |V |- 1 times, once for each edge in the tree; each time includes one call to insert and one to union.

For graphs that are not connected Kruskal’s algorithm returns a forest of spanning trees— one for each component of the graph.

- functor PRIM(

structure G : sig

type Vertex type Edge type Graph

val adj : Graph -> Vertex -> Edge list

val ends: Edge -> Vertex * Vertex

end

structure VS : SetSig

structure T : SetSig

structure PQ : QueueSig

sharing type G.Vertex = VS.Item

and type PQ.Item = T.Item = G.Edge

) = struct

type Graph = G.Graph

type Vertex = G.Vertex

type Tree = T.Set

fun span (g:Graph) (s:Vertex) : Tree =

let fun grow edges tree included =

if PQ.isEmpty edges then tree else

let val (e, es) = PQ.deq edges

val (u, v) = G.ends e

in

if VS.member included v then

grow es tree included

else grow

(PQ.menq (G.adj g v, es))

(T.insert (e, tree))

(VS.insert (v, included))

end

in

grow (PQ.menq(G.adj g s, PQ.empty))

T.empty

(VS.insert(s,VS.empty))

end

end;

- functor KRUSKAL(

structure G: sig

type Edge

type Vertex

type Graph

val ends : Edge -> Vertex * Vertex (* graph *)

val edges: Graph -> Edge list

end

structure PQ: QueueSig (* a priority queue of edges *)

structure F: SetSig (* a set of edges *)

structure P: sig

type Part

eqtype Item

val empty: Part

val union: Part -> (Item*Item) -> Part

val find : Part -> Item -> Item

end

sharing type F.Item = PQ.Item = G.Edge

and type P.Item = G.Vertex

) = struct

type Graph = G.Graph

and Forest = F.Set

fun span (g: Graph) : Forest =

let fun grow edgeQ forest p =

if PQ.isEmpty edgeQ then forest

else

let val (e,q) = PQ.deq edgeQ

val (u,v) = G.ends e

in if P.find p u = P.find p v

then grow q forest p

else grow q

(F.insert(e, forest))

(P.union p (u,v))

end

in

grow (PQ.menq (G.edges g, PQ.empty)) F.empty P.empty

end

end;

(C) Michael Fourman 1994-2006