def test_cut__leaves_bothTreesIntact(self): # arrange tree = LinkCutTree() a1 = tree.makeTree('a1') a2 = tree.makeTree('a2') a3 = tree.makeTree('a3') a4 = tree.makeTree('a4') a5 = tree.makeTree('a5') a6 = tree.makeTree('a6') tree.link(a1,a2) tree.link(a2,a3) tree.link(a3,a4) tree.link(a4,a5) tree.link(a5,a6) c1 = tree.makeTree('c1') c2 = tree.makeTree('c2') c3 = tree.makeTree('c3') tree.link(c1,c2) tree.link(c2,c3) tree.link(a6,c1); #act tree.cut(c2) #assert self.assertEqual(a1, tree.getRoot(c1)) self.assertEqual(c2, tree.getRoot(c3))
def test_cut__leaves_bothTreesIntact(self): # arrange tree = LinkCutTree() a1 = tree.makeTree('a1') a2 = tree.makeTree('a2') a3 = tree.makeTree('a3') a4 = tree.makeTree('a4') a5 = tree.makeTree('a5') a6 = tree.makeTree('a6') tree.link(a1, a2) tree.link(a2, a3) tree.link(a3, a4) tree.link(a4, a5) tree.link(a5, a6) c1 = tree.makeTree('c1') c2 = tree.makeTree('c2') c3 = tree.makeTree('c3') tree.link(c1, c2) tree.link(c2, c3) tree.link(a6, c1) #act tree.cut(c2) #assert self.assertEqual(a1, tree.getRoot(c1)) self.assertEqual(c2, tree.getRoot(c3))
class RetroactiveUnionFind(object): """Fully retroactive union find implemented using link-cut trees to represented disjoint forests""" def __init__(self): self.forest = LinkCutTree() self.time = 0 # unionAgo(a,b) links nodes a and b in the LinkCutTree if they weren't already linked. # If they were it cuts the latest edge on the path between a and b (if it is later than the new link time). # The new subtree will contain exactly one of a and b. We make that node the root of the subtree and link it to # the other node still in the main tree. # This preserves old unions because any edge below the cut edge will now follow a path through the a-b edge up to # the lca, which is guaranteed not to have an edge value greater than the cut edge. Any edge above the cut \ # edge will not be affected. def unionAgo(self, a_data, b_data, tdelta=0): # if the sets are already connected at the specified time return if self.sameSetAgo(a_data, b_data, tdelta): return #get node objects to work with a = self.forest.getNode(a_data) b = self.forest.getNode(b_data) union_time = self.time + tdelta if a is None: a = self.forest.makeTree(a_data) if b is None: b = self.forest.makeTree(b_data) # if the nodes are not connected at all, connect them. If they are connected at a later time, # cut the oldest edge on the path between the two nodes, make the union'ed node the root of that tree # and attach it to the other union'ed node. if self.forest.getRoot(a) != self.forest.getRoot(b): self.forest.makeRoot(b) self.forest.link(a, b, union_time) else: lca = self.forest.lca(a, b) max_time = float("-inf") max_time_node = None for next in [a, b]: while next is not lca: if next.parent_edge_weight > max_time: max_time = next.parent_edge_weight max_time_node = next next = next.represented_parent self.forest.cut(max_time_node) if self.forest.getRoot(a) == next: self.forest.makeRoot(a) self.forest.link(b, a, union_time) else: self.forest.makeRoot(b) self.forest.link(a, b, union_time) self.time += 1 # sameSetAgo(a,b,t) will find the lca of a and b and traverse the path from both to the lca, # finding the largest edge on the path between a and b. If any edge is larger than time + tdelta then a, and b # were not connected at (time + tdelta) def sameSetAgo(self, a_data, b_data, tdelta=0): # sameset is reflexive if a_data == b_data: return True a = self.forest.getNode(a_data) b = self.forest.getNode(b_data) query_time = self.time + tdelta if a is None or b is None: return False lca = self.forest.lca(a, b) if lca is None: return False for next in [a, b]: while next is not lca: if next.parent_edge_weight > query_time: return False next = next.represented_parent return True # sameSetWhen(a,b) traverses the path between a and b and return the largest edge, # which is the time at which a and b were connected. def sameSetWhen(self, a, b): lca = self.forest.lca(a, b) if lca is None: return float("-inf") max_time = float("-inf") for next in [a, b]: while next is not lca: if next.parent_edge_weight > max_time: max_time = next.parent_edge_weight next = next.represented_parent return max_time