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

Lines changed: 82 additions & 0 deletions
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

Lines changed: 82 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy