Skip to content

Commit 8691a30

Browse files
committed
2 more tree/graph problems
1 parent ac0abc8 commit 8691a30

File tree

4 files changed

+170
-10
lines changed

4 files changed

+170
-10
lines changed

chapter_4/p4_1.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from typing import List
2+
from utils.graphs import Ditex
3+
from collections import deque
4+
5+
6+
def populate_graph(edges: List[tuple]) -> dict:
7+
graph = {}
8+
for a, b in edges:
9+
if a not in graph:
10+
graph[a] = Ditex(a)
11+
if b not in graph:
12+
graph[b] = Ditex(b)
13+
14+
graph[a].out_edges.add(b)
15+
graph[b].in_edges.add(a)
16+
return graph
17+
18+
19+
def route_digraph(edges: List[tuple], s: int, e: int):
20+
graph = populate_graph(edges)
21+
start_dq = deque()
22+
end_dq = deque()
23+
24+
seen_start = set([s])
25+
seen_end = set([e])
26+
27+
start_dq.append(s)
28+
end_dq.append(e)
29+
30+
round_robin_flag = True
31+
32+
while start_dq and end_dq:
33+
if round_robin_flag: # From start to end
34+
ce = graph[start_dq.popleft()]
35+
seen_start.add(ce.val)
36+
37+
if ce.val in seen_end:
38+
return True
39+
for child in ce.out_edges:
40+
if child not in seen_start:
41+
start_dq.append(child)
42+
else: # From end toward start
43+
ce = graph[end_dq.popleft()]
44+
seen_end.add(ce.val)
45+
46+
if ce.val in seen_start:
47+
return True
48+
49+
for parent in ce.in_edges:
50+
if parent not in seen_end:
51+
end_dq.append(parent)
52+
53+
round_robin_flag != round_robin_flag
54+
return False
55+
56+
57+
if __name__ == "__main__":
58+
edges = [(1, 11), (1, 12), (1, 14), (12, 23),
59+
(23, 2), (2, 21), (2, 22), (24, 2)]
60+
targets = (1, 2), (11, 24), (1, 22), (1, 11)
61+
for s, e in targets:
62+
print(
63+
f"Is there a route from {s} to {e} ? {route_digraph(edges, s,e)}")

chapter_4/p4_8.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from utils.graphs import BiNode, ltbt
2+
from typing import Tuple, Optional
3+
from utils.treeviz import viz_tree
4+
5+
# No parent, method 1 with covers
6+
7+
8+
def first_common_ancestor(r: BiNode, e1: int, e2: int):
9+
res_1 = com_anc_covers(r, e1, e2)
10+
res_2 = com_anc_helper(r, e1, e2)
11+
12+
print(f"Common anc, method 1 : {res_1}, "
13+
f"method 2: {res_2[0].val if res_2[1] else 'Not found'}")
14+
return res_2[0].val if res_2[1] else None
15+
16+
17+
def com_anc_covers(n: BiNode,
18+
e1: int,
19+
e2: int) -> Optional[int]:
20+
if not n:
21+
return None
22+
23+
# If both are covered by the left/right subtree,
24+
# Look for solution there
25+
rc1 = covers(n.right, e1)
26+
rc2 = covers(n.right, e2)
27+
if rc1 and rc2:
28+
return com_anc_covers(n.right, e1, e2)
29+
30+
lc1 = covers(n.left, e1)
31+
lc2 = covers(n.left, e2)
32+
if lc1 and lc2:
33+
return com_anc_covers(n.left, e1, e2)
34+
35+
# If current is one of them, and the other in a subtree
36+
if (n.val == e1 and (lc2 or rc2)) or (n.val == e2 and (lc1 or rc1)):
37+
return n.val
38+
39+
# If e1 and e2 covered by different subtrees, this is the place
40+
if (rc1 ^ rc2) and (lc1 ^ lc2):
41+
return n.val
42+
43+
44+
def covers(root: BiNode, elem: int):
45+
if not root:
46+
return False
47+
if root.val == elem:
48+
return True
49+
else:
50+
return covers(root.left, elem) or covers(root.right, elem)
51+
52+
# No parent, method 2
53+
# Send up the result of current search
54+
55+
56+
def com_anc_helper(root: BiNode,
57+
e1: int,
58+
e2: int) -> Tuple[Optional[BiNode], Optional[bool]]:
59+
if not root:
60+
return (None, None)
61+
62+
# If the ancestor is in the left (or right)subtree,
63+
# return that
64+
65+
left_res, left_has_anc = com_anc_helper(root.left, e1, e2)
66+
if left_res and left_has_anc:
67+
return (left_res, left_has_anc)
68+
69+
right_res, right_has_anc = com_anc_helper(root.right, e1, e2)
70+
if right_res and right_has_anc:
71+
return (right_res, right_has_anc)
72+
73+
# If both nodes found in the left and right trees
74+
# But not ancestors, this is the ancestor
75+
if left_res and right_res:
76+
return (root, True)
77+
else:
78+
if (root.val == e1 or root.val == e2):
79+
# Current is one of them and the other in subtrees
80+
is_anc = (left_res or right_res)
81+
return (root, is_anc)
82+
else:
83+
# Send up if one of target nodes found
84+
return (left_res if left_res else right_res, False)
85+
86+
87+
if __name__ == "__main__":
88+
ex = [2, 10, 30, 55, 15, None, None, 3, 37, None, 17]
89+
root = ltbt(ex)
90+
viz_tree(root)
91+
92+
first_common_ancestor(root, 17, 37)
93+
first_common_ancestor(root, 17, 37)
94+
first_common_ancestor(root, 55, 3)
95+
first_common_ancestor(root, 3, 55)
96+
first_common_ancestor(root, 2, 9999)

utils/binutils.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# Binary utilities
22

3-
def blen(a : int):
4-
return len(bin(a)) -2
3+
def blen(a: int):
4+
return len(bin(a)) - 2
5+
56

67
def ones(number: int):
7-
return (0b1 << (number)) -1
8+
return (0b1 << (number)) - 1
9+
810

911
if __name__ == "__main__":
10-
print(f"{ones(5):b}")
12+
print(f"{ones(5):b}")

utils/graphs.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def __str__(self):
1111
return f"Vertex({self.val}) with edges {len(self.edges)}"
1212

1313

14-
class Ditex(Vertex):
14+
class Ditex(Vertex): # Directed vertex
1515

1616
def __init__(self, val):
1717
self.val = val
@@ -27,10 +27,6 @@ def __str__(self):
2727

2828

2929
class BiNode:
30-
val: int = None
31-
left = None
32-
right = None
33-
3430
def __str__(self):
3531
return f"({self.left if self.left else '_' }/{self.val}\\{self.right if self.right else '_'})"
3632

@@ -41,7 +37,10 @@ def __init__(self, val, left=None, right=None):
4137

4238

4339
class BiPaNode(BiNode):
44-
parent = None
40+
41+
def __init__(self, val, left=None, right=None):
42+
super().__init__(val, left=left, right=right)
43+
self.parent = None
4544

4645

4746
def ltbt(arr: List[int]) -> BiNode:

0 commit comments

Comments
 (0)