【从蛋壳到满天飞】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]
这个区间相应的内容,