Skip to content

Commit

Permalink
Added BCC and binary lifting
Browse files Browse the repository at this point in the history
  • Loading branch information
bjorn-martinsson committed Sep 25, 2023
1 parent 7f54a87 commit d55d809
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 0 deletions.
52 changes: 52 additions & 0 deletions pyrival/graphs/bcc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Given a directed graph, find_SCC returns a list of lists containing
the strongly connected components in topological order.
Note that this implementation can be also be used to check if a directed graph is a
DAG, and in that case it can be used to find the topological ordering of the nodes.
"""

def find_SCC(graph):
SCC, S, P = [], [], []
depth = [0] * len(graph)

stack = list(range(len(graph)))
while stack:
node = stack.pop()
if node < 0:
d = depth[~node] - 1
if P[-1] > d:
SCC.append(S[d:])
del S[d:], P[-1]
for node in SCC[-1]:
depth[node] = -1
elif depth[node] > 0:
while P[-1] > depth[node]:
P.pop()
elif depth[node] == 0:
S.append(node)
P.append(len(S))
depth[node] = len(S)
stack.append(~node)
stack += graph[node]
return SCC[::-1]

"""
Given an undirected simple graph, find_BCC returns a list of lists
containing the biconnected components of the graph. Runs in O(n + m) time.
"""
def find_BCC(graph):
d = 0
depth = [0] * len(graph)
stack = list(range(len(graph)))
while stack:
node = stack.pop()
if node < 0:
d -= 1
elif not depth[node]:
d = depth[node] = d + 1
stack.append(~node)
stack += graph[node]

graph2 = [[v for v in g if d + 2 > depth[v] != d - 1] for g,d in zip(graph, depth)]
return find_SCC(graph2)
106 changes: 106 additions & 0 deletions pyrival/graphs/binary_lifting_on_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
Binary lifting technique applied to a tree.
There are three different uses of this implementation
1. Computing LCA in O(log n) time.
Example:
0
|
1
/ \
2 3
graph = [[1], [0, 2, 3], [1], [1]]
BL = binary_lift(graph, root=0)
print(BL.lca(2, 3)) # prints 1
2. Compute the distance between two nodes in O(log n) time.
Example:
graph = [[1], [0, 2, 3], [1], [1]]
BL = binary_lift(graph)
print(BL.distance(2, 3)) # prints 2
3. Compute the sum/min/max/... of the weight
of a path between a pair of nodes in O(log n) time.
res = Path[0]
for node in Path[1:]:
res = f(res, node)
return res
Example:
graph = [[1], [0, 2, 3], [1], [1]]
data = [1, 10, 20, 5]
BL = binary_lift(graph, data, f = lambda a,b: a + b)
print(BL(2, 3)) # prints 35
"""

class binary_lift:
def __init__(self, graph, data=(), f=min, root=0):
n = len(graph)

parent = [-1] * (n + 1)
depth = self.depth = [-1] * n
bfs = [root]
depth[root] = 0
for node in bfs:
for nei in graph[node]:
if depth[nei] == -1:
parent[nei] = node
depth[nei] = depth[node] + 1
bfs.append(nei)

data = self.data = [data]
parent = self.parent = [parent]
self.f = f

for _ in range(max(depth).bit_length()):
old_data = data[-1]
old_parent = parent[-1]

data.append([f(val, old_data[p]) for val,p in zip(old_data, old_parent)])
parent.append([old_parent[p] for p in old_parent])

def lca(self, a, b):
depth = self.depth
parent = self.parent

if depth[a] < depth[b]:
a,b = b,a

d = depth[a] - depth[b]
for i in range(d.bit_length()):
if (d >> i) & 1:
a = parent[i][a]

for i in range(depth[a].bit_length())[::-1]:
if parent[i][a] != parent[i][b]:
a = parent[i][a]
b = parent[i][b]

if a != b:
return parent[0][a]
else:
return a

def distance(self, a, b):
return self.depth[a] + self.depth[b] - 2 * self.depth[self.lca(a,b)]

def __call__(self, a, b):
depth = self.depth
parent = self.parent
data = self.data
f = self.f

c = self.lca(a, b)
val = data[0][c]
for x,d in (a, depth[a] - depth[c]), (b, depth[b] - depth[c]):
for i in range(d.bit_length()):
if (d >> i) & 1:
val = f(val, data[i][x])
x = parent[i][x]

return val

0 comments on commit d55d809

Please sign in to comment.