Skip to content

Commit d9d085f

Browse files
authored
algorithm: binary lifting (TheAlgorithms#1218)
* Algorithm: BinaryLifting * Update BinaryLifting.js * made the requested changes * added more comments
1 parent 945657a commit d9d085f

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

Graphs/BinaryLifting.js

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Author: Adrito Mukherjee
3+
* Binary Lifting implementation in Javascript
4+
* Binary Lifting is a technique that is used to find the kth ancestor of a node in a rooted tree with N nodes
5+
* The technique requires preprocessing the tree in O(N log N) using dynamic programming
6+
* The techniqe can answer Q queries about kth ancestor of any node in O(Q log N)
7+
* It is faster than the naive algorithm that answers Q queries with complexity O(Q K)
8+
* It can be used to find Lowest Common Ancestor of two nodes in O(log N)
9+
* Tutorial on Binary Lifting: https://codeforces.com/blog/entry/100826
10+
*/
11+
12+
class BinaryLifting {
13+
constructor (root, tree) {
14+
this.root = root
15+
this.connections = new Map()
16+
this.up = new Map() // up[node][i] stores the 2^i-th parent of node
17+
for (const [i, j] of tree) {
18+
this.addEdge(i, j)
19+
}
20+
this.log = Math.ceil(Math.log2(this.connections.size))
21+
this.dfs(root, root)
22+
}
23+
24+
addNode (node) {
25+
// Function to add a node to the tree (connection represented by set)
26+
this.connections.set(node, new Set())
27+
}
28+
29+
addEdge (node1, node2) {
30+
// Function to add an edge (adds the node too if they are not present in the tree)
31+
if (!this.connections.has(node1)) {
32+
this.addNode(node1)
33+
}
34+
if (!this.connections.has(node2)) {
35+
this.addNode(node2)
36+
}
37+
this.connections.get(node1).add(node2)
38+
this.connections.get(node2).add(node1)
39+
}
40+
41+
dfs (node, parent) {
42+
// The dfs function calculates 2^i-th ancestor of all nodes for i ranging from 0 to this.log
43+
// We make use of the fact the two consecutive jumps of length 2^(i-1) make the total jump length 2^i
44+
this.up.set(node, new Map())
45+
this.up.get(node).set(0, parent)
46+
for (let i = 1; i < this.log; i++) {
47+
this.up
48+
.get(node)
49+
.set(i, this.up.get(this.up.get(node).get(i - 1)).get(i - 1))
50+
}
51+
for (const child of this.connections.get(node)) {
52+
if (child !== parent) this.dfs(child, node)
53+
}
54+
}
55+
56+
kthAncestor (node, k) {
57+
// if value of k is more than or equal to the number of total nodes, we return the root of the graph
58+
if (k >= this.connections.size) {
59+
return this.root
60+
}
61+
// if i-th bit is set in the binary representation of k, we jump from a node to its 2^i-th ancestor
62+
// so after checking all bits of k, we will have made jumps of total length k, in just log k steps
63+
for (let i = 0; i < this.log; i++) {
64+
if (k & (1 << i)) {
65+
node = this.up.get(node).get(i)
66+
}
67+
}
68+
return node
69+
}
70+
}
71+
72+
function binaryLifting (root, tree, queries) {
73+
const graphObject = new BinaryLifting(root, tree)
74+
const ancestors = []
75+
for (const [node, k] of queries) {
76+
const ancestor = graphObject.kthAncestor(node, k)
77+
ancestors.push(ancestor)
78+
}
79+
return ancestors
80+
}
81+
82+
export default binaryLifting

Graphs/test/BinaryLifting.test.js

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import binaryLifting from '../BinaryLifting'
2+
3+
// The graph for Test Case 1 looks like this:
4+
//
5+
// 0
6+
// /|\
7+
// / | \
8+
// 1 3 5
9+
// / \ \
10+
// 2 4 6
11+
// \
12+
// 7
13+
// / \
14+
// 11 8
15+
// \
16+
// 9
17+
// \
18+
// 10
19+
20+
test('Test case 1', () => {
21+
const root = 0
22+
const graph = [
23+
[0, 1],
24+
[0, 3],
25+
[0, 5],
26+
[5, 6],
27+
[1, 2],
28+
[1, 4],
29+
[4, 7],
30+
[7, 11],
31+
[7, 8],
32+
[8, 9],
33+
[9, 10]
34+
]
35+
const queries = [
36+
[2, 1],
37+
[6, 1],
38+
[7, 2],
39+
[8, 2],
40+
[10, 2],
41+
[10, 3],
42+
[10, 5],
43+
[11, 3]
44+
]
45+
const kthAncestors = binaryLifting(root, graph, queries)
46+
expect(kthAncestors).toEqual([1, 5, 1, 4, 8, 7, 1, 1])
47+
})
48+
49+
// The graph for Test Case 2 looks like this:
50+
//
51+
// 0
52+
// / \
53+
// 1 2
54+
// / \ \
55+
// 3 4 5
56+
// / / \
57+
// 6 7 8
58+
59+
test('Test case 2', () => {
60+
const root = 0
61+
const graph = [
62+
[0, 1],
63+
[0, 2],
64+
[1, 3],
65+
[1, 4],
66+
[2, 5],
67+
[3, 6],
68+
[5, 7],
69+
[5, 8]
70+
]
71+
const queries = [
72+
[2, 1],
73+
[3, 1],
74+
[3, 2],
75+
[6, 2],
76+
[7, 3],
77+
[8, 2],
78+
[8, 3]
79+
]
80+
const kthAncestors = binaryLifting(root, graph, queries)
81+
expect(kthAncestors).toEqual([0, 1, 0, 1, 0, 2, 0])
82+
})

0 commit comments

Comments
 (0)