Skip to content

Commit d05e013

Browse files
authored
feat: Lowest Common Ancestor (LCA) algorithm (#512)
1 parent 3a41a76 commit d05e013

File tree

3 files changed

+418
-37
lines changed

3 files changed

+418
-37
lines changed

README.md

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,13 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute.
9393
2. [`BitCounter`](./math/binary/bitcounter.go#L11): BitCounter - The function returns the number of set bits for an unsigned integer number
9494
3. [`IsPowerOfTwo`](./math/binary/checkisnumberpoweroftwo.go#L19): IsPowerOfTwo This function uses the fact that powers of 2 are represented like 10...0 in binary, and numbers one less than the power of 2 are represented like 11...1. Therefore, using the and function: 10...0 & 01...1 00...0 -> 0 This is also true for 0, which is not a power of 2, for which we have to add and extra condition.
9595
4. [`IsPowerOfTwoLeftShift`](./math/binary/checkisnumberpoweroftwo.go#L26): IsPowerOfTwoLeftShift This function takes advantage of the fact that left shifting a number by 1 is equivalent to multiplying by 2. For example, binary 00000001 when shifted by 3 becomes 00001000, which in decimal system is 8 or = 2 * 2 * 2
96-
5. [`MeanUsingAndXor`](./math/binary/arithmeticmean.go#L12): MeanUsingAndXor This function finds arithmetic mean using "AND" and "XOR" operations
97-
6. [`MeanUsingRightShift`](./math/binary/arithmeticmean.go#L17): MeanUsingRightShift This function finds arithmetic mean using right shift
98-
7. [`ReverseBits`](./math/binary/reversebits.go#L14): ReverseBits This function initialized the result by 0 (all bits 0) and process the given number starting from its least significant bit. If the current bit is 1, set the corresponding most significant bit in the result and finally move on to the next bit in the input number. Repeat this till all its bits are processed.
99-
8. [`SequenceGrayCode`](./math/binary/rbc.go#L11): SequenceGrayCode The function generates an "Gray code" sequence of length n
100-
9. [`Sqrt`](./math/binary/sqrt.go#L16): No description provided.
101-
10. [`XorSearchMissingNumber`](./math/binary/xorsearch.go#L11): XorSearchMissingNumber This function finds a missing number in a sequence
96+
5. [`LogBase2`](./math/binary/logarithm.go#L7): LogBase2 Finding the exponent of n = 2**x using bitwise operations (logarithm in base 2 of n) [See more](https://en.wikipedia.org/wiki/Logarithm)
97+
6. [`MeanUsingAndXor`](./math/binary/arithmeticmean.go#L12): MeanUsingAndXor This function finds arithmetic mean using "AND" and "XOR" operations
98+
7. [`MeanUsingRightShift`](./math/binary/arithmeticmean.go#L17): MeanUsingRightShift This function finds arithmetic mean using right shift
99+
8. [`ReverseBits`](./math/binary/reversebits.go#L14): ReverseBits This function initialized the result by 0 (all bits 0) and process the given number starting from its least significant bit. If the current bit is 1, set the corresponding most significant bit in the result and finally move on to the next bit in the input number. Repeat this till all its bits are processed.
100+
9. [`SequenceGrayCode`](./math/binary/rbc.go#L11): SequenceGrayCode The function generates an "Gray code" sequence of length n
101+
10. [`Sqrt`](./math/binary/sqrt.go#L16): No description provided.
102+
11. [`XorSearchMissingNumber`](./math/binary/xorsearch.go#L11): XorSearchMissingNumber This function finds a missing number in a sequence
102103

103104
---
104105
</details><details>
@@ -256,24 +257,25 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute.
256257
---
257258
##### Functions:
258259

259-
1. [`Bin2`](./dynamic/binomialcoefficient.go#L21): Bin2 function
260-
2. [`CoinChange`](./dynamic/coinchange.go#L5): CoinChange finds the number of possible combinations of coins of different values which can get to the target amount.
261-
3. [`CutRodDp`](./dynamic/rodcutting.go#L21): CutRodDp solve the same problem using dynamic programming
262-
4. [`CutRodRec`](./dynamic/rodcutting.go#L8): CutRodRec solve the problem recursively: initial approach
263-
5. [`EditDistanceDP`](./dynamic/editdistance.go#L35): EditDistanceDP is an optimised implementation which builds on the ideas of the recursive implementation. We use dynamic programming to compute the DP table where dp[i][j] denotes the edit distance value of first[0..i-1] and second[0..j-1]. Time complexity is O(m * n) where m and n are lengths of the strings, first and second respectively.
264-
6. [`EditDistanceRecursive`](./dynamic/editdistance.go#L10): EditDistanceRecursive is a naive implementation with exponential time complexity.
265-
7. [`IsSubsetSum`](./dynamic/subsetsum.go#L14): No description provided.
266-
8. [`Knapsack`](./dynamic/knapsack.go#L17): Knapsack solves knapsack problem return maxProfit
267-
9. [`LongestCommonSubsequence`](./dynamic/longestcommonsubsequence.go#L8): LongestCommonSubsequence function
268-
10. [`LongestIncreasingSubsequence`](./dynamic/longestincreasingsubsequence.go#L9): LongestIncreasingSubsequence returns the longest increasing subsequence where all elements of the subsequence are sorted in increasing order
269-
11. [`LongestIncreasingSubsequenceGreedy`](./dynamic/longestincreasingsubsequencegreedy.go#L9): LongestIncreasingSubsequenceGreedy is a function to find the longest increasing subsequence in a given array using a greedy approach. The dynamic programming approach is implemented alongside this one. Worst Case Time Complexity: O(nlogn) Auxiliary Space: O(n), where n is the length of the array(slice). Reference: https://www.geeksforgeeks.org/construction-of-longest-monotonically-increasing-subsequence-n-log-n/
270-
12. [`LpsDp`](./dynamic/longestpalindromicsubsequence.go#L21): LpsDp function
271-
13. [`LpsRec`](./dynamic/longestpalindromicsubsequence.go#L7): LpsRec function
272-
14. [`MatrixChainDp`](./dynamic/matrixmultiplication.go#L24): MatrixChainDp function
273-
15. [`MatrixChainRec`](./dynamic/matrixmultiplication.go#L10): MatrixChainRec function
274-
16. [`Max`](./dynamic/knapsack.go#L11): Max function - possible duplicate
275-
17. [`NthCatalanNumber`](./dynamic/catalan.go#L13): NthCatalan returns the n-th Catalan Number Complexity: O(n²)
276-
18. [`NthFibonacci`](./dynamic/fibonacci.go#L6): NthFibonacci returns the nth Fibonacci Number
260+
1. [`Abbreviation`](./dynamic/abbreviation.go#L24): Returns true if it is possible to make a equals b (if b is an abbreviation of a), returns false otherwise
261+
2. [`Bin2`](./dynamic/binomialcoefficient.go#L21): Bin2 function
262+
3. [`CoinChange`](./dynamic/coinchange.go#L5): CoinChange finds the number of possible combinations of coins of different values which can get to the target amount.
263+
4. [`CutRodDp`](./dynamic/rodcutting.go#L21): CutRodDp solve the same problem using dynamic programming
264+
5. [`CutRodRec`](./dynamic/rodcutting.go#L8): CutRodRec solve the problem recursively: initial approach
265+
6. [`EditDistanceDP`](./dynamic/editdistance.go#L35): EditDistanceDP is an optimised implementation which builds on the ideas of the recursive implementation. We use dynamic programming to compute the DP table where dp[i][j] denotes the edit distance value of first[0..i-1] and second[0..j-1]. Time complexity is O(m * n) where m and n are lengths of the strings, first and second respectively.
266+
7. [`EditDistanceRecursive`](./dynamic/editdistance.go#L10): EditDistanceRecursive is a naive implementation with exponential time complexity.
267+
8. [`IsSubsetSum`](./dynamic/subsetsum.go#L14): No description provided.
268+
9. [`Knapsack`](./dynamic/knapsack.go#L17): Knapsack solves knapsack problem return maxProfit
269+
10. [`LongestCommonSubsequence`](./dynamic/longestcommonsubsequence.go#L8): LongestCommonSubsequence function
270+
11. [`LongestIncreasingSubsequence`](./dynamic/longestincreasingsubsequence.go#L9): LongestIncreasingSubsequence returns the longest increasing subsequence where all elements of the subsequence are sorted in increasing order
271+
12. [`LongestIncreasingSubsequenceGreedy`](./dynamic/longestincreasingsubsequencegreedy.go#L9): LongestIncreasingSubsequenceGreedy is a function to find the longest increasing subsequence in a given array using a greedy approach. The dynamic programming approach is implemented alongside this one. Worst Case Time Complexity: O(nlogn) Auxiliary Space: O(n), where n is the length of the array(slice). Reference: https://www.geeksforgeeks.org/construction-of-longest-monotonically-increasing-subsequence-n-log-n/
272+
13. [`LpsDp`](./dynamic/longestpalindromicsubsequence.go#L21): LpsDp function
273+
14. [`LpsRec`](./dynamic/longestpalindromicsubsequence.go#L7): LpsRec function
274+
15. [`MatrixChainDp`](./dynamic/matrixmultiplication.go#L24): MatrixChainDp function
275+
16. [`MatrixChainRec`](./dynamic/matrixmultiplication.go#L10): MatrixChainRec function
276+
17. [`Max`](./dynamic/knapsack.go#L11): Max function - possible duplicate
277+
18. [`NthCatalanNumber`](./dynamic/catalan.go#L13): NthCatalan returns the n-th Catalan Number Complexity: O(n²)
278+
19. [`NthFibonacci`](./dynamic/fibonacci.go#L6): NthFibonacci returns the nth Fibonacci Number
277279

278280
---
279281
</details><details>
@@ -411,10 +413,12 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute.
411413
5. [`FloydWarshall`](./graph/floydwarshall.go#L15): FloydWarshall Returns all pair's shortest path using Floyd Warshall algorithm
412414
6. [`GetIdx`](./graph/depthfirstsearch.go#L3): No description provided.
413415
7. [`KruskalMST`](./graph/kruskal.go#L87): KruskalMST will return a minimum spanning tree along with its total cost to using Kruskal's algorithm. Time complexity is O(m * log (n)) where m is the number of edges in the graph and n is number of nodes in it.
414-
8. [`New`](./graph/graph.go#L16): Constructor functions for graphs (undirected by default)
415-
9. [`NewDSU`](./graph/kruskal.go#L34): NewDSU will return an initialised DSU using the value of n which will be treated as the number of elements out of which the DSU is being made
416-
10. [`NotExist`](./graph/depthfirstsearch.go#L12): No description provided.
417-
11. [`Topological`](./graph/topological.go#L7): Assumes that graph given is valid and possible to get a topo ordering. constraints are array of []int{a, b}, representing an edge going from a to b
416+
8. [`LowestCommonAncestor`](./graph/lowestcommonancestor.go#L111): For each node, we will precompute its ancestor above him, its ancestor two nodes above, its ancestor four nodes above, etc. Let's call `jump[j][u]` is the `2^j`-th ancestor above the node `u` with `u` in range `[0, numbersVertex)`, `j` in range `[0,MAXLOG)`. These information allow us to jump from any node to any ancestor above it in `O(MAXLOG)` time.
417+
9. [`New`](./graph/graph.go#L16): Constructor functions for graphs (undirected by default)
418+
10. [`NewDSU`](./graph/kruskal.go#L34): NewDSU will return an initialised DSU using the value of n which will be treated as the number of elements out of which the DSU is being made
419+
11. [`NewTree`](./graph/lowestcommonancestor.go#L84): No description provided.
420+
12. [`NotExist`](./graph/depthfirstsearch.go#L12): No description provided.
421+
13. [`Topological`](./graph/topological.go#L7): Assumes that graph given is valid and possible to get a topo ordering. constraints are array of []int{a, b}, representing an edge going from a to b
418422

419423
---
420424
##### Types
@@ -429,7 +433,13 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute.
429433

430434
5. [`Item`](./graph/dijkstra.go#L5): No description provided.
431435

432-
6. [`WeightedGraph`](./graph/floydwarshall.go#L9): No description provided.
436+
6. [`Query`](./graph/lowestcommonancestor_test.go#L9): No description provided.
437+
438+
7. [`Tree`](./graph/lowestcommonancestor.go#L25): No description provided.
439+
440+
8. [`TreeEdge`](./graph/lowestcommonancestor.go#L12): No description provided.
441+
442+
9. [`WeightedGraph`](./graph/floydwarshall.go#L9): No description provided.
433443

434444

435445
---
@@ -738,14 +748,14 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute.
738748
1. [`Factorize`](./math/prime/primefactorization.go#L5): Factorize is a function that computes the exponents of each prime in the prime factorization of n
739749
2. [`Generate`](./math/prime/sieve.go#L26): Generate returns a int slice of prime numbers up to the limit
740750
3. [`GenerateChannel`](./math/prime/sieve.go#L9): Generate generates the sequence of integers starting at 2 and sends it to the channel `ch`
741-
4. [`MillerRabinDeterministic`](./math/prime/millerrabinprimalitytest.go#L121): MillerRabinDeterministic is a Deterministic version of the Miller-Rabin test, which returns correct results for all valid int64 numbers.
742-
5. [`MillerRabinProbabilistic`](./math/prime/millerrabinprimalitytest.go#L101): MillerRabinProbabilistic is a probabilistic test for primality of an integer based of the algorithm devised by Miller and Rabin.
743-
6. [`MillerRandomTest`](./math/prime/millerrabinprimalitytest.go#L77): MillerRandomTest This is the intermediate step that repeats within the miller rabin primality test for better probabilitic chances of receiving the correct result with random witnesses.
744-
7. [`MillerTest`](./math/prime/millerrabinprimalitytest.go#L49): MillerTest tests whether num is a strong probable prime to a witness. Formally: a^d ≡ 1 (mod n) or a^(2^r * d) ≡ -1 (mod n), 0 <= r <= s
745-
8. [`MillerTestMultiple`](./math/prime/millerrabinprimalitytest.go#L84): MillerTestMultiple is like MillerTest but runs the test for multiple witnesses.
746-
9. [`NaiveApproach`](./math/prime/primecheck.go#L8): NaiveApproach checks if an integer is prime or not. Returns a bool.
747-
10. [`PairApproach`](./math/prime/primecheck.go#L22): PairApproach checks primality of an integer and returns a bool. More efficient than the naive approach as number of iterations are less.
748-
11. [`Sieve`](./math/prime/sieve.go#L16): Sieve Sieving the numbers that are not prime from the channel - basically removing them from the channels
751+
4. [`MillerRabinDeterministic`](./math/prime/millerrabintest.go#L121): MillerRabinDeterministic is a Deterministic version of the Miller-Rabin test, which returns correct results for all valid int64 numbers.
752+
5. [`MillerRabinProbabilistic`](./math/prime/millerrabintest.go#L101): MillerRabinProbabilistic is a probabilistic test for primality of an integer based of the algorithm devised by Miller and Rabin.
753+
6. [`MillerRandomTest`](./math/prime/millerrabintest.go#L77): MillerRandomTest This is the intermediate step that repeats within the miller rabin primality test for better probabilitic chances of receiving the correct result with random witnesses.
754+
7. [`MillerTest`](./math/prime/millerrabintest.go#L49): MillerTest tests whether num is a strong probable prime to a witness. Formally: a^d ≡ 1 (mod n) or a^(2^r * d) ≡ -1 (mod n), 0 <= r <= s
755+
8. [`MillerTestMultiple`](./math/prime/millerrabintest.go#L84): MillerTestMultiple is like MillerTest but runs the test for multiple witnesses.
756+
9. [`OptimizedTrialDivision`](./math/prime/primecheck.go#L26): OptimizedTrialDivision checks primality of an integer using an optimized trial division method. The optimizations include not checking divisibility by the even numbers and only checking up to the square root of the given number.
757+
10. [`Sieve`](./math/prime/sieve.go#L16): Sieve Sieving the numbers that are not prime from the channel - basically removing them from the channels
758+
11. [`TrialDivision`](./math/prime/primecheck.go#L9): TrialDivision tests whether a number is prime by trying to divide it by the numbers less than it.
749759

750760
---
751761
</details><details>

graph/lowestcommonancestor.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// lowestcommonancestor.go
2+
// description: Implementation of Lowest common ancestor (LCA) algorithm.
3+
// detail:
4+
// Let `T` be a tree. The LCA of `u` and `v` in T is the shared ancestor of `u` and `v`
5+
// that is located farthest from the root.
6+
// references: [cp-algorithms](https://cp-algorithms.com/graph/lca_binary_lifting.html)
7+
// author(s) [Dat](https://github.com/datbeohbbh)
8+
// see lowestcommonancestor_test.go for a test implementation.
9+
10+
package graph
11+
12+
type TreeEdge struct {
13+
from int
14+
to int
15+
}
16+
17+
type ITree interface {
18+
dfs(int, int)
19+
addEdge(int, int)
20+
GetDepth(int) int
21+
GetDad(int) int
22+
GetLCA(int, int) int
23+
}
24+
25+
type Tree struct {
26+
numbersVertex int
27+
root int
28+
MAXLOG int
29+
depth []int
30+
dad []int
31+
jump [][]int
32+
edges [][]int
33+
}
34+
35+
func (tree *Tree) addEdge(u, v int) {
36+
tree.edges[u] = append(tree.edges[u], v)
37+
tree.edges[v] = append(tree.edges[v], u)
38+
}
39+
40+
func (tree *Tree) dfs(u, par int) {
41+
tree.jump[0][u] = par
42+
tree.dad[u] = par
43+
for _, v := range tree.edges[u] {
44+
if v != par {
45+
tree.depth[v] = tree.depth[u] + 1
46+
tree.dfs(v, u)
47+
}
48+
}
49+
}
50+
51+
func (tree *Tree) GetDepth(u int) int {
52+
return tree.depth[u]
53+
}
54+
55+
func (tree *Tree) GetDad(u int) int {
56+
return tree.dad[u]
57+
}
58+
59+
func (tree *Tree) GetLCA(u, v int) int {
60+
if tree.GetDepth(u) < tree.GetDepth(v) {
61+
u, v = v, u
62+
}
63+
64+
for j := tree.MAXLOG - 1; j >= 0; j-- {
65+
if tree.GetDepth(tree.jump[j][u]) >= tree.GetDepth(v) {
66+
u = tree.jump[j][u]
67+
}
68+
}
69+
70+
if u == v {
71+
return u
72+
}
73+
74+
for j := tree.MAXLOG - 1; j >= 0; j-- {
75+
if tree.jump[j][u] != tree.jump[j][v] {
76+
u = tree.jump[j][u]
77+
v = tree.jump[j][v]
78+
}
79+
}
80+
81+
return tree.jump[0][u]
82+
}
83+
84+
func NewTree(numbersVertex, root int, edges []TreeEdge) (tree *Tree) {
85+
tree = new(Tree)
86+
tree.numbersVertex, tree.root, tree.MAXLOG = numbersVertex, root, 0
87+
tree.depth = make([]int, numbersVertex)
88+
tree.dad = make([]int, numbersVertex)
89+
90+
for (1 << tree.MAXLOG) <= numbersVertex {
91+
(tree.MAXLOG) += 1
92+
}
93+
(tree.MAXLOG) += 1
94+
95+
tree.jump = make([][]int, tree.MAXLOG)
96+
for j := 0; j < tree.MAXLOG; j++ {
97+
tree.jump[j] = make([]int, numbersVertex)
98+
}
99+
100+
tree.edges = make([][]int, numbersVertex)
101+
for _, e := range edges {
102+
tree.addEdge(e.from, e.to)
103+
}
104+
105+
return tree
106+
}
107+
108+
// For each node, we will precompute its ancestor above him, its ancestor two nodes above, its ancestor four nodes above, etc.
109+
// Let's call `jump[j][u]` is the `2^j`-th ancestor above the node `u` with `u` in range `[0, numbersVertex)`, `j` in range `[0,MAXLOG)`.
110+
// These information allow us to jump from any node to any ancestor above it in `O(MAXLOG)` time.
111+
func LowestCommonAncestor(tree *Tree) {
112+
// call dfs to compute depth from the root to each node and the parent of each node.
113+
tree.dfs(tree.root, tree.root)
114+
115+
// compute jump[j][u]
116+
for j := 1; j < tree.MAXLOG; j++ {
117+
for u := 0; u < tree.numbersVertex; u++ {
118+
tree.jump[j][u] = tree.jump[j-1][tree.jump[j-1][u]]
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)