From 847c5d82ae5da62e2dacbf965eca23cc43d41271 Mon Sep 17 00:00:00 2001 From: Changi Cho Date: Thu, 13 Oct 2022 22:36:39 +0900 Subject: [PATCH 1/6] feat: Create SegmentTree --- Data-Structures/Tree/SegmentTree.js | 59 +++++++++++++++++++ Data-Structures/Tree/test/SegmentTree.test.js | 16 +++++ 2 files changed, 75 insertions(+) create mode 100644 Data-Structures/Tree/SegmentTree.js create mode 100644 Data-Structures/Tree/test/SegmentTree.test.js diff --git a/Data-Structures/Tree/SegmentTree.js b/Data-Structures/Tree/SegmentTree.js new file mode 100644 index 0000000000..f35cd8b5bc --- /dev/null +++ b/Data-Structures/Tree/SegmentTree.js @@ -0,0 +1,59 @@ +class SegmentTree { + size + tree + constructor (arr) { + this.size = arr.length + this.tree = new Array(2 * this.size) + this.tree.fill(0) + + this.build(arr) + } + + build (arr) { + const n = this.size + // insert leaf nodes in tree + for (let i = 0; i < n; i++) { + this.tree[n + i] = arr[i] + } + + // build the tree by calculating parents + for (let i = n - 1; i > 0; --i) { + this.tree[i] = this.tree[i << 1] + this.tree[(i << 1) | 1] + } + } + + update (p, value) { + // set value at position p + this.tree[p + this.size] = value + p = p + this.size + + // move upward and update parents + for (let i = p; i > 1; i >>= 1) { + this.tree[i >> 1] = this.tree[i] + this.tree[i ^ 1] + } + } + + query (left, right) { + left-- + let res = 0 + + // loop to find the sum in the range + for ( + left += this.size, right += this.size; + left < right; + left >>= 1, right >>= 1 + ) { + if ((left & 1) > 0) { + res += this.tree[left++] + } + + if ((right & 1) > 0) { + res += this.tree[--right] + } + } + + return res + } +} + +export { SegmentTree } diff --git a/Data-Structures/Tree/test/SegmentTree.test.js b/Data-Structures/Tree/test/SegmentTree.test.js new file mode 100644 index 0000000000..7285292b36 --- /dev/null +++ b/Data-Structures/Tree/test/SegmentTree.test.js @@ -0,0 +1,16 @@ +import { SegmentTree } from '../SegmentTree' + +describe('SegmentTree sum test', () => { + const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + + const segment = new SegmentTree(a) + + it('init sum check', () => { + expect(segment.query(1, 3)).toBe(6) + }) + + it('init sum check', () => { + segment.update(2, 1) + expect(segment.query(1, 3)).toBe(4) + }) +}) From cd085747045dbd6b9150b1ee957b81cea947c50d Mon Sep 17 00:00:00 2001 From: Changi Cho Date: Fri, 14 Oct 2022 18:11:06 +0900 Subject: [PATCH 2/6] fix: Change query range index fit for left ~ right --- Data-Structures/Tree/SegmentTree.js | 2 +- Data-Structures/Tree/test/SegmentTree.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Data-Structures/Tree/SegmentTree.js b/Data-Structures/Tree/SegmentTree.js index f35cd8b5bc..77bc095595 100644 --- a/Data-Structures/Tree/SegmentTree.js +++ b/Data-Structures/Tree/SegmentTree.js @@ -34,7 +34,7 @@ class SegmentTree { } query (left, right) { - left-- + right++ let res = 0 // loop to find the sum in the range diff --git a/Data-Structures/Tree/test/SegmentTree.test.js b/Data-Structures/Tree/test/SegmentTree.test.js index 7285292b36..440fbd8283 100644 --- a/Data-Structures/Tree/test/SegmentTree.test.js +++ b/Data-Structures/Tree/test/SegmentTree.test.js @@ -6,11 +6,11 @@ describe('SegmentTree sum test', () => { const segment = new SegmentTree(a) it('init sum check', () => { - expect(segment.query(1, 3)).toBe(6) + expect(segment.query(0, 2)).toBe(6) }) it('init sum check', () => { segment.update(2, 1) - expect(segment.query(1, 3)).toBe(4) + expect(segment.query(0, 2)).toBe(4) }) }) From 904d22b800526e05cba6d8f50501d0c5f2257f7d Mon Sep 17 00:00:00 2001 From: Changi Cho Date: Fri, 14 Oct 2022 18:14:07 +0900 Subject: [PATCH 3/6] feat: Add comment --- Data-Structures/Tree/SegmentTree.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Data-Structures/Tree/SegmentTree.js b/Data-Structures/Tree/SegmentTree.js index 77bc095595..096751bca4 100644 --- a/Data-Structures/Tree/SegmentTree.js +++ b/Data-Structures/Tree/SegmentTree.js @@ -1,3 +1,15 @@ +/** + * Segment Tree + * concept : [Wikipedia](https://en.wikipedia.org/wiki/Segment_tree) + * inspired by : https://www.geeksforgeeks.org/segment-tree-efficient-implementation/ + * + * time complexity + * - init : O(N) + * - update : O(log(N)) + * - query : O(log(N)) + * + * space complexity : O(N) + */ class SegmentTree { size tree From 1a837f5277348546e06e77404a46c24cc3a1093a Mon Sep 17 00:00:00 2001 From: Changi Cho Date: Fri, 14 Oct 2022 23:51:54 +0900 Subject: [PATCH 4/6] refactor: Refactor property scope --- Data-Structures/Tree/SegmentTree.js | 31 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Data-Structures/Tree/SegmentTree.js b/Data-Structures/Tree/SegmentTree.js index 096751bca4..4206115a6b 100644 --- a/Data-Structures/Tree/SegmentTree.js +++ b/Data-Structures/Tree/SegmentTree.js @@ -15,52 +15,51 @@ class SegmentTree { tree constructor (arr) { this.size = arr.length - this.tree = new Array(2 * this.size) + this.tree = new Array(2 * arr.length) this.tree.fill(0) this.build(arr) } build (arr) { - const n = this.size + const { size, tree } = this // insert leaf nodes in tree - for (let i = 0; i < n; i++) { - this.tree[n + i] = arr[i] + for (let i = 0; i < size; i++) { + tree[size + i] = arr[i] } // build the tree by calculating parents - for (let i = n - 1; i > 0; --i) { - this.tree[i] = this.tree[i << 1] + this.tree[(i << 1) | 1] + for (let i = size - 1; i > 0; --i) { + tree[i] = tree[i << 1] + tree[(i << 1) | 1] } } update (p, value) { + const { size, tree } = this + // set value at position p - this.tree[p + this.size] = value - p = p + this.size + tree[p + size] = value + p += size // move upward and update parents for (let i = p; i > 1; i >>= 1) { - this.tree[i >> 1] = this.tree[i] + this.tree[i ^ 1] + tree[i >> 1] = tree[i] + tree[i ^ 1] } } query (left, right) { + const { size, tree } = this right++ let res = 0 // loop to find the sum in the range - for ( - left += this.size, right += this.size; - left < right; - left >>= 1, right >>= 1 - ) { + for (left += size, right += size; left < right; left >>= 1, right >>= 1) { if ((left & 1) > 0) { - res += this.tree[left++] + res += tree[left++] } if ((right & 1) > 0) { - res += this.tree[--right] + res += tree[--right] } } From cd57889645f24596f9284dffb22a81e792337cfa Mon Sep 17 00:00:00 2001 From: Changi Cho Date: Sat, 15 Oct 2022 00:27:14 +0900 Subject: [PATCH 5/6] feat: Add comments of implementation details --- Data-Structures/Tree/SegmentTree.js | 40 ++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/Data-Structures/Tree/SegmentTree.js b/Data-Structures/Tree/SegmentTree.js index 4206115a6b..90616baaba 100644 --- a/Data-Structures/Tree/SegmentTree.js +++ b/Data-Structures/Tree/SegmentTree.js @@ -14,6 +14,12 @@ class SegmentTree { size tree constructor (arr) { + // we define tree like this + // tree[1] : root node of tree + // tree[i] : i'th node + // tree[i * 2] : i'th left child + // tree[i * 2 + 1] : i'th right child + // and we use bit, shift operation for index this.size = arr.length this.tree = new Array(2 * arr.length) this.tree.fill(0) @@ -21,43 +27,65 @@ class SegmentTree { this.build(arr) } + // function to build the tree build (arr) { const { size, tree } = this // insert leaf nodes in tree + // leaf nodes will start from index N + // in this array and will go up to index (2 * N – 1) for (let i = 0; i < size; i++) { tree[size + i] = arr[i] } // build the tree by calculating parents + // tree's root node will contain all leaf node's sum for (let i = size - 1; i > 0; --i) { - tree[i] = tree[i << 1] + tree[(i << 1) | 1] + // current node's value is the sum of left child, right child + // tree[i] = tree[i * 2] + tree[i * 2 + 1] + tree[i] = tree[i * 2] + tree[i * 2 + 1] } } - update (p, value) { + update (index, value) { const { size, tree } = this - // set value at position p - tree[p + size] = value - p += size + // only update values in the parents of the given node being changed. + // to get the parent move to parent node (index / 2) + + // set value at position index + index += size + // tree[index] is leaf node and index's value of array + tree[index] = value // move upward and update parents - for (let i = p; i > 1; i >>= 1) { + for (let i = index; i > 1; i >>= 1) { + // i ^ 1 turns (2 * i) to (2 * i + 1) + // i ^ 1 is second child tree[i >> 1] = tree[i] + tree[i ^ 1] } } + // interval [L,R) with left index(L) included and right (R) excluded. query (left, right) { const { size, tree } = this + // cause R is excluded, increase right for convenient right++ let res = 0 // loop to find the sum in the range for (left += size, right += size; left < right; left >>= 1, right >>= 1) { + // L is the left border of an query interval + + // if L is odd it means that it is the right child of its parent and our interval includes only L and not the parent. + // So we will simply include this node to sum and move to the parent of its next node by doing L = (L + 1) / 2. + + // if L is even it is the left child of its parent + // and the interval includes its parent also unless the right borders interfere. if ((left & 1) > 0) { res += tree[left++] } + // same in R (the right border of an query interval) if ((right & 1) > 0) { res += tree[--right] } From 2c3077a614e07affe241fad0f62b8636d0f9be9e Mon Sep 17 00:00:00 2001 From: Changi Cho Date: Sat, 15 Oct 2022 01:10:09 +0900 Subject: [PATCH 6/6] feat: Remove useless comment it duplicated by next line. so it is not necessary --- Data-Structures/Tree/SegmentTree.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Data-Structures/Tree/SegmentTree.js b/Data-Structures/Tree/SegmentTree.js index 90616baaba..3778bb1e39 100644 --- a/Data-Structures/Tree/SegmentTree.js +++ b/Data-Structures/Tree/SegmentTree.js @@ -41,7 +41,6 @@ class SegmentTree { // tree's root node will contain all leaf node's sum for (let i = size - 1; i > 0; --i) { // current node's value is the sum of left child, right child - // tree[i] = tree[i * 2] + tree[i * 2 + 1] tree[i] = tree[i * 2] + tree[i * 2 + 1] } }