def MinimumSpanningTree(G): """ Return the minimum spanning tree of an undirected graph G. G should be represented in such a way that iter(G) lists its vertices, iter(G[u]) lists the neighbors of u, G[u][v] gives the length of edge u,v, and G[u][v] should always equal G[v][u]. The tree is returned as a list of edges. """ if not isUndirected(G): raise ValueError("MinimumSpanningTree: input is not undirected") for u in G: for v in G[u]: if G[u][v] != G[v][u]: raise ValueError("MinimumSpanningTree: asymmetric weights") # Kruskal's algorithm: sort edges by weight, and add them one at a time. # We use Kruskal's algorithm, first because it is very simple to # implement once UnionFind exists, and second, because the only slow # part (the sort) is sped up by being built in to Python. subtrees = UnionFind() tree = [] for W, u, v in sorted((G[u][v], u, v) for u in G for v in G[u]): if subtrees[u] != subtrees[v]: tree.append((u, v)) subtrees.union(u, v) return tree
def __init__(self, G): """Search for biconnected components of graph G.""" if not isUndirected(G): raise NotBiconnected self._dfsnumber = {} self._low = {} self._rootedge = None DFS.Searcher.__init__(self, G)
def __init__(self,G): """Search for biconnected components of graph G.""" if not isUndirected(G): raise NotBiconnected self._dfsnumber = {} self._low = {} self._rootedge = None DFS.Searcher.__init__(self,G)
def Chordal(G): """Test if a given graph is chordal.""" if not isUndirected(G): raise ValueError("Input to Chordal is not an undirected graph") try: PerfectEliminationOrdering(G) except: return False return True
def MinimumSpanningTree(G): if not isUndirected(G): raise ValueError("MinimumSpanningTree: input is not undirected") for u in G: for v in G[u]: if G[u][v] != G[v][u]: raise ValueError("MinimumSpanningTree: asymmetric weights") subtrees = UnionFind() tree = [] for W, u, v in sorted((G[u][v], u, v) for u in G for v in G[u]): if subtrees[u] != subtrees[v]: tree.append((u, v)) subtrees.union(u, v) return tree
def CubicMatchPartitions(G): """Partition a biconnected cubic graph G into three matchings. Each matching is represented as a graph, in which G[v] is a list of the three edges of G in the order of the three matchings. This function generates a sequence of such representations. """ if not isUndirected(G): raise ValueError("CubicMatchPartitions: graph is not undirected") for v in G: if len(G[v]) != 3: raise ValueError("CubicMatchPartitions: graph is not cubic") ST = stOrientation(G) L = TopologicalOrder(ST) for B in xrange(1<<(len(L)//2 - 1)): # Here with a bitstring representing the sequence of choices out = {} pos = 0 for v in L: source = [w for w in G[v] if w in out] sourcepos = {} adjlist = [None,None,None] for w in source: sourcepos[w] = [i for i in (0,1,2) if out[w][i]==v][0] adjlist[sourcepos[w]] = w usedpos = [sourcepos[w] for w in source] if len(set(usedpos)) != len(usedpos): # two edges in with same index, doesn't form matching break elif len(source) == 0: # start vertex, choose one orientation adjlist = list(ST[v]) elif len(source) == 1: # two outgoing vertices, one incoming avail = [i for i in (0,1,2) if i != usedpos[0]] if B & (1<<pos): avail.reverse() pos += 1 for i,w in zip(avail,list(ST[v])): adjlist[i] = w elif len(source) == 2: avail = 3 - sum(usedpos) adjlist[avail] = list(ST[v])[0] out[v] = adjlist if len(source) == 3: # final vertex of topological ordering, still all consistent yield out
def CubicMatchPartitions(G): """Partition a biconnected cubic graph G into three matchings. Each matching is represented as a graph, in which G[v] is a list of the three edges of G in the order of the three matchings. This function generates a sequence of such representations. """ if not isUndirected(G): raise ValueError("CubicMatchPartitions: graph is not undirected") for v in G: if len(G[v]) != 3: raise ValueError("CubicMatchPartitions: graph is not cubic") ST = stOrientation(G) L = TopologicalOrder(ST) for B in xrange(1 << (len(L) // 2 - 1)): # Here with a bitstring representing the sequence of choices out = {} pos = 0 for v in L: source = [w for w in G[v] if w in out] sourcepos = {} adjlist = [None, None, None] for w in source: sourcepos[w] = [i for i in (0, 1, 2) if out[w][i] == v][0] adjlist[sourcepos[w]] = w usedpos = [sourcepos[w] for w in source] if len(set(usedpos)) != len(usedpos): # two edges in with same index, doesn't form matching break elif len(source) == 0: # start vertex, choose one orientation adjlist = list(ST[v]) elif len(source) == 1: # two outgoing vertices, one incoming avail = [i for i in (0, 1, 2) if i != usedpos[0]] if B & (1 << pos): avail.reverse() pos += 1 for i, w in zip(avail, list(ST[v])): adjlist[i] = w elif len(source) == 2: avail = 3 - sum(usedpos) adjlist[avail] = list(ST[v])[0] out[v] = adjlist if len(source) == 3: # final vertex of topological ordering, still all consistent yield out
def __init__(self,G): """Search for biconnected components of graph G.""" if not isUndirected(G): raise ValueError("BiconnectedComponents: input not undirected graph") # set up data structures for DFS self._components = [] self._dfsnumber = {} self._activelen = {} self._active = [] self._low = {} self._ancestors = {} # directed subgraph from nodes to DFS ancestors # perform the Depth First Search DFS.Searcher.__init__(self,G) # clean up now-useless data structures del self._dfsnumber, self._activelen, self._active del self._low, self._ancestors
def __init__(self,G): if not isUndirected(G): raise MediumError("not an undirected graph") self._action = {v:{} for v in G} self._reverse = {} for v in G: for w in G[v]: t = G[v][w] if t in self._action[v]: raise MediumError("multiple edges for state %s and token %s" % (v,t)) self._action[v][t] = w if t not in self._reverse: rt = G[w][v] if rt in self._reverse: raise MediumError("mismatched token reversals") self._reverse[t] = rt self._reverse[rt] = t elif G[w][v] != self._reverse[t]: raise MediumError("mismatched token reversals")
def __init__(self, G): """Search for biconnected components of graph G.""" if not isUndirected(G): raise ValueError( "BiconnectedComponents: input not undirected graph") # set up data structures for DFS self._components = [] self._dfsnumber = {} self._activelen = {} self._active = [] self._low = {} self._ancestors = {} # directed subgraph from nodes to DFS ancestors # perform the Depth First Search DFS.Searcher.__init__(self, G) # clean up now-useless data structures del self._dfsnumber, self._activelen, self._active del self._low, self._ancestors
def __init__(self,G): """Relate edges for st-orientation.""" if not isUndirected(G): raise ValueError("stOrienter: input not undirected graph") # set up data structures for DFS self._dfsnumber = {} self._low = {} self._down = {} # down[v] = child we're currently exploring from v self._lowv = {} # lowv[n] = vertex with low number n # The main data structure! # a dictionary mapping edges to lists of edges # each of which should be oriented the same as the key. self.orient = {} self.roots = [] # edges with no predecessor # perform the Depth First Search DFS.Searcher.__init__(self,G) # clean up now-useless data structures del self._dfsnumber, self._low, self._down, self._lowv
def __init__(self, G): """Relate edges for st-orientation.""" if not isUndirected(G): raise ValueError("stOrienter: input not undirected graph") # set up data structures for DFS self._dfsnumber = {} self._low = {} self._down = {} # down[v] = child we're currently exploring from v self._lowv = {} # lowv[n] = vertex with low number n # The main data structure! # a dictionary mapping edges to lists of edges # each of which should be oriented the same as the key. self.orient = {} self.roots = [] # edges with no predecessor # perform the Depth First Search DFS.Searcher.__init__(self, G) # clean up now-useless data structures del self._dfsnumber, self._low, self._down, self._lowv
def D3reducible(G,triangleHooks=[],pathHooks=[],finalize=isK4): """Test if the graph G is D3-reducible. Whenever a reduction is found, the hook functions are called in turn on it, and should return True if the reduction should be allowed to continue or False otherwise. The arguments to a triangle hook are the three triangle vertices, their three neighbors, and the id that will be given to the new vertex formed by the collapsed triangle. The arguments to a path hook are the three path vertices and the apex. The finalize hook takes as input the irreducible graph after all reductions are complete, and produces as output the return value for the overall computation. By default the return value is a Boolean that is true whenever the irreducible graph is K4.""" if not isUndirected(G): raise TypeError("Argument to D3reducible must be an undirected graph") G = copyGraph(G) # We are going to change G, so make a copy of it C = {v for v in G if len(G[v]) == 3} # Active vertices, init. all w/deg=3 def otherNeighbor(u,v,w): """Other neighbor of v given known neighbors u,w""" return [x for x in G[v] if x != u and x != w][0] def triangle(u,v,w): """Try a D3a reduction.""" # u, v, and w are known to form a triangle but we still need to # check whether they have distinct neighbors.""" Nu = otherNeighbor(v,u,w) Nv = otherNeighbor(u,v,w) Nw = otherNeighbor(u,w,v) if Nu == Nv or Nv == Nw or Nu == Nw: return # Need to have three distinct neighbors x = object() # Cons up an id for the triangle # Run the hooks for hook in triangleHooks: if not hook(u,v,w,Nu,Nv,Nw,x): return False # Make the change! del G[u],G[v],G[w] G[Nu].remove(u) G[Nv].remove(v) G[Nw].remove(w) G[x] = {Nu,Nv,Nw} G[Nu].add(x) G[Nv].add(x) G[Nw].add(x) # Update the active vertices for z in (x,Nu,Nv,Nw): if len(G[z]) == 3: C.add(z) def path(u,v,w): """Try a D3b reduction.""" # u, v, and w are known to induce a path but we still need to # check whether they have a common neighbor as apex.""" apexes = G[u] & G[v] & G[w] if len(apexes) != 1: return apex = apexes.pop() # Run the hooks for hook in pathHooks: if not hook(u,v,w,apex): return False # Make the change! del G[v] G[u].remove(v) G[w].remove(v) G[apex].remove(v) G[u].add(w) G[w].add(u) # Update the active vertices if len(G[apex]) == 3: C.add(apex) pass def reduce(u,v,w): """Try a reduction in which v is the middle vertex""" for x in (u,v,w): if x not in G or len(G[x]) != 3: return # No longer in G or not all degree 3, no redux if w in G[u]: triangle(u,v,w) else: path(u,v,w) while C: v = C.pop() if v in G and len(G[v]) == 3: p,q,r = tuple(G[v]) # Decode set of neighbors reduce(p,v,q) reduce(p,v,r) reduce(q,v,r) # Now check whether we found K4 return finalize(G)
def PartialCubeEdgeLabeling(G): """ Label edges of G by their equivalence classes in a partial cube structure. We follow the algorithm of arxiv:0705.1025, in which a number of equivalence classes equal to the maximum degree of G can be found simultaneously by a single breadth first search, using bitvectors. However, in order to avoid deep recursions (problematic in Python) we use a union-find data structure to keep track of edge identifications discovered so far. That is, we repeatedly contract our initial graph, maintaining as we do the property that G[v][w] points to a union-find set representing edges in the original graph that have been contracted to the single edge v-w. """ # Some simple sanity checks if not isUndirected(G): raise Medium.MediumError("graph is not undirected") L = list(StronglyConnectedComponents(G)) if len(L) != 1: raise Medium.MediumError("graph is not connected") # Set up data structures for algorithm: # - UF: union find data structure representing known edge equivalences # - CG: contracted graph at current stage of algorithm # - LL: limit on number of remaining available labels UF = UnionFind() CG = dict([(v,dict([(w,(v,w)) for w in G[v]])) for v in G]) NL = len(CG)-1 # Initial sanity check: are there few enough edges? # Needed so that we don't try to use union-find on a dense # graph and incur superquadratic runtimes. n = len(CG) m = sum([len(CG[v]) for v in CG]) if 1<<(m//n) > n: raise Medium.MediumError("graph has too many edges") # Main contraction loop in place of the original algorithm's recursion while len(CG) > 1: if not isBipartite(CG): raise Medium.MediumError("graph is not bipartite") # Find max degree vertex in G, and update label limit deg,root = max([(len(CG[v]),v) for v in CG]) if deg > NL: raise Medium.MediumError("graph has too many equivalence classes") NL -= deg # Set up bitvectors on vertices bitvec = dict([(v,0) for v in CG]) neighbors = {} i = 0 for neighbor in CG[root]: bitvec[neighbor] = 1<<i neighbors[1<<i] = neighbor i += 1 # Breadth first search to propagate bitvectors to the rest of the graph for LG in BFS.BreadthFirstLevels(CG,root): for v in LG: for w in LG[v]: bitvec[w] |= bitvec[v] # Make graph of labeled edges and union them together labeled = dict([(v,set()) for v in CG]) for v in CG: for w in CG[v]: diff = bitvec[v]^bitvec[w] if not diff or bitvec[w] &~ bitvec[v] == 0: continue # zero edge or wrong direction if diff not in neighbors: raise Medium.MediumError("multiply-labeled edge") neighbor = neighbors[diff] UF.union(CG[v][w],CG[root][neighbor]) UF.union(CG[w][v],CG[neighbor][root]) labeled[v].add(w) labeled[w].add(v) # Map vertices to components of labeled-edge graph component = {} compnum = 0 for SCC in StronglyConnectedComponents(labeled): for v in SCC: component[v] = compnum compnum += 1 # generate new compressed subgraph NG = dict([(i,{}) for i in range(compnum)]) for v in CG: for w in CG[v]: if bitvec[v] == bitvec[w]: vi = component[v] wi = component[w] if vi == wi: raise Medium.MediumError("self-loop in contracted graph") if wi in NG[vi]: UF.union(NG[vi][wi],CG[v][w]) else: NG[vi][wi] = CG[v][w] CG = NG # Here with all edge equivalence classes represented by UF. # Turn them into a labeled graph and return it. return dict([(v,dict([(w,UF[v,w]) for w in G[v]])) for v in G])
def PartialCubeEdgeLabeling(G): """ Label edges of G by their equivalence classes in a partial cube structure. We follow the algorithm of arxiv:0705.1025, in which a number of equivalence classes equal to the maximum degree of G can be found simultaneously by a single breadth first search, using bitvectors. However, in order to avoid deep recursions (problematic in Python) we use a union-find data structure to keep track of edge identifications discovered so far. That is, we repeatedly contract our initial graph, maintaining as we do the property that G[v][w] points to a union-find set representing edges in the original graph that have been contracted to the single edge v-w. """ # Some simple sanity checks if not isUndirected(G): raise Medium.MediumError("graph is not undirected") L = list(StronglyConnectedComponents(G)) if len(L) != 1: raise Medium.MediumError("graph is not connected") # Set up data structures for algorithm: # - UF: union find data structure representing known edge equivalences # - CG: contracted graph at current stage of algorithm # - LL: limit on number of remaining available labels UF = UnionFind() CG = dict([(v, dict([(w, (v, w)) for w in G[v]])) for v in G]) NL = len(CG) - 1 # Initial sanity check: are there few enough edges? # Needed so that we don't try to use union-find on a dense # graph and incur superquadratic runtimes. n = len(CG) m = sum([len(CG[v]) for v in CG]) if 1 << (m // n) > n: raise Medium.MediumError("graph has too many edges") # Main contraction loop in place of the original algorithm's recursion while len(CG) > 1: if not isBipartite(CG): raise Medium.MediumError("graph is not bipartite") # Find max degree vertex in G, and update label limit deg, root = max([(len(CG[v]), v) for v in CG]) if deg > NL: raise Medium.MediumError("graph has too many equivalence classes") NL -= deg # Set up bitvectors on vertices bitvec = dict([(v, 0) for v in CG]) neighbors = {} i = 0 for neighbor in CG[root]: bitvec[neighbor] = 1 << i neighbors[1 << i] = neighbor i += 1 # Breadth first search to propagate bitvectors to the rest of the graph for LG in BFS.BreadthFirstLevels(CG, root): for v in LG: for w in LG[v]: bitvec[w] |= bitvec[v] # Make graph of labeled edges and union them together labeled = dict([(v, set()) for v in CG]) for v in CG: for w in CG[v]: diff = bitvec[v] ^ bitvec[w] if not diff or bitvec[w] & ~bitvec[v] == 0: continue # zero edge or wrong direction if diff not in neighbors: raise Medium.MediumError("multiply-labeled edge") neighbor = neighbors[diff] UF.union(CG[v][w], CG[root][neighbor]) UF.union(CG[w][v], CG[neighbor][root]) labeled[v].add(w) labeled[w].add(v) # Map vertices to components of labeled-edge graph component = {} compnum = 0 for SCC in StronglyConnectedComponents(labeled): for v in SCC: component[v] = compnum compnum += 1 # generate new compressed subgraph NG = dict([(i, {}) for i in range(compnum)]) for v in CG: for w in CG[v]: if bitvec[v] == bitvec[w]: vi = component[v] wi = component[w] if vi == wi: raise Medium.MediumError( "self-loop in contracted graph") if wi in NG[vi]: UF.union(NG[vi][wi], CG[v][w]) else: NG[vi][wi] = CG[v][w] CG = NG # Here with all edge equivalence classes represented by UF. # Turn them into a labeled graph and return it. return dict([(v, dict([(w, UF[v, w]) for w in G[v]])) for v in G])
def D3reducible(G, triangleHooks=[], pathHooks=[], finalize=isK4): """Test if the graph G is D3-reducible. Whenever a reduction is found, the hook functions are called in turn on it, and should return True if the reduction should be allowed to continue or False otherwise. The arguments to a triangle hook are the three triangle vertices, their three neighbors, and the id that will be given to the new vertex formed by the collapsed triangle. The arguments to a path hook are the three path vertices and the apex. The finalize hook takes as input the irreducible graph after all reductions are complete, and produces as output the return value for the overall computation. By default the return value is a Boolean that is true whenever the irreducible graph is K4.""" if not isUndirected(G): raise TypeError("Argument to D3reducible must be an undirected graph") G = copyGraph(G) # We are going to change G, so make a copy of it C = {v for v in G if len(G[v]) == 3} # Active vertices, init. all w/deg=3 def otherNeighbor(u, v, w): """Other neighbor of v given known neighbors u,w""" return [x for x in G[v] if x != u and x != w][0] def triangle(u, v, w): """Try a D3a reduction.""" # u, v, and w are known to form a triangle but we still need to # check whether they have distinct neighbors.""" Nu = otherNeighbor(v, u, w) Nv = otherNeighbor(u, v, w) Nw = otherNeighbor(u, w, v) if Nu == Nv or Nv == Nw or Nu == Nw: return # Need to have three distinct neighbors x = object() # Cons up an id for the triangle # Run the hooks for hook in triangleHooks: if not hook(u, v, w, Nu, Nv, Nw, x): return False # Make the change! del G[u], G[v], G[w] G[Nu].remove(u) G[Nv].remove(v) G[Nw].remove(w) G[x] = {Nu, Nv, Nw} G[Nu].add(x) G[Nv].add(x) G[Nw].add(x) # Update the active vertices for z in (x, Nu, Nv, Nw): if len(G[z]) == 3: C.add(z) def path(u, v, w): """Try a D3b reduction.""" # u, v, and w are known to induce a path but we still need to # check whether they have a common neighbor as apex.""" apexes = G[u] & G[v] & G[w] if len(apexes) != 1: return apex = apexes.pop() # Run the hooks for hook in pathHooks: if not hook(u, v, w, apex): return False # Make the change! del G[v] G[u].remove(v) G[w].remove(v) G[apex].remove(v) G[u].add(w) G[w].add(u) # Update the active vertices if len(G[apex]) == 3: C.add(apex) pass def reduce(u, v, w): """Try a reduction in which v is the middle vertex""" for x in (u, v, w): if x not in G or len(G[x]) != 3: return # No longer in G or not all degree 3, no redux if w in G[u]: triangle(u, v, w) else: path(u, v, w) while C: v = C.pop() if v in G and len(G[v]) == 3: p, q, r = tuple(G[v]) # Decode set of neighbors reduce(p, v, q) reduce(p, v, r) reduce(q, v, r) # Now check whether we found K4 return finalize(G)