Пример #1
0
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])
Пример #2
0
def RoutingTable(M):
    """
    Return a dictionary mapping pairs (state1,state2) to tokens,
    such that the action of the token takes state1 closer to state2.
    By following successive tokens from this table, we can find
    a path in the medium that uses each token at most once
    and involves no token-reverse pairs.
    
    We use the O(n^2) time algorithm from arxiv:cs.DS/0206033.
    This is also a key step of the partial cube recognition algorithm
    from arxiv:0705.1025 -- as part of that algorithm, if we
    recognize that the input is not a medium, we raise MediumError.
    """
    G = StateTransitionGraph(M)
    current = initialState = next(iter(M))

    # find list of tokens that lead to the initial state
    activeTokens = set()
    for LG in BFS.BreadthFirstLevels(G,initialState):
        for v in LG:
            for w in LG[v]:
                activeTokens.add(G[w][v])
    for t in activeTokens:
        if M.reverse(t) in activeTokens:
            raise MediumError("shortest path to initial state is not concise")
    activeTokens = list(activeTokens)
    inactivated = object()  # flag object to mark inactive tokens

    # rest of data structure: point from states to list and list to states
    activeForState = {S:-1 for S in M}
    statesForPos = [[] for i in activeTokens]
    
    def scan(S):
        """Find the next token that is effective for s."""
        i = activeForState[S]
        while True:
            i += 1
            if i >= len(activeTokens):
                raise MediumError("no active token from %s to %s" %(S,current))
            if activeTokens[i] != inactivated and M(S,activeTokens[i]) != S:
                activeForState[S] = i
                statesForPos[i].append(S)
                return
    
    # set initial active states
    for S in M:
        if S != current:
            scan(S)

    # traverse the graph, maintaining active tokens
    visited = set()
    routes = {}
    for prev,current,edgetype in DFS.search(G,initialState):
        if prev != current and edgetype != DFS.nontree:
            if edgetype == DFS.reverse:
                prev,current = current,prev
            
            # add token to end of list, point to it from old state
            activeTokens.append(G[prev][current])
            activeForState[prev] = len(activeTokens) - 1
            statesForPos.append([prev])
            
            # inactivate reverse token, find new token for its states
            activeTokens[activeForState[current]] = inactivated
            for S in statesForPos[activeForState[current]]:
                if S != current:
                    scan(S)

            # remember routing table as part of returned results
            if current not in visited:
                for S in M:
                    if S != current:
                        routes[S,current] = activeTokens[activeForState[S]]

    return routes