This section presents a collection of conservative DRAM algorithms for solving graph problems. The algorithms use two processors per edge of an input graph and require only constant extra space in each processor. Most of the algorithms use treefix computations as subroutines.
We represent each vertex in an undirected graph by a doubly linked incidence ring of processors, one for each edge. Each element of the incidence ring contains pointers to the next and previous elements in the ring, and one pointer for a graph edge. For each edge , the element in the incidence ring for u contains a pointer to an edge element in the incidence ring for v, and vice versa. A directed graph is represented in the same doubly linked fashion, but the graph edges are labeled with their directions.
We represent trees with arbitrary vertex degrees by an incidence ring structure as well. If the tree is directed, each ring has a unique principal element that points toward the root. Breaking the incidence ring before the principal element yields the standard binary-tree representation of the tree [14, pp. 332-333,].
We now present brief descriptions of the algorithms. The performance is given in terms of the number of steps on a DRAM when the input representation has size n. We assume the implicit tree contractions in the algorithms are performed by the randomized Algorithm TC . Deterministic bounds can be obtained by multiplying the number of steps by , where m is the number of processors. An upper bound on the time required in the DRAM model can be obtained by multiplying the number of steps by the load factor of the input.
Generalized treefix. Perform a treefix operation on a directed tree with arbitrary vertex degree. The input values are stored in the principal elements of the tree, which is where the output values are to be placed. The leaffix value at a node i whose children have values is . Each nonprincipal element is assigned the identity for its value. A binary treefix computation performed on the binary tree representation underlying the tree computes the desired values. Performance: .
Tree functions. Given a directed tree, compute for each node the number of descendants, its height, or its depth. The number of dependents for each node can be computed by a leaffix computation with as integer addition and for all nodes. The height of a node can also be computed by a leaffix computation where , the identity is , and for all nodes. The depth of a node can be computed by a rootfix computation with as addition and for all nodes except the root which has value 0. Performance: .
Rooting an undirected tree. Pick a root of a tree with undirected graph pointers, and orient the graph pointers toward the root. Form an ``Eulerian tour'' of the pointers of the representation [27] by directing each element of the tree to link its incoming ring pointer with its graph edge directed outward and its graph edge directed inward with its outgoing ring pointer. Each graph edge is used twice in the tour, once in each direction, but each ring pointer is used only once. Use Algorithm LC to form a contraction tree of the tour. Choose the root of the contraction tree to be the root of the tree, and break the tour so that it begins with the root. Use parallel prefix to number each node according to its first occurrence in the tour. Use contraction trees to distribute the smallest value in each incidence ring to the elements of the ring. Orient each graph edge from the larger value to the smaller. Performance: .
Rerooting a directed tree. Given a directed tree and another distinguished vertex k, reorient the graph edges of the tree to point toward k. The algorithm for rooting a tree can be used by picking k as the root instead of the root of the contraction tree, but a single treefix computation suffices. Perform a leaffix computation with and if , and use Boolean OR for . Each principal element whose leaffix value is 1 lies on the path from to the root. Reverse the direction of the graph pointers of these elements. (Note: rerooting a tree changes the principal elements.) Performance: .
Tree-walk numberings of a binary tree. Number the nodes of a binary tree according to the order they would be visited in a preorder/inorder/postorder tree walk. For each of the walks, we will compute , the number of nodes visited before the left subtree of k. Use a leaffix computation to compute the number of the subtree rooted at k. We first compute the preorder numbering. (For the purposes of these numbering algorithms, we consider the root to be a left child.) If node k is a left child, set . If node k is a right child, set to the size of its sibling subtree plus 1. A rootfix computation with + yields , which is the preorder numbering of node k. The inorder numbering can be computed similarly. If node k is a left child, set . If k is a right child, set to the size of its sibling subtree plus 1. Compute for each node using a rootfix computation with +. The inorder numbering of node k is plus the size of its left subtree plus 1. The postfix numbering can be computed by setting if node k is a left child, and by setting to the size of its sibling subtree if k is a right child. After computing using a rootfix computation with +, the postfix numbering of node k is plus the sizes of its two subtrees plus 1. Performance: .
Prefix and postfix numberings of a directed tree. Number the edges of an arbitrary directed tree according to the order they are visited in a preorder/postorder tree walk. The problem reduces to prefix/postfix numbering on the underlying binary tree representation. Performance: .
Diameter and center of a tree. The diameter is the length of the longest path in the tree. A center is a vertex v such that the longest path from v to a leaf is minimal over all vertices in the tree. The diameter can be determined by rooting the tree and using rootfix to find the farthest leaf from the root. Reroot the tree at this leaf. The distance from the new root to the farthest leaf is the diameter. (This algorithm is based on an analog algorithm attributed to J. Wennmacker [6].) A center of the tree can be determined by finding a median element of the path that realizes the diameter. Performance: .
Centroid of a tree. A centroid is a vertex v such that the largest subtree with v as a leaf is minimal over all vertices in the tree. A centroid can be determined by rooting the tree and computing the size of each subtree. By broadcasting the size m of the tree from the root, each graph edge in each incidence ring can determine the number of elements on the other side of the edge. For each incidence ring, compute the maximum of these values. A vertex with the minimum of these maximum values is a centroid. Performance: .
Separator of a tree. A separator [20] is a partition of the vertices of an n-vertex tree into three sets A, B, and C, with , , and , such that no edge of the tree goes between a vertex in A and a vertex in C. Determine a centroid of the tree. This vertex is the separator vertex in B. It remains to partition the remaining vertices between A and C. For each graph edge in the incidence ring, count the number of vertices in the subtree on the other side of the edge. Put the largest subtree in A. Use parallel prefix on the incidence ring to compute a running sum of the sizes of the other subtrees. Put all subtrees whose prefix value is at most in C, and put the remainder in A. Performance: .
Subexpression evaluation. Given a directed tree in which each leaf has a value and each internal node has an operator from , compute for each internal node the subexpression rooted at that node. A single leaffix-like computation suffices using the ideas of Brent [2] and Miller and Reif [22]. Performance: .
Minimum-cost spanning forest. Given an undirected input graph and a cost function , determine a set of edges such that each vertex in V is incident on an edge of F, and the sum of the weights of the edges in F is minimal. We give a conservative DRAM implementation of Boruvka's algorithm, also attributed to Sollin [26, pp. 71-83,]. We assume without loss of generality that the edge weights are distinct--otherwise, we can assign the weight of a graph edge e between two incidence-ring elements with addresses a and b to be and then compare weights lexicographically. We determine F by marking edges in G. Initially, no edges are marked. At each step of the algorithm, the currently marked graph edges form a subforest of F. Break each incidence ring by removing a single ring pointer, and direct the resulting linear list. At each step of the algorithm, the marked graph edges and the ring pointers form a set of rooted trees, where the index i of the tree is the address of the root. The algorithm proceeds as follows. For each tree , use a rootfix computation to broadcast i to all of the elements in . Use a leaffix computation on to determine an edge connecting an edge element with an edge element , where , with smallest weight. If no such edge exists, the algorithm terminates. If picks the same edge as , the tree with smaller index does nothing. Otherwise, mark e as a member of F, directing it into , and reroot with u as the new root. Repeat this procedure until the algorithm terminates. Performance: .
Connected components. Given an undirected input graph , determine a labeling such that if and only if v and are in the same connected component of G. The algorithm is the same as the minimum spanning tree algorithm, choosing the weight of a graph edge e between incidence ring elements with addresses a and b to be . The label of a vertex is the index of its tree. Performance: .
Biconnected components. Two edges of an undirected graph are in the same biconnected component if they lie on a common simple cycle. Determine a labeling such that if and only if e and are in the same biconnected component of G. We give a conservative DRAM implementation of the biconnectivity algorithm of Tarjan and Vishkin [27]. We assume that the reader has some familiarity with that algorithm. Find a (directed) minimum spanning tree of G. Number the vertices in the minimum spanning tree in preorder. Use leaffix computations to compute for each vertex v three values: , , and . The value is the number of descendants of v, and ( ) is the lowest (highest) numbered vertex (with respect to the preorder numbering of T) that is either a descendant of v or adjacent to a descendant of v by an edge of E-F. Build a new graph where the edges of F are the vertices of . Let e be an edge from u to , where is the parent of u in F. The adjacency ring for u in G acts as the adjacency ring for e in . Add two kinds of edges to . For each edge in E-F such that , add an edge to . For each edge of F such that and , and or , add an edge to . It can be verified that the representation of is conservative with respect to the representation of G. Find the connected components of . Two edges of F are in the same block if as vertices in they are in the same connected component. Finally, for each edge in E-F, let . Performance: .
Eulerian cycle. An Eulerian cycle of an undirected graph is a cycle containing each edge in E exactly once. If any vertex has odd degree, then no Eulerian cycle exists. Form a set of disjoint cycles of the pointers of the representation of G as in the algorithm for directing a tree. The cycles can be merged using an algorithm similar to the minimum-cost-spanning-forest algorithm. Performance: .