From 259c452b2aa9e957349c256827ff6efd06c9d8ad Mon Sep 17 00:00:00 2001 From: Adrito-M Date: Fri, 21 Oct 2022 05:09:52 +0530 Subject: [PATCH 1/4] Algorithm: BinaryLifting --- Graphs/BinaryLifting.js | 99 +++++++++++++++++++++++++++++++ Graphs/test/BinaryLifting.test.js | 82 +++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 Graphs/BinaryLifting.js create mode 100644 Graphs/test/BinaryLifting.test.js diff --git a/Graphs/BinaryLifting.js b/Graphs/BinaryLifting.js new file mode 100644 index 0000000000..d81a929aec --- /dev/null +++ b/Graphs/BinaryLifting.js @@ -0,0 +1,99 @@ +/** + * Author: Adrito Mukherjee + * Binary Lifting implementation in Javascript + * Binary Lifting is a technique that is used to find the kth ancestor of a node in a rooted tree with N nodes + * The technique requires preprocessing the tree in O(N log N) using dynamic programming + * The techniqe can answer Q queries about kth ancestor of any node in O(Q log N) + * It is faster than the naive algorithm that answers Q queries with complexity O(Q K) + * It can be used to find Lowest Common Ancestor of two nodes in O(log N) + */ + +class BinaryLifting { + constructor (root, tree) { + this.connections = {} + this.up = {} // up[node][i] stores the 2^i-th parent of node + for (const [i, j] of tree) { + this.addEdge(i, j) + } + // LOG should be such that 2^LOG is greater than total number of nodes in the tree + this.LOG = 0 + while ((1 << this.LOG) <= Object.keys(this.connections).length) { + this.LOG++ + } + this.dfs(root, root) + } + + addNode (node) { + // Function to add a node to the tree (connection represented by set) + this.connections[node] = new Set() + } + + addEdge (node1, node2) { + // Function to add an edge (adds the node too if they are not present in the tree) + if (!(node1 in this.connections)) { + this.addNode(node1) + } + if (!(node2 in this.connections)) { + this.addNode(node2) + } + this.connections[node1].add(node2) + this.connections[node2].add(node1) + } + + dfs (node, parent) { + this.up[node] = {} + this.up[node][0] = parent + for (let i = 1; i < this.LOG; i++) { + this.up[node][i] = this.up[this.up[node][i - 1]][i - 1] + } + for (const child of this.connections[node]) { + if (child !== parent) this.dfs(child, node) + } + } + + kthAncestor (node, k) { + for (let i = 0; i < this.LOG; i++) { + if (k & (1 << i)) { + node = this.up[node][i] + } + } + return node + } +} + +function binaryLifting (root, tree, queries) { + const graphObject = new BinaryLifting(root, tree) + const ancestors = [] + for (const [node, k] of queries) { + const ancestor = graphObject.kthAncestor(node, k) + ancestors.push(ancestor) + } + return ancestors +} + +export { binaryLifting } + +// binaryLifting( +// 0, +// [ +// [0, 1], +// [0, 3], +// [0, 5], +// [5, 6], +// [1, 2], +// [1, 4], +// [4, 7], +// [7, 11], +// [7, 8], +// [8, 9], +// [9, 10] +// ], +// [ +// [10, 4], +// [10, 7], +// [7, 2], +// [11, 3] +// ] +// ) + +// [4, 0, 1, 1] diff --git a/Graphs/test/BinaryLifting.test.js b/Graphs/test/BinaryLifting.test.js new file mode 100644 index 0000000000..33db1e6f16 --- /dev/null +++ b/Graphs/test/BinaryLifting.test.js @@ -0,0 +1,82 @@ +import { binaryLifting } from '../BinaryLifting' + +// 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 = [ + [2, 1], + [6, 1], + [7, 2], + [8, 2], + [10, 2], + [10, 3], + [10, 5], + [11, 3] + ] + const kthAncestors = binaryLifting(root, graph, queries) + expect(kthAncestors).toEqual([1, 5, 1, 4, 8, 7, 1, 1]) +}) + +// 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 = [ + [2, 1], + [3, 1], + [3, 2], + [6, 2], + [7, 3], + [8, 2], + [8, 3] + ] + const kthAncestors = binaryLifting(root, graph, queries) + expect(kthAncestors).toEqual([0, 1, 0, 1, 0, 2, 0]) +}) From 20ca12d84f9bfc753fce2cd25edc2dbd57aad07c Mon Sep 17 00:00:00 2001 From: Adrito Mukherjee <98008131+Adrito-M@users.noreply.github.com> Date: Fri, 21 Oct 2022 05:14:21 +0530 Subject: [PATCH 2/4] Update BinaryLifting.js --- Graphs/BinaryLifting.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Graphs/BinaryLifting.js b/Graphs/BinaryLifting.js index d81a929aec..3e96d255ec 100644 --- a/Graphs/BinaryLifting.js +++ b/Graphs/BinaryLifting.js @@ -6,6 +6,7 @@ * The techniqe can answer Q queries about kth ancestor of any node in O(Q log N) * It is faster than the naive algorithm that answers Q queries with complexity O(Q K) * It can be used to find Lowest Common Ancestor of two nodes in O(log N) + * Tutorial on Binary Lifting: https://codeforces.com/blog/entry/100826 */ class BinaryLifting { From d164c323179bd410642294e63076d04352b3435e Mon Sep 17 00:00:00 2001 From: Adrito-M Date: Thu, 27 Oct 2022 18:10:26 +0530 Subject: [PATCH 3/4] made the requested changes --- Graphs/BinaryLifting.js | 67 ++++++++++--------------------- Graphs/test/BinaryLifting.test.js | 2 +- 2 files changed, 23 insertions(+), 46 deletions(-) diff --git a/Graphs/BinaryLifting.js b/Graphs/BinaryLifting.js index 3e96d255ec..0e616fbc28 100644 --- a/Graphs/BinaryLifting.js +++ b/Graphs/BinaryLifting.js @@ -11,51 +11,53 @@ class BinaryLifting { constructor (root, tree) { - this.connections = {} - this.up = {} // up[node][i] stores the 2^i-th parent of node + this.root = root + this.connections = new Map() + this.up = new Map() // up[node][i] stores the 2^i-th parent of node for (const [i, j] of tree) { this.addEdge(i, j) } - // LOG should be such that 2^LOG is greater than total number of nodes in the tree - this.LOG = 0 - while ((1 << this.LOG) <= Object.keys(this.connections).length) { - this.LOG++ - } + this.log = Math.ceil(Math.log2(this.connections.size)) this.dfs(root, root) } addNode (node) { // Function to add a node to the tree (connection represented by set) - this.connections[node] = new Set() + this.connections.set(node, new Set()) } addEdge (node1, node2) { // Function to add an edge (adds the node too if they are not present in the tree) - if (!(node1 in this.connections)) { + if (!this.connections.has(node1)) { this.addNode(node1) } - if (!(node2 in this.connections)) { + if (!this.connections.has(node2)) { this.addNode(node2) } - this.connections[node1].add(node2) - this.connections[node2].add(node1) + this.connections.get(node1).add(node2) + this.connections.get(node2).add(node1) } dfs (node, parent) { - this.up[node] = {} - this.up[node][0] = parent - for (let i = 1; i < this.LOG; i++) { - this.up[node][i] = this.up[this.up[node][i - 1]][i - 1] + this.up.set(node, new Map()) + this.up.get(node).set(0, parent) + for (let i = 1; i < this.log; i++) { + this.up + .get(node) + .set(i, this.up.get(this.up.get(node).get(i - 1)).get(i - 1)) } - for (const child of this.connections[node]) { + for (const child of this.connections.get(node)) { if (child !== parent) this.dfs(child, node) } } kthAncestor (node, k) { - for (let i = 0; i < this.LOG; i++) { + if (k >= this.connections.size) { + return this.root + } + for (let i = 0; i < this.log; i++) { if (k & (1 << i)) { - node = this.up[node][i] + node = this.up.get(node).get(i) } } return node @@ -72,29 +74,4 @@ function binaryLifting (root, tree, queries) { return ancestors } -export { binaryLifting } - -// binaryLifting( -// 0, -// [ -// [0, 1], -// [0, 3], -// [0, 5], -// [5, 6], -// [1, 2], -// [1, 4], -// [4, 7], -// [7, 11], -// [7, 8], -// [8, 9], -// [9, 10] -// ], -// [ -// [10, 4], -// [10, 7], -// [7, 2], -// [11, 3] -// ] -// ) - -// [4, 0, 1, 1] +export default binaryLifting diff --git a/Graphs/test/BinaryLifting.test.js b/Graphs/test/BinaryLifting.test.js index 33db1e6f16..769b877e01 100644 --- a/Graphs/test/BinaryLifting.test.js +++ b/Graphs/test/BinaryLifting.test.js @@ -1,4 +1,4 @@ -import { binaryLifting } from '../BinaryLifting' +import binaryLifting from '../BinaryLifting' // The graph for Test Case 1 looks like this: // From d9667a6097b6d1edd03c3aade727e33ffe4d3d32 Mon Sep 17 00:00:00 2001 From: Adrito-M Date: Thu, 27 Oct 2022 18:24:09 +0530 Subject: [PATCH 4/4] added more comments --- Graphs/BinaryLifting.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Graphs/BinaryLifting.js b/Graphs/BinaryLifting.js index 0e616fbc28..594a5b6676 100644 --- a/Graphs/BinaryLifting.js +++ b/Graphs/BinaryLifting.js @@ -39,6 +39,8 @@ class BinaryLifting { } dfs (node, parent) { + // The dfs function calculates 2^i-th ancestor of all nodes for i ranging from 0 to this.log + // We make use of the fact the two consecutive jumps of length 2^(i-1) make the total jump length 2^i this.up.set(node, new Map()) this.up.get(node).set(0, parent) for (let i = 1; i < this.log; i++) { @@ -52,9 +54,12 @@ class BinaryLifting { } kthAncestor (node, k) { + // if value of k is more than or equal to the number of total nodes, we return the root of the graph if (k >= this.connections.size) { return this.root } + // if i-th bit is set in the binary representation of k, we jump from a node to its 2^i-th ancestor + // so after checking all bits of k, we will have made jumps of total length k, in just log k steps for (let i = 0; i < this.log; i++) { if (k & (1 << i)) { node = this.up.get(node).get(i) 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