Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion retroactive/basic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from bst import BSTNode
from names import *
from bst import BSTNode
from queue import Queue
from link_cut_tree import LinkCutTree
from splay_tree import SplayNode


144 changes: 143 additions & 1 deletion retroactive/basic/link_cut_tree.py
Original file line number Diff line number Diff line change
@@ -1 +1,143 @@
## TODO
from splay_tree import SplayNode


class LinkCutTree(object):

def __init__(self):
self.nodes = {}

# access(n) makes n the root of the virtual tree, and its aux tree contains the path from root to v only.
# First we cut off the right child of the node we need to access (this is done by setting the value of prev to
# None initially). We then splay n to the root of its aux tree. After that we attach n to its path parent (the node
# it points to outside of its own aux tree), we then splay the parent, and repeat the process until we reach the
# root of the virtual tree. After this process we have a chain of right children all the way down to n.
# Splaying n now makes it the root of the virtual tree. It has no right child since we cut it off initially.
# The sub-tree to its left contains all its path parents in-order because the splay and right link process preserves
# order, and the link function (defined elsewhere) always adds new children on the right.
# Note: we return the last aux root so we can have an efficient lca algorithm
def access(self, node):
next = node
prev = None
while next is not None:
next.splay()
next.right = prev
prev = next
next = next.parent

node.splay()
return prev

#
# Mutate
#

# cut(n) accesses n, meaning it is the deepest node on the current preferred path and thus only has a left child
# (no parent since it is the root of the virtual tree). When n is accessed it becomes the head of the virtual tree.
# All nodes in its left subtree are its ancestors in the preferred path.
# All n's children in the represented tree currently have path parent pointers to n.
# So removing n's left child severs the subtree rooted at n from the rest of the tree.
def cut(self, node):
self.access(node)

# if the node is already a root return
if node.left is None:
return

# virtual tree updates
node.left.parent = None
node.left = None

# represented tree updates
node.represented_parent = None
node.parent_edge_weight = None

# link(p,c,e) Connects two represented trees by connecting the auxiliary trees containing the nodes p and c.
# p represents the node that will get a new child path in the represented tree, and c is the new sub-tree.
# c needs to be the root of its represented tree to ensure that it does not have multiple parents.
# Since c can only have one unique parent in the new represented tree, p (the leaf node in the path that c
# was attached to), we store information about the represented edge between c and p in c.
# Following a nodes represented parents up a tree is equivalent to doing a reverse in order traversal.
def link(self, parent_node, child_root, edge_weight = None):
# make sure the two nodes are from different trees
if self.getRoot(parent_node) == self.getRoot(child_root):
return

self.access(parent_node)
self.access(child_root)
# assert: child_root.isRoot() == True and child.left == None, because if not we will have two paths leading
# to the child node after linking (child_root.left and child_root.parent) violating the tree property
if child_root.left is not None:
raise Exception('Trying to link a child tree from an internal node')

#virtual tree
parent_node.right = child_root
child_root.parent = parent_node

#represented tree
child_root.parent_edge_weight = edge_weight
child_root.represented_parent = parent_node

def makeTree(self, data):
if data in self.nodes:
raise Exception("makeTree: Creating duplicate nodes")
else:
new_node = SplayNode(data)
self.nodes[data] = new_node
return new_node

# makeRoot(node) flips the path from node to its root, inverting the path and making it a child path of node.
# All other connections are preserved.
def makeRoot(self, node):
self.access(node)
flipped = None
to_flip = node
while to_flip is not None:
next_edge_weight = to_flip.parent_edge_weight
next_to_flip = to_flip.represented_parent or None
next_edge_weight = to_flip.parent_edge_weight
self.cut(to_flip)
if flipped is not None:
self.link(flipped, to_flip, edge_weight)
flipped = to_flip
to_flip = next_to_flip
edge_weight = next_edge_weight



#
# Query
#

def getNode(self, data):
if data in self.nodes:
return self.nodes[data]
else:
return None

# get root(n) accesses n, putting it in the preferred path from the root. Since the in order traversal of the aux
# tree represents the path, the leftmost node will be the root of the represented tree.
def getRoot(self, node):
self.access(node)
while node.left is not None:
node = node.left
# splay the node (now root) so the cost of sequential requests for the same root is amortized O(lg n)
node.splay()
return node

# path aggregate follows the path in the represented tree from root to the chosen node
# (under the hood this means traversing the aux tree containing the chosen node in order)
def pathAggregate(self, node, fn):
self.access(node)
node.inOrder(fn)

# lca(a,b) returns the least common ancestor of a and b. This works because after accessing a the last aux tree
# before b's tree becomes the root tree is the point at which the path from root to a and b diverges. This is
# because each aux jump basically follows a path parent pointer. So if we access a and make it the root aux tree,
# the access to b will eventually have to jump into that aux tree. The path pointer it uses to do that will point
# to the node at which a and b diverge.
def lca(self, a, b):
if self.getRoot(a) != self.getRoot(b):
return None
self.access(a)
return self.access(b)

133 changes: 133 additions & 0 deletions retroactive/basic/splay_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
class SplayNode(object):
"""Splay tree used by the link-cut tree."""

def __init__(self, data):
self.data = data
self.left = None
self.right = None
self.parent = None
# there can only be a represented edge between two nodes if one is the in order successor of the other
self.represented_parent = None
# stores the edge weight to the parent, since this is a tree this is enough to represent all edges uniquely
self.parent_edge_weight = float("inf")


def inOrder(self, fn):
if self.left is not None:
self.left.inOrder(fn)

fn(self.data)

if self.right is not None:
self.right.inOrder(fn)


def addLeft (self, other):
self.left = other
other.parent = self

def addRight (self, other):
self.right = other
other.parent = self

def isRoot(self):
return (self.parent == None or (self.parent.left != self and self.parent.right != self))

# copied from http://stevekrenzel.com/articles/printing-trees
def __str__(self, depth=0):
ret = ""

# Print right branch
if self.right != None:
ret += self.right.__str__(depth + 1)

# Print own value
ret += "\n" + (" "*depth) + str(self.data)

# Print left branch
if self.left != None:
ret += self.left.__str__(depth + 1)

return ret

def rotateRight(self):
if self.isRoot():
raise Exception("Trying to right rotate a root")
old_parent = self.parent

old_parent.left = self.right
if old_parent.left is not None:
old_parent.left.parent = old_parent

self.right = old_parent
self.parent = old_parent.parent
old_parent.parent = self

if self.parent is not None:
if self.parent.left == old_parent:
self.parent.left = self
elif self.parent.right == old_parent:
self.parent.right = self


def rotateLeft(self):
if self.isRoot():
raise Exception("Trying to right rotate a root")

old_parent = self.parent

old_parent.right = self.left
if old_parent.right is not None:
old_parent.right.parent = old_parent

self.left = old_parent
self.parent = old_parent.parent
old_parent.parent = self

if self.parent is not None:
# if neither of these cases is triggered it means self is the root of its splay tree and the parent pointer points to a path parent
if self.parent.left == old_parent:
self.parent.left = self
elif self.parent.right == old_parent:
self.parent.right = self


def splay(self):
while not self.isRoot():
if self.parent.isRoot():
if self.parent.left == self:
self.rotateRight()
elif self.parent.right == self:
self.rotateLeft()
else:
# this should never happen because an unacknowledged child is treated as a root, violating the loop condition
raise Exception("Splay: Attempting to rotate an unacknowledged (is not a left or right child) node ")
else:
# assert: grandparent != null because !parent.isRoot()
grandparent = self.parent.parent
if grandparent.left == self.parent:

if self.parent.left == self:
#zig-zig
self.parent.rotateRight()
self.rotateRight()
else:
#zig-zag
self.rotateLeft()
self.rotateRight()

elif grandparent.right == self.parent:
# assert: grandparent.right == self
if self.parent.right == self:
#zig-zig
self.parent.rotateLeft()
self.rotateLeft()
else:
#zig-zag
self.rotateRight()
self.rotateLeft()

else:
# this should never be thrown since a node without a grandparent should be caught
# in the first if statement in the loop (self.parent.isRoot())
raise Exception("Splay: grandparent is not attached to parent")
5 changes: 3 additions & 2 deletions retroactive/dispatcher.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from basic import PriorityQueue, Deque, Queue, UnionFind, Stack, SDPS
from partial import GeneralPartiallyRetroactive, PartiallyRetroactiveQueue, PartiallyRetroactiveSDPS
from full_retroactivity import GeneralFullyRetroactive
from retroactive.full import RetroactiveUnionFind


def PartiallyRetroactive(initstate):
"""
Expand Down Expand Up @@ -44,7 +46,6 @@ def FullyRetroactive(initstate):
elif isinstance(initstate, PriorityQueue):
return GeneralFullyRetroactive(PartiallyRetroactive(initstate))
elif isinstance(initstate, UnionFind):
##TODO: update once FR UnionFind is implemented
return GeneralFullyRetroactive(PartiallyRetroactive(initstate))
return RetroactiveUnionFind()
else:
return GeneralFullyRetroactive(PartiallyRetroactive(initstate))
21 changes: 21 additions & 0 deletions retroactive/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,28 @@ def testPartiallyRetroactiveSDPS():
print x.state
assert x.state == [1,2,3,4,5]

def testFullyRetroActiveUnionFind():
x = RetroactiveUnionFind()
# union a and b at the current time
x.unionAgo('a','b')
assert x.sameSetAgo('a', 'b', -2) == False

# union a and b two steps earlier
x.unionAgo('a', 'b' ,-2)
assert x.sameSetAgo('a', 'b', -3) == True

x.unionAgo('c','d')
assert x.sameSetAgo('b', 'd') == False

# union a and c before all other unions
x.unionAgo('a','c',-10)
assert x.sameSetAgo('b', 'd',-9) == False
assert x.sameSetAgo('b','d', 0) == True

def all_tests():
testPartiallyRetroactiveSDPS()
testPartiallyRetroactiveQueue()
testGeneralPartiallyRetroactive()
testFullyRetroActiveUnionFind()

all_tests()
Loading