def _delete_nonterminal_leaves(edges, terminals): r"""Prunes all non-terminal leaves from the tree. Given the edges of a tree and a set of terminal vertices, this method removes all non-terminal leaves and returns the new tree edges. Parameters ---------- edges : list A list of the edges of the tree. terminals : set A set of the terminal vertices. Returns ------- tree_edges : list A list of the edges of the pruned tree. Notes ----- This method deletes all non-terminal leaves from the tree. This process is repeated until all leaves are terminals. There is room to improve this algorithm. """ # Form a graph from the edge list. tree = Graph() for (u, v) in edges: tree.add_edge(u, v) # Now delete all leaves that are not terminals while True: leaves = tree.leaves() changed = False for leaf in leaves: if leaf not in terminals: tree.delete_vertex(leaf) changed = True if not changed: break return tree.edges()
def gabow(graph, k=None): def find_exchange(father, included, excluded): X = NamedUnionFind(vertices) find = X.find union = X.union (min_weight, e, f) = (inf, None, None) for (x, y) in included: # Make it so that y = father[x] if y != father[x]: x, y = y, x y = find(y) union(x, y, y) for edge in excluded: mark(edge) for (x, y) in edges: if (x, y) in marked or (y, x) in marked: unmark((x, y)) unmark((y, x)) elif father[x] != y and father[y] != x: a = find(x) ancestors = set() while a not in ancestors: ancestors.add(a) a = find(father[a]) a = find(y) while a not in ancestors: a = find(father[a]) for u in [x, y]: v = find(u) while v != a: fv = father[v] exchange_weight = weight[x, y] - weight[v, fv] if exchange_weight < min_weight: min_weight = exchange_weight e = (v, fv) f = (x, y) w = find(fv) union(v, w, w) v = w return (min_weight, e, f) inf = float('inf') if k is None: k = inf weight = {(u, v): graph[u][v] for u in graph for v in graph[u]} marked = set() mark = marked.add unmark = marked.discard edges = sorted(graph.edge_set(), key=lambda (u, v): weight[(u, v)]) vertices = graph.vertices() # arbitrary root vertex root = vertices[0] tree_weight, father = prim(graph, root, edge_list=False) father[root] = root (exchange_weight, e, f) = find_exchange(father, [], []) heap = [(tree_weight + exchange_weight, e, f, father, [], [])] tree_edges = sorted([(min(x, y), max(x, y)) for (x, y) in father.items() if x != y]) yield tree_weight, tree_edges j = 1 while j < k: (tree_weight, e, f, father, included, excluded) = heappop(heap) if tree_weight == inf: return new_graph = Graph() new_graph.add_edges(father.items()) new_graph.delete_edge(e) new_graph.add_edge(f) new_father = breadth_first_search_tree(new_graph, root) new_father[root] = root tree_edges = sorted([(min(x, y), max(x, y)) for (x, y) in new_father.items() if x != y]) yield tree_weight, tree_edges new_tree_weight = tree_weight - weight[f] + weight[e] included_i = included + [e] excluded_j = excluded + [e] (exchange_weight, e, f) = find_exchange(father, included_i, excluded) heappush(heap, (new_tree_weight + exchange_weight, e, f, father, included_i, excluded)) (exchange_weight, e, f) = find_exchange(new_father, included, excluded_j) heappush(heap, (tree_weight + exchange_weight, e, f, new_father, included, excluded_j)) j += 1
def distance_network_heuristic(graph, terminals): r"""Returns an approximate minimal Steiner tree connecting `terminals` in `graph`. Given a connected, undirected graph `graph` with positive edge weights and a subset of the vertices `terminals`, this method finds a subgraph with near-minimal total edge cost among all connected subgraphs that contain these vertices. Parameters ---------- graph : rosemary.graphs.graphs.Graph terminals : list or set or tuple A collection of vertices of `graph`. Returns ------- (weight, edges) : tuple The number `weight` is the weight of the Steiner tree. The list `edges` is a list of the edges in the Steiner tree. Notes ----- The Steiner problem in graphs asks to find the minimal tree connecting the terminal vertices. This problem is NP-complete and has proven to be extremely difficult even for small problem instances. Given this, it is of practical importance to have heuristics for finding low-cost Steiner trees. This method uses the heuristic algorithm given in [1], which produces a tree spanning the terminals with total cost <= 2*(1 - 1/t)*MST, where t is the number of terminal vertices and MST is the weight of a minimal Steiner tree. We also apply the improvement procedure given in [3]. The implementation given runs in time O(t*|E|*log(|V|)), where |E| is the number of edges of `graph` and |V| is the number of vertices. References ---------- .. [1] L. Kou, G. Markowsky, L. Berman, "A Fast Algorithm for Steiner Trees", Acta Informatica, Volume 15, Issue 2, June 1981, 141-145. .. [2] P. Winter, "Steiner Problem in Networks: A Survey", Networks, Volume 17, Issue 2, Summer 1987, 129-167. .. [3] S. Voss, "Steiner's Problem in Graphs: Heuristic Methods", Discrete Applied Mathematics 40, 1992, 45-72. .. [4] H.J. Proemel, A. Steger, "The Steiner Tree Problem - A Tour through Graphs, Algorithms, and Complexity", Vieweg, 2002. Examples -------- >>> G = Graph() >>> G.add_edges([('u1', 'u2', 1), ('u1', 'v1', 2), ('u1', 'v2', 2), ('u2', 'v3', 4), ('u2', 'v4', 3), ('u2', 'v5', 5), ('u3', 'v1', 2), ('u3', 'v3', 8), ('u4', 'v2', 8), ('u4', 'v5', 8), ('v1', 'v2', 8), ('v1', 'v3', 9), ('v2', 'v5', 5), ('v3', 'v4', 8)]) >>> distance_network_heuristic(G, ['v1', 'v2', 'v3', 'v4', 'v5']) (17, [('u1', 'u2'), ('u1', 'v1'), ('u1', 'v2'), ('u2', 'v3'), ('u2', 'v4'), ('v2', 'v5')]) """ # Create the distance network induced by the terminal set. distance_network = Graph() # shortest_prev[u] holds the predecessor dict for the shortest path # tree rooted at u. shortest_prev = {} shortest_dist = {} for u in terminals: u_dist, u_prev = dijkstra(graph, u) shortest_dist[u] = u_dist shortest_prev[u] = u_prev # For each pair (u, v) of terminal vertices, add an edge with weight # equal to the length of the shortest u, v path. distance_network.add_edges([(u, v, shortest_dist[u][v]) for (u, v) in itertools.combinations(terminals, 2)]) # Determine the minimum spanning tree of the distance network. _, mst_edges = prim(distance_network, edge_list=True) subnetwork = Graph() # Construct a subnetwork of the graph by replacing each edge in the # minimum spanning tree by the corresponding minimum cost path. for (u, v) in mst_edges: a, b = shortest_prev[u][v], v while a is not None: subnetwork.add_edge(a, b, graph[a][b]) a, b = shortest_prev[u][a], a # Determine the minimum spanning tree of the subnetwork. _, subnetwork_mst_edges = prim(subnetwork, edge_list=True) tree_weight, tree_edges = _improve(graph, subnetwork_mst_edges, terminals) return (tree_weight, tree_edges)