【從蛋殼到滿天飛】JS 數據結構解析和算法實現,所有文章大概的內容以下: Arrays(數組)、Stacks(棧)、Queues(隊列)、LinkedList(鏈表)、Recursion(遞歸思想)、BinarySearchTree(二分搜索樹)、Set(集合)、Map(映射)、Heap(堆)、PriorityQueue(優先隊列)、SegmentTree(線段樹)、Trie(字典樹)、UnionFind(並查集)、AVLTree(AVL 平衡樹)、RedBlackTree(紅黑平衡樹)、HashTable(哈希表)html
源代碼有三個:ES6(單個單個的 class 類型的 js 文件) | JS + HTML(一個 js 配合一個 html)| JAVA (一個一個的工程)git
所有源代碼已上傳 github,點擊我吧,光看文章可以掌握兩成,動手敲代碼、動腦思考、畫圖才能夠掌握八成。github
本文章適合 對數據結構想了解而且感興趣的人羣,文章風格一如既往如此,就以爲手機上看起來比較方便,這樣顯得比較有條理,整理這些筆記加源碼,時間跨度也算將近半年時間了,但願對想學習數據結構的人或者正在學習數據結構的人羣有幫助。面試
[i,j]
區間內看到多少種顏色?O(n)
級別的,O(n)
級別的,O(n)
級別的話,侷限性
。[i,j]
的最大值、最小值,或者區間數字和。O(n)
級別的,O(logn)
這個複雜度內完成。[i,j]
的最大值、最小值、或者區間數字和,添加
元素或者刪除
元素的。次方
關係,0 層就是 2^0,1 層就是 2^1,2^h-1
個(h-1層)
,有2^(h-1)
個節點,2 * 2^(h-1)-1
個節點,這樣一來就是2^h-1
個)。(h-1)
層節點數爲2^(h-1)
,下層節點的數量是上層節點數量的 2 倍,2^h-1
個節點(大約是2^h
),2^h
時必定能夠裝下滿二叉樹全部的元素,(h-1層)
,有2^(h-1)
個節點,(class: MySegmentTree)
MySegmentTree算法
// 自定義線段樹 SegmentTree
class MySegmentTree {
constructor(array) {
// 拷貝一份參數數組中的元素
this.data = new Array(array.length);
for (var i = 0; i < array.length; i++) this.data[i] = array[i];
// 初始化線段樹 開4倍的空間 這樣才能在全部狀況下存儲線段樹上全部的節點
this.tree = new Array(4 * this.data.length);
}
// 獲取線段樹中實際的元素個數
getSize() {
return this.data.length;
}
// 根據索引獲取元素
get(index) {
if (index < 0 || index >= this.getSize())
throw new Error('index is illegal.');
return this.data[index];
}
// 輔助函數:返回徹底二叉樹的數組表示中,一個索引所表示的元素的左孩子節點的索引
// 計算出線段樹中指定索引位置的元素其左孩子節點的索引 -
calcLeftChildIndex(index) {
return index * 2 + 1;
}
// 輔助函數:返回徹底二叉樹的數組表示中,一個索引所表示的元素的右孩子節點的索引
// 計算出線段樹中指定索引位置的元素其右孩子節點的索引 -
calcRightChildIndex(index) {
return index * 2 + 2;
}
}
複製代碼
mid = (left + right) / 2
,mid = left + (right - left) / 2
,left至mid
,右子樹區間爲 mid+1至right
,(class: MySegmentTree, class: Main)
數組
MySegmentTree:線段樹數據結構
// 自定義線段樹 SegmentTree
class MySegmentTree {
constructor(array) {
// 拷貝一份參數數組中的元素
this.data = new Array(array.length);
for (var i = 0; i < array.length; i++) this.data[i] = array[i];
// 初始化線段樹 開4倍的空間 這樣才能在全部狀況下存儲線段樹上全部的節點
this.tree = new Array(4 * this.data.length);
// 開始構建線段樹
this.buildingSegmentTree(0, 0, this.data.length - 1);
}
// 獲取線段樹中實際的元素個數
getSize() {
return this.data.length;
}
// 根據索引獲取元素
get(index) {
if (index < 0 || index >= this.getSize())
throw new Error('index is illegal.');
return this.data[index];
}
// 構建線段樹
buildingSegmentTree(treeIndex, left, right) {
// 解決最基本問題
// 當一條線段的兩端相同時,說明這個區間只有一個元素,
// 那麼遞歸也到底了
if (left === right) {
this.tree[treeIndex] = this.data[left];
return;
}
// 計算當前線段樹的左右子樹的索引
const leftChildIndex = this.calcLeftChildIndex(treeIndex);
const rightChildIndex = this.calcRightChildIndex(treeIndex);
// 將一個區間拆分爲兩段,而後繼續構建其左右子線段樹
let middle = Math.floor(left + (right - left) / 2); //(left + right) / 2
// 構建左子線段樹
this.buildingSegmentTree(leftChildIndex, left, middle);
// 構建右子線段樹
this.buildingSegmentTree(rightChildIndex, middle + 1, right);
// 融合左子線段樹和右子線段樹
this.tree[treeIndex] = this.merge(
this.tree[leftChildIndex],
this.tree[rightChildIndex]
);
}
// 輔助函數:返回徹底二叉樹的數組表示中,一個索引所表示的元素的左孩子節點的索引
// 計算出線段樹中指定索引位置的元素其左孩子節點的索引 -
calcLeftChildIndex(index) {
return index * 2 + 1;
}
// 輔助函數:返回徹底二叉樹的數組表示中,一個索引所表示的元素的右孩子節點的索引
// 計算出線段樹中指定索引位置的元素其右孩子節點的索引 -
calcRightChildIndex(index) {
return index * 2 + 2;
}
// 輔助函數: 融合兩棵線段樹,也就是對線段樹進行業務邏輯的處理
merge(treeElementA, treeElmentB) {
// 默認進行求和操做
return treeElementA + treeElmentB;
}
// 輔助函數:更新融合的方法,也就是自定義處理線段樹融合的業務邏輯
updateMerge(mergeMethod) {
this.merge = mergeMethod;
}
// @Override toString() 2018-11-7 jwl
toString() {
let segmentTreeConsoleInfo = ''; // 控制檯信息
let segmentTreePageInfo = ''; // 頁面信息
// 輸出頭部信息
segmentTreeConsoleInfo += 'SegmentTree:';
segmentTreePageInfo += 'SegmentTree:';
segmentTreeConsoleInfo += '\r\n';
segmentTreePageInfo += '<br/><br/>';
// 輸出傳入的數據信息
segmentTreeConsoleInfo += 'data = [';
segmentTreePageInfo += 'data = [';
for (let i = 0; i < this.data.length - 1; i++) {
segmentTreeConsoleInfo += this.data[i] + ',';
segmentTreePageInfo += this.data[i] + ',';
}
if (this.data != null && this.data.length != 0) {
segmentTreeConsoleInfo += this.data[this.data.length - 1];
segmentTreePageInfo += this.data[this.data.length - 1];
}
segmentTreeConsoleInfo += '],\r\n';
segmentTreePageInfo += '],<br/><br/>';
// 輸出生成的線段樹信息
segmentTreeConsoleInfo += 'tree = [';
segmentTreePageInfo += 'tree = [';
let treeSize = 0;
for (let i = 0; i < this.tree.length - 1; i++) {
if (this.tree[i] !== undefined) treeSize++;
segmentTreeConsoleInfo += this.tree[i] + ',';
segmentTreePageInfo += this.tree[i] + ',';
}
if (this.tree != null && this.tree.length != 0) {
if (this.tree[this.tree.length - 1] !== undefined) treeSize++;
segmentTreeConsoleInfo += this.tree[this.tree.length - 1];
segmentTreePageInfo += this.tree[this.tree.length - 1];
}
segmentTreeConsoleInfo += '],\r\n';
segmentTreePageInfo += '],<br/><br/>';
segmentTreeConsoleInfo += 'originArraySize:' + this.getSize() + ',';
segmentTreePageInfo += 'originArraySize:' + this.getSize() + ',';
segmentTreeConsoleInfo +=
'treeCapacity: ' + this.tree.length + ',treeSize: ' + treeSize;
segmentTreePageInfo +=
'treeCapacity: ' + this.tree.length + ',treeSize: ' + treeSize;
// 返回輸出的總信息
document.body.innerHTML += segmentTreePageInfo;
return segmentTreeConsoleInfo;
}
}
複製代碼
Main:主函數ide
// main 函數
class Main {
constructor() {
this.alterLine('MySegmentTree Area');
// 初始數據
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
// 初始化線段樹,將初始數據和融合器傳入進去
let mySegmentTree = new MySegmentTree(nums);
// 指定線段樹的融合器
mySegmentTree.updateMerge((a, b) => a + b);
// 輸出
console.log(mySegmentTree.toString());
}
// 將內容顯示在頁面上
show(content) {
document.body.innerHTML += `${content}<br /><br />`;
}
// 展現分割線
alterLine(title) {
let line = `--------------------${title}----------------------`;
console.log(line);
document.body.innerHTML += `${line}<br /><br />`;
}
}
// 頁面加載完畢
window.onload = function() {
// 執行主函數
new Main();
};
複製代碼
(class: MySegmentTree, class: Main)
函數
MySegmentTree:線段樹性能
// 自定義線段樹 SegmentTree
class MySegmentTree {
constructor(array) {
// 拷貝一份參數數組中的元素
this.data = new Array(array.length);
for (var i = 0; i < array.length; i++) this.data[i] = array[i];
// 初始化線段樹 開4倍的空間 這樣才能在全部狀況下存儲線段樹上全部的節點
this.tree = new Array(4 * this.data.length);
// 開始構建線段樹
this.buildingSegmentTree(0, 0, this.data.length - 1);
}
// 獲取線段樹中實際的元素個數
getSize() {
return this.data.length;
}
// 根據索引獲取元素
get(index) {
if (index < 0 || index >= this.getSize())
throw new Error('index is illegal.');
return this.data[index];
}
// 構建線段樹
buildingSegmentTree(treeIndex, left, right) {
// 解決最基本問題
// 當一條線段的兩端相同時,說明這個區間只有一個元素,
// 那麼遞歸也到底了
if (left === right) {
this.tree[treeIndex] = this.data[left];
return;
}
// 計算當前線段樹的左右子樹的索引
const leftChildIndex = this.calcLeftChildIndex(treeIndex);
const rightChildIndex = this.calcRightChildIndex(treeIndex);
// 將一個區間拆分爲兩段,而後繼續構建其左右子線段樹
let middle = Math.floor(left + (right - left) / 2); //(left + right) / 2
// 構建左子線段樹
this.buildingSegmentTree(leftChildIndex, left, middle);
// 構建右子線段樹
this.buildingSegmentTree(rightChildIndex, middle + 1, right);
// 融合左子線段樹和右子線段樹
this.tree[treeIndex] = this.merge(
this.tree[leftChildIndex],
this.tree[rightChildIndex]
);
}
// 查詢指定區間的線段樹數據
// 返回區間[queryLeft, queryRight]的值
query(queryLeft, queryRight) {
if (
queryLeft < 0 ||
queryRight < 0 ||
queryLeft > queryRight ||
queryLeft >= this.data.length ||
queryRight >= this.data.length
)
throw new Error('queryLeft or queryRight is illegal.');
// 調用遞歸的查詢方法
return this.recursiveQuery(
0,
0,
this.data.length - 1,
queryLeft,
queryRight
);
}
// 遞歸的查詢方法 -
// 在以treeIndex爲根的線段樹中[left...right]的範圍裏,
// 搜索區間[queryLeft...queryRight]的值
recursiveQuery(treeIndex, left, right, queryLeft, queryRight) {
// 若是查詢範圍 與 指定的線段樹的區間 相同,那麼說明徹底匹配,
// 直接返回當前這個線段便可,每個節點表明 一個線段(區間)處理後的結果
if (left === queryLeft && right === queryRight)
return this.tree[treeIndex];
// 求出當前查詢範圍的中間值
const middle = Math.floor(left + (right - left) / 2);
// 滿二叉樹確定有左右孩子節點
// 上面的判斷沒有徹底匹配,說明須要繼續 縮小查詢範圍,也就是要在左右子樹中進行查詢了
const leftChildIndex = this.calcLeftChildIndex(treeIndex);
const rightChildIndex = this.calcRightChildIndex(treeIndex);
// 判斷:
// 1. 從左子樹中查仍是右子樹中查,又或者從左右子樹中同時查,而後將兩個查詢結果融合。
// 2. 若是 待查詢的區間的左端點大於查詢範圍的中間值,說明只須要從右子樹中進行查詢便可。
// 3. 若是 待查詢的區間的右端點小於查詢範圍的中間值 + 1,說明只須要從左子樹中進行查詢。
// 4. 若是 待查詢的區間在左右端點各分部一部分,說明要同時從左右子樹中進行查詢。
if (queryLeft > middle)
return this.recursiveQuery(
rightChildIndex,
middle + 1,
right,
queryLeft,
queryRight
);
else if (queryRight < middle + 1)
return this.recursiveQuery(
leftChildIndex,
left,
middle,
queryLeft,
queryRight
);
else {
// 求出 左子樹中一部分待查詢區間中的值
const leftChildValue = this.recursiveQuery(
leftChildIndex,
left,
middle,
queryLeft,
middle
);
// 求出 右子樹中一部分待查詢區間中的值
const rightChildValue = this.recursiveQuery(
rightChildIndex,
middle + 1,
right,
middle + 1,
queryRight
);
// 融合左右子樹種的數據並返回
return this.merge(leftChildValue, rightChildValue);
}
}
// 輔助函數:返回徹底二叉樹的數組表示中,一個索引所表示的元素的左孩子節點的索引
// 計算出線段樹中指定索引位置的元素其左孩子節點的索引 -
calcLeftChildIndex(index) {
return index * 2 + 1;
}
// 輔助函數:返回徹底二叉樹的數組表示中,一個索引所表示的元素的右孩子節點的索引
// 計算出線段樹中指定索引位置的元素其右孩子節點的索引 -
calcRightChildIndex(index) {
return index * 2 + 2;
}
// 輔助函數: 融合兩棵線段樹,也就是對線段樹進行業務邏輯的處理 -
merge(treeElementA, treeElmentB) {
// 默認進行求和操做
return treeElementA + treeElmentB;
}
// 輔助函數:更新融合的方法,也就是自定義處理線段樹融合的業務邏輯 +
updateMerge(mergeMethod) {
this.merge = mergeMethod;
}
// @Override toString() 2018-11-7 jwl
toString() {
let segmentTreeConsoleInfo = ''; // 控制檯信息
let segmentTreePageInfo = ''; // 頁面信息
// 輸出頭部信息
segmentTreeConsoleInfo += 'SegmentTree:';
segmentTreePageInfo += 'SegmentTree:';
segmentTreeConsoleInfo += '\r\n';
segmentTreePageInfo += '<br/><br/>';
// 輸出傳入的數據信息
segmentTreeConsoleInfo += 'data = [';
segmentTreePageInfo += 'data = [';
for (let i = 0; i < this.data.length - 1; i++) {
segmentTreeConsoleInfo += this.data[i] + ',';
segmentTreePageInfo += this.data[i] + ',';
}
if (this.data != null && this.data.length != 0) {
segmentTreeConsoleInfo += this.data[this.data.length - 1];
segmentTreePageInfo += this.data[this.data.length - 1];
}
segmentTreeConsoleInfo += '],\r\n';
segmentTreePageInfo += '],<br/><br/>';
// 輸出生成的線段樹信息
segmentTreeConsoleInfo += 'tree = [';
segmentTreePageInfo += 'tree = [';
let treeSize = 0;
for (let i = 0; i < this.tree.length - 1; i++) {
if (this.tree[i] !== undefined) treeSize++;
segmentTreeConsoleInfo += this.tree[i] + ',';
segmentTreePageInfo += this.tree[i] + ',';
}
if (this.tree != null && this.tree.length != 0) {
if (this.tree[this.tree.length - 1] !== undefined) treeSize++;
segmentTreeConsoleInfo += this.tree[this.tree.length - 1];
segmentTreePageInfo += this.tree[this.tree.length - 1];
}
segmentTreeConsoleInfo += '],\r\n';
segmentTreePageInfo += '],<br/><br/>';
segmentTreeConsoleInfo += 'originArraySize:' + this.getSize() + ',';
segmentTreePageInfo += 'originArraySize:' + this.getSize() + ',';
segmentTreeConsoleInfo +=
'treeCapacity: ' + this.tree.length + ',treeSize: ' + treeSize;
segmentTreePageInfo +=
'treeCapacity: ' + this.tree.length + ',treeSize: ' + treeSize;
// 返回輸出的總信息
document.body.innerHTML += segmentTreePageInfo;
return segmentTreeConsoleInfo;
}
}
複製代碼
Main:主函數
// main 函數
class Main {
constructor() {
this.alterLine('MySegmentTree Area');
// 初始數據
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
// 初始化線段樹,將初始數據和融合器傳入進去
let mySegmentTree = new MySegmentTree(nums);
// 指定線段樹的融合器
mySegmentTree.updateMerge((a, b) => a + b);
// 輸出
console.log(mySegmentTree.toString());
this.show('');
this.alterLine('MySegmentTree Queue Area');
console.log('查詢區間[0, 2]:' + mySegmentTree.query(0, 2));
this.show('查詢區間[0, 2]:' + mySegmentTree.query(0, 2));
console.log('查詢區間[3, 9]:' + mySegmentTree.query(3, 9));
this.show('查詢區間[3, 9]:' + mySegmentTree.query(3, 9));
console.log('查詢區間[0, 9]:' + mySegmentTree.query(0, 9));
this.show('查詢區間[0, 9]:' + mySegmentTree.query(0, 9));
}
// 將內容顯示在頁面上
show(content) {
document.body.innerHTML += `${content}<br /><br />`;
}
// 展現分割線
alterLine(title) {
let line = `--------------------${title}----------------------`;
console.log(line);
document.body.innerHTML += `${line}<br /><br />`;
}
}
// 頁面加載完畢
window.onload = function() {
// 執行主函數
new Main();
};
複製代碼
303.區域和檢索-數組不可變
https://leetcode-cn.com/problems/range-sum-query-immutable/
307.區域和檢索 - 數組可修改
https://leetcode-cn.com/problems/range-sum-query-mutable/
303 方式一 和 方式二
// 答題
class Solution {
// leetcode 303. 區域和檢索-數組不可變
NumArray(nums) {
/** * @param {number[]} nums * 處理方式一:對原數組進行預處理操做 */
var NumArray = function(nums) {
if (nums.length > 0) {
this.data = new Array(nums.length + 1);
this.data[0] = 0;
for (var i = 0; i < nums.length; i++) {
this.data[i + 1] = this.data[i] + nums[i];
}
}
};
/** * @param {number} i * @param {number} j * @return {number} */
NumArray.prototype.sumRange = function(i, j) {
return this.data[j + 1] - this.data[i];
};
/** * Your NumArray object will be instantiated and called as such: * var obj = Object.create(NumArray).createNew(nums) * var param_1 = obj.sumRange(i,j) */
/** * @param {number[]} nums * 處理方式二:使用線段樹 */
var NumArray = function(nums) {
if (nums.length > 0) {
this.mySegmentTree = new MySegmentTree(nums);
}
};
/** * @param {number} i * @param {number} j * @return {number} */
NumArray.prototype.sumRange = function(i, j) {
return this.mySegmentTree.query(i, j);
};
return new NumArray(nums);
}
}
複製代碼
307 方式一
// 答題
class Solution {
// leetcode 307. 區域和檢索 - 數組可修改
NumArray2(nums) {
/** * @param {number[]} nums * 方式一:對原數組進行預處理操做 */
var NumArray = function(nums) {
// 克隆一份原數組
this.data = new Array(nums.length);
for (var i = 0; i < nums.length; i++) {
this.data[i] = nums[i];
}
if (nums.length > 0) {
this.sum = new Array(nums.length + 1);
this.sum[0] = 0;
for (let i = 0; i < nums.length; i++)
this.sum[i + 1] = this.sum[i] + nums[i];
}
};
/** * @param {number} i * @param {number} val * @return {void} */
NumArray.prototype.update = function(i, val) {
this.data[i] = val;
for (let j = 0; j < this.data.length; j++)
this.sum[j + 1] = this.sum[j] + this.data[j];
};
/** * @param {number} i * @param {number} j * @return {number} */
NumArray.prototype.sumRange = function(i, j) {
return this.sum[j + 1] - this.sum[i];
};
/** * Your NumArray object will be instantiated and called as such: * var obj = Object.create(NumArray).createNew(nums) * obj.update(i,val) * var param_2 = obj.sumRange(i,j) */
}
}
複製代碼
Main
// main 函數
class Main {
constructor() {
this.alterLine('leetcode 303. 區域和檢索-數組不可變');
let s = new Solution();
let nums = [-2, 0, 3, -5, 2, -1];
let numArray = s.NumArray(nums);
console.log(numArray.sumRange(0, 2));
this.show(numArray.sumRange(0, 2));
console.log(numArray.sumRange(2, 5));
this.show(numArray.sumRange(2, 5));
console.log(numArray.sumRange(0, 5));
this.show(numArray.sumRange(0, 5));
}
// 將內容顯示在頁面上
show(content) {
document.body.innerHTML += `${content}<br /><br />`;
}
// 展現分割線
alterLine(title) {
let line = `--------------------${title}----------------------`;
console.log(line);
document.body.innerHTML += `${line}<br /><br />`;
}
}
// 頁面加載完畢
window.onload = function() {
// 執行主函數
new Main();
};
複製代碼
O(n)
級別的,O(1)
級別的,只不過初始化操做時是O(n)
級別的。O(logn)
級別的,O(n)
的複雜度,(class: MySegmentTree, class: NumArray2, class: Main)
MySegmentTree
// 自定義線段樹 SegmentTree
class MySegmentTree {
constructor(array) {
// 拷貝一份參數數組中的元素
this.data = new Array(array.length);
for (var i = 0; i < array.length; i++) this.data[i] = array[i];
// 初始化線段樹 開4倍的空間 這樣才能在全部狀況下存儲線段樹上全部的節點
this.tree = new Array(4 * this.data.length);
// 開始構建線段樹
this.buildingSegmentTree(0, 0, this.data.length - 1);
}
// 獲取線段樹中實際的元素個數
getSize() {
return this.data.length;
}
// 根據索引獲取元素
get(index) {
if (index < 0 || index >= this.getSize())
throw new Error('index is illegal.');
return this.data[index];
}
// 構建線段樹
buildingSegmentTree(treeIndex, left, right) {
// 解決最基本問題
// 當一條線段的兩端相同時,說明這個區間只有一個元素,
// 那麼遞歸也到底了
if (left === right) {
this.tree[treeIndex] = this.data[left];
return;
}
// 計算當前線段樹的左右子樹的索引
const leftChildIndex = this.calcLeftChildIndex(treeIndex);
const rightChildIndex = this.calcRightChildIndex(treeIndex);
// 將一個區間拆分爲兩段,而後繼續構建其左右子線段樹
let middle = Math.floor(left + (right - left) / 2); //(left + right) / 2
// 構建左子線段樹
this.buildingSegmentTree(leftChildIndex, left, middle);
// 構建右子線段樹
this.buildingSegmentTree(rightChildIndex, middle + 1, right);
// 融合左子線段樹和右子線段樹
this.tree[treeIndex] = this.merge(
this.tree[leftChildIndex],
this.tree[rightChildIndex]
);
}
// 查詢指定區間的線段樹數據
// 返回區間[queryLeft, queryRight]的值
query(queryLeft, queryRight) {
if (
queryLeft < 0 ||
queryRight < 0 ||
queryLeft > queryRight ||
queryLeft >= this.data.length ||
queryRight >= this.data.length
)
throw new Error('queryLeft or queryRight is illegal.');
// 調用遞歸的查詢方法
return this.recursiveQuery(
0,
0,
this.data.length - 1,
queryLeft,
queryRight
);
}
// 遞歸的查詢方法 -
// 在以treeIndex爲根的線段樹中[left...right]的範圍裏,
// 搜索區間[queryLeft...queryRight]的值
recursiveQuery(treeIndex, left, right, queryLeft, queryRight) {
// 若是查詢範圍 與 指定的線段樹的區間 相同,那麼說明徹底匹配,
// 直接返回當前這個線段便可,每個節點表明 一個線段(區間)處理後的結果
if (left === queryLeft && right === queryRight)
return this.tree[treeIndex];
// 求出當前查詢範圍的中間值
const middle = Math.floor(left + (right - left) / 2);
// 滿二叉樹確定有左右孩子節點
// 上面的判斷沒有徹底匹配,說明須要繼續 縮小查詢範圍,也就是要在左右子樹中進行查詢了
const leftChildIndex = this.calcLeftChildIndex(treeIndex);
const rightChildIndex = this.calcRightChildIndex(treeIndex);
// 判斷:
// 1. 從左子樹中查仍是右子樹中查,又或者從左右子樹中同時查,而後將兩個查詢結果融合。
// 2. 若是 待查詢的區間的左端點大於查詢範圍的中間值,說明只須要從右子樹中進行查詢便可。
// 3. 若是 待查詢的區間的右端點小於查詢範圍的中間值 + 1,說明只須要從左子樹中進行查詢。
// 4. 若是 待查詢的區間在左右端點各分部一部分,說明要同時從左右子樹中進行查詢。
if (queryLeft > middle)
return this.recursiveQuery(
rightChildIndex,
middle + 1,
right,
queryLeft,
queryRight
);
else if (queryRight < middle + 1)
return this.recursiveQuery(
leftChildIndex,
left,
middle,
queryLeft,
queryRight
);
else {
// 求出 左子樹中一部分待查詢區間中的值
const leftChildValue = this.recursiveQuery(
leftChildIndex,
left,
middle,
queryLeft,
middle
);
// 求出 右子樹中一部分待查詢區間中的值
const rightChildValue = this.recursiveQuery(
rightChildIndex,
middle + 1,
right,
middle + 1,
queryRight
);
// 融合左右子樹種的數據並返回
return this.merge(leftChildValue, rightChildValue);
}
}
// 設置指定索引位置的元素 更新操做
set(index, element) {
if (index < 0 || index >= this.data.length)
throw new Error('index is illegal.');
this.recursiveSet(0, 0, this.data.length - 1, index, element);
}
// 遞歸的設置指定索引位置元素的方法 -
// 在以treeIndex爲根的線段樹中更新index的值爲element
recursiveSet(treeIndex, left, right, index, element) {
// 解決最基本的問題 遞歸到底了就結束
// 由於找到了該索引位置的節點了
if (left === right) {
this.tree[treeIndex] = element;
this.data[index] = element;
return;
}
// 求出當前查詢範圍的中間值
const middle = Math.floor(left + (right - left) / 2);
// 滿二叉樹確定有左右孩子節點
// 上面的判斷沒有徹底匹配,說明須要繼續 縮小查詢範圍,也就是要在左右子樹中進行查詢了
const leftChildIndex = this.calcLeftChildIndex(treeIndex);
const rightChildIndex = this.calcRightChildIndex(treeIndex);
// 若是指定的索引大於 查詢範圍的中間值,那就說明 該索引的元素在右子樹中
// 不然該索引元素在左子樹中
if (index > middle)
this.recursiveSet(
rightChildIndex,
middle + 1,
right,
index,
element
);
// index < middle + 1
else this.recursiveSet(leftChildIndex, left, middle, index, element);
// 將改變後的左右子樹再進行一下融合,由於遞歸到底時修改了指定索引位置的元素,
// 那麼指定索引位置所在的線段(區間)也須要再次進行融合操做,
// 從而達到修改一個值改變 相應的線段(區間)
this.tree[treeIndex] = this.merge(
this.tree[leftChildIndex],
this.tree[rightChildIndex]
);
}
// 輔助函數:返回徹底二叉樹的數組表示中,一個索引所表示的元素的左孩子節點的索引
// 計算出線段樹中指定索引位置的元素其左孩子節點的索引 -
calcLeftChildIndex(index) {
return index * 2 + 1;
}
// 輔助函數:返回徹底二叉樹的數組表示中,一個索引所表示的元素的右孩子節點的索引
// 計算出線段樹中指定索引位置的元素其右孩子節點的索引 -
calcRightChildIndex(index) {
return index * 2 + 2;
}
// 輔助函數: 融合兩棵線段樹,也就是對線段樹進行業務邏輯的處理 -
merge(treeElementA, treeElmentB) {
// 默認進行求和操做
return treeElementA + treeElmentB;
}
// 輔助函數:更新融合的方法,也就是自定義處理線段樹融合的業務邏輯 +
updateMerge(mergeMethod) {
this.merge = mergeMethod;
}
// @Override toString() 2018-11-7 jwl
toString() {
let segmentTreeConsoleInfo = ''; // 控制檯信息
let segmentTreePageInfo = ''; // 頁面信息
// 輸出頭部信息
segmentTreeConsoleInfo += 'SegmentTree:';
segmentTreePageInfo += 'SegmentTree:';
segmentTreeConsoleInfo += '\r\n';
segmentTreePageInfo += '<br/><br/>';
// 輸出傳入的數據信息
segmentTreeConsoleInfo += 'data = [';
segmentTreePageInfo += 'data = [';
for (let i = 0; i < this.data.length - 1; i++) {
segmentTreeConsoleInfo += this.data[i] + ',';
segmentTreePageInfo += this.data[i] + ',';
}
if (this.data != null && this.data.length != 0) {
segmentTreeConsoleInfo += this.data[this.data.length - 1];
segmentTreePageInfo += this.data[this.data.length - 1];
}
segmentTreeConsoleInfo += '],\r\n';
segmentTreePageInfo += '],<br/><br/>';
// 輸出生成的線段樹信息
segmentTreeConsoleInfo += 'tree = [';
segmentTreePageInfo += 'tree = [';
let treeSize = 0;
for (let i = 0; i < this.tree.length - 1; i++) {
if (this.tree[i] !== undefined) treeSize++;
segmentTreeConsoleInfo += this.tree[i] + ',';
segmentTreePageInfo += this.tree[i] + ',';
}
if (this.tree != null && this.tree.length != 0) {
if (this.tree[this.tree.length - 1] !== undefined) treeSize++;
segmentTreeConsoleInfo += this.tree[this.tree.length - 1];
segmentTreePageInfo += this.tree[this.tree.length - 1];
}
segmentTreeConsoleInfo += '],\r\n';
segmentTreePageInfo += '],<br/><br/>';
segmentTreeConsoleInfo += 'originArraySize:' + this.getSize() + ',';
segmentTreePageInfo += 'originArraySize:' + this.getSize() + ',';
segmentTreeConsoleInfo +=
'treeCapacity: ' + this.tree.length + ',treeSize: ' + treeSize;
segmentTreePageInfo +=
'treeCapacity: ' + this.tree.length + ',treeSize: ' + treeSize;
// 返回輸出的總信息
document.body.innerHTML += segmentTreePageInfo;
return segmentTreeConsoleInfo;
}
}
複製代碼
NumArray2
// 答題
class Solution {
// leetcode 307. 區域和檢索 - 數組可修改
NumArray2(nums) {
/** * @param {number[]} nums * 方式一:對原數組進行預處理操做 */
var NumArray = function(nums) {
// 克隆一份原數組
this.data = new Array(nums.length);
for (var i = 0; i < nums.length; i++) {
this.data[i] = nums[i];
}
if (nums.length > 0) {
this.sum = new Array(nums.length + 1);
this.sum[0] = 0;
for (let i = 0; i < nums.length; i++)
this.sum[i + 1] = this.sum[i] + nums[i];
}
};
/** * @param {number} i * @param {number} val * @return {void} */
NumArray.prototype.update = function(i, val) {
this.data[i] = val;
for (let j = 0; j < this.data.length; j++)
this.sum[j + 1] = this.sum[j] + this.data[j];
};
/** * @param {number} i * @param {number} j * @return {number} */
NumArray.prototype.sumRange = function(i, j) {
return this.sum[j + 1] - this.sum[i];
};
/** * Your NumArray object will be instantiated and called as such: * var obj = Object.create(NumArray).createNew(nums) * obj.update(i,val) * var param_2 = obj.sumRange(i,j) */
/** * @param {number[]} nums * 方式二:對原數組進行預處理操做 */
var NumArray = function(nums) {
this.tree = new MySegmentTree(nums);
};
/** * @param {number} i * @param {number} val * @return {void} */
NumArray.prototype.update = function(i, val) {
this.tree.set(i, val);
};
/** * @param {number} i * @param {number} j * @return {number} */
NumArray.prototype.sumRange = function(i, j) {
return this.tree.query(i, j);
};
return new NumArray(nums);
}
}
複製代碼
Main
// main 函數
class Main {
constructor() {
this.alterLine('leetcode 307. 區域和檢索 - 數組可修改');
let s = new Solution();
let nums = [1, 3, 5];
let numArray = s.NumArray2(nums);
console.log(numArray.sumRange(0, 2));
this.show(numArray.sumRange(0, 2));
numArray.update(1, 2);
console.log(numArray.sumRange(0, 2));
this.show(numArray.sumRange(0, 2));
}
// 將內容顯示在頁面上
show(content) {
document.body.innerHTML += `${content}<br /><br />`;
}
// 展現分割線
alterLine(title) {
let line = `--------------------${title}----------------------`;
console.log(line);
document.body.innerHTML += `${line}<br /><br />`;
}
}
// 頁面加載完畢
window.onload = function() {
// 執行主函數
new Main();
};
複製代碼
https://leetcode-cn.com/tag/segment-tree/
,O(n)
級別的,[5,16]
這樣的一個區間,在這種狀況下再開始動態的建立這個線段樹,[0,4]
這樣的一個區間,右孩子表示 5 到一億這樣的一個區間,[5,16]
,[5,16]
這個區間相應的內容,