Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Graphs/BinaryLifting.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* Tutorial on Binary Lifting: https://codeforces.com/blog/entry/100826
*/

class BinaryLifting {
export class BinaryLifting {
constructor (root, tree) {
this.root = root
this.connections = new Map()
Expand Down
64 changes: 64 additions & 0 deletions Graphs/LCABinaryLifting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Author: Adrito Mukherjee
* Findind Lowest Common Ancestor By Binary Lifting implementation in JavaScript
* The technique requires preprocessing the tree in O(N log N) using dynamic programming)
* It can be used to find Lowest Common Ancestor of two nodes in O(log N)
* Tutorial on Lowest Common Ancestor: https://www.geeksforgeeks.org/lca-in-a-tree-using-binary-lifting-technique
*/

import { BinaryLifting } from './BinaryLifting'

class LCABinaryLifting {
constructor (root, tree) {
this.binaryLifting = new BinaryLifting(root, tree)
this.log = this.binaryLifting.log
this.connections = this.binaryLifting.connections
this.up = this.binaryLifting.up
this.kthAncestor = this.binaryLifting.kthAncestor
this.depth = new Map() // depth[node] stores the depth of node from root
this.depth.set(root, 1)
this.dfs(root, root)
}

dfs (node, parent) {
for (const child of this.connections.get(node)) {
if (child !== parent) {
this.depth.set(child, this.depth.get(node) + 1)
this.dfs(child, node)
}
}
}

getLCA (node1, node2) {
// We make sure that node1 is the deeper node among node1 and node2
if (this.depth.get(node1) < this.depth.get(node2)) {
[node1, node2] = [node2, node1]
}
// We check if node1 is the ancestor of node2, and if so, then return node1
const k = this.depth.get(node1) - this.depth.get(node2)
node1 = this.kthAncestor(node1, k)
if (node1 === node2) {
return node1
}

for (let i = this.log - 1; i >= 0; i--) {
if (this.up.get(node1).get(i) !== this.up.get(node2).get(i)) {
node1 = this.up.get(node1).get(i)
node2 = this.up.get(node2).get(i)
}
}
return this.up.get(node1).get(0)
}
}

function lcaBinaryLifting (root, tree, queries) {
const graphObject = new LCABinaryLifting(root, tree)
const lowestCommonAncestors = []
for (const [node1, node2] of queries) {
const lca = graphObject.getLCA(node1, node2)
lowestCommonAncestors.push(lca)
}
return lowestCommonAncestors
}

export default lcaBinaryLifting
80 changes: 80 additions & 0 deletions Graphs/test/LCABinaryLifting.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import lcaBinaryLifting from '../LCABinaryLifting'

// The graph for Test Case 1 looks like this:
//
// 0
// /|\
// / | \
// 1 3 5
// / \ \
// 2 4 6
// \
// 7
// / \
// 11 8
// \
// 9
// \
// 10

test('Test case 1', () => {
const root = 0
const graph = [
[0, 1],
[0, 3],
[0, 5],
[5, 6],
[1, 2],
[1, 4],
[4, 7],
[7, 11],
[7, 8],
[8, 9],
[9, 10]
]
const queries = [
[1, 3],
[6, 5],
[3, 6],
[7, 10],
[8, 10],
[11, 2],
[11, 10]
]
const kthAncestors = lcaBinaryLifting(root, graph, queries)
expect(kthAncestors).toEqual([0, 5, 0, 7, 8, 1, 7])
})

// The graph for Test Case 2 looks like this:
//
// 0
// / \
// 1 2
// / \ \
// 3 4 5
// / / \
// 6 7 8

test('Test case 2', () => {
const root = 0
const graph = [
[0, 1],
[0, 2],
[1, 3],
[1, 4],
[2, 5],
[3, 6],
[5, 7],
[5, 8]
]
const queries = [
[1, 2],
[3, 4],
[5, 4],
[6, 7],
[6, 8],
[7, 8]
]
const kthAncestors = lcaBinaryLifting(root, graph, queries)
expect(kthAncestors).toEqual([0, 1, 0, 0, 0, 5])
})