def _shortest_x_tree(H, source_node, b_tree, F=sum_function, valid_ordering=False): """General form of the Shorest B-Tree algorithm, extended to also perform the implicit Shortest F-Tree procedure if the b_tree flag is not set (providing better time/memory performance than explcitily taking the hypergraph's symmetric image and then performing the SBT procedure on that). Uses priority queue to achieve O(size(H)*lg(n)) runtime. Refer to 'shorest_b_tree's or 'shorest_f_tree's documentation for more details. :param H: the H to perform the 'SXT' algorithm on. :param source_node: the root of the tree to be found. :param b_tree: boolean flag representing whether the Shortest B-Tree algorithm should be executed (vs the Shortest F-Tree). :param F: function pointer to any additive weight function; that is, any function that is only a function of the weights of the nodes in the tail of a hyperedge. :param valid_ordering: a boolean flag to signal whether or not a valid ordering of the nodes should be returned. :returns: dict -- mapping from each node to the ID of the hyperedge that preceeded it in this traversal. dict -- mapping from each node to the node's weight. list -- [only if valid_ordering argument is passed] a valid ordering of the nodes. :raises: TypeError -- Algorithm only applicable to directed hypergraphs """ if not isinstance(H, DirectedHypergraph): raise TypeError("Algorithm only applicable to directed hypergraphs") if b_tree: forward_star = H.get_forward_star hyperedge_tail = H.get_hyperedge_tail hyperedge_head = H.get_hyperedge_head else: forward_star = H.get_backward_star hyperedge_tail = H.get_hyperedge_head hyperedge_head = H.get_hyperedge_tail hyperedge_weight = H.get_hyperedge_weight node_set = H.get_node_set() # Pv keeps track of the ID of the hyperedge that directely # preceeded each node in the traversal Pv = {node: None for node in node_set} hyperedge_ids = H.get_hyperedge_id_set() # W keeps track of the smallest weight path from the source node # to each node W = {node: float("inf") for node in node_set} W[source_node] = 0 # k keeps track of how many nodes in the tail of each hyperedge are # B-connected (when all nodes in a tail are B-connected, that hyperedge # can then be traversed) k = {hyperedge_id: 0 for hyperedge_id in hyperedge_ids} # List of nodes removed from the priority queue in the order that # they were removed ordering = [] Q = PriorityQueue() Q.add_element(W[source_node], source_node) while not Q.is_empty(): # At current_node, we can traverse each hyperedge in its forward star current_node = Q.get_top_priority() ordering.append(current_node) for hyperedge_id in forward_star(current_node): # Since we're arrived at a new node, we increment # k[hyperedge_id] to indicate that we've reached 1 new # node in this hyperedge's tail k[hyperedge_id] += 1 # Traverse this hyperedge only when we have reached all the nodes # in its tail (i.e., when k[hyperedge_id] == |T(hyperedge_id)|) if k[hyperedge_id] == len(hyperedge_tail(hyperedge_id)): f = F(hyperedge_tail(hyperedge_id), W) # For each node in the head of the newly-traversed hyperedge, # if the previous weight of the node is more than the new # weight... for head_node in \ [node for node in hyperedge_head(hyperedge_id) if W[node] > hyperedge_weight(hyperedge_id) + f]: # Update its weight to the new, smaller weight W[head_node] = hyperedge_weight(hyperedge_id) + f Pv[head_node] = hyperedge_id # If it's not already in the priority queue... if not Q.contains_element(head_node): # Add it to the priority queue Q.add_element(W[head_node], head_node) else: # Otherwise, decrease it's key in the priority queue Q.reprioritize(W[head_node], head_node) if valid_ordering: return Pv, W, ordering else: return Pv, W
def test_priority_queue(): Q = PriorityQueue() Q.add_element(3, "a") Q.add_element(2, "b") Q.add_element(4, "c") Q.add_element(1, "d") Q.add_element(5, "e") assert not Q.is_empty() assert Q.contains_element("a") assert Q.contains_element("b") assert Q.contains_element("c") assert Q.contains_element("d") assert Q.contains_element("e") assert Q.peek() == "d" Q.reprioritize(6, "d") Q.reprioritize(7, "d") Q.reprioritize(8, "d") assert Q.peek() == "b" Q.delete_element("b") assert not Q.contains_element("b") assert Q.peek() == "a" assert Q.get_top_priority() == "a" assert not Q.contains_element("a") # Try invalid delete try: Q.delete_element("b") assert False except ValueError: pass except BaseException as e: assert False, e # Try invalid reprioritize try: Q.reprioritize(1, "b") assert False except ValueError: pass except BaseException as e: assert False, e Q = PriorityQueue() # Try invalid peek try: Q.peek() assert False except IndexError: pass except BaseException as e: assert False, e # Try invalid get_top_priority try: Q.get_top_priority() assert False except IndexError: pass except BaseException as e: assert False, e