【从蛋壳到满天飞】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 (一个一个的工程)node
所有源代码已上传 github,点击我吧,光看文章可以掌握两成,动手敲代码、动脑思考、画图才能够掌握八成。git
本文章适合 对数据结构想了解而且感兴趣的人群,文章风格一如既往如此,就以为手机上看起来比较方便,这样显得比较有条理,整理这些笔记加源码,时间跨度也算将近半年时间了,但愿对想学习数据结构的人或者正在学习数据结构的人群有帮助。github
添加元素的状况面试
在红黑树中的三节点中添加元素的第一种状况算法
下图中节点 37 和节点 42 已经造成了一个三节点,数组
由于所谓红色节点的意思就是它和它的父亲是融合在一块儿的,数据结构
若是这个时候再添加一个元素 66,新添加的节点默认为红色,dom
那么按照二分搜索树中的性质,这个元素会被添加为根节点 44 的右孩子,ide
此时就会对应二三树中临时的四节点,在红黑树的表示就是,
根节点是黑色的,左右孩子节点都是红色的,
所谓红色节点的意思就是它和它的父亲是融合在一块儿的。
// 中括号为黑色节点,大括号为红色节点
// [42]
// /
// {37}
// 添加元素 66
// [42]
// / \
// {37} {66}
复制代码
颜色翻转
在二三树中这个临时的四节点以后会被分裂成三个二节点,
对应红黑树中的操做是将根节点 42 下面两个节点 37 和 66 都染成黑色,
这样就表示了在二三树中三个二节点组成了一棵子树,
临时的四节点分裂以后,新的子树的根节点须要向上进行融合操做,
因此在红黑树中的操做是,这个根节点 42 要变成红色,
由于所谓红色节点的意思就是它和它的父亲是融合在一块儿的,
这个过程就让根节点的颜色与左右子树的颜色进行了一个翻转操做,
颜色翻转的英文就是 flipColors,这是在红黑树中添加一个新元素的辅助过程,
实际上也是等价的在二三树中的三节点中添加了一个新元素所对应的一个辅助过程。
// 中括号为黑色节点,大括号为红色节点
// 添加元素 66
// [42]
// / \
// {37} {66}
// 颜色翻转
// {42}
// / \
// [37] [66]
复制代码
在红黑树中的三节点中添加元素的另一种状况
// 中括号为黑色节点,大括号为红色节点
// [42]
// /
// {37}
// 添加元素 12
// [42]
// /
// {37}
// /
// {23}
复制代码
右旋转
// 中括号为黑色节点,大括号为红色节点
// 小括号只是参与演示,并不真实存在
// 原来是这样的
// [42] node
// / \
// x {37} (T2)
// / \
// {23} (T1)
// 进行右旋转后
// {37} x
// / \
// {23} {42} node
// / \
// (T1) (T2)
// node.left = T1
// x.right = node;
// x.color = node.color;
// node.color = RED;
// 颜色翻转后
// {37} x
// / \
// [23] [42] node
// / \
// (T1) (T2)
// x.color = RED;
// x.left.color = BLACK;
// x.right.color = BLACK;
复制代码
MyRedBlackTree
// 自定义红黑树节点 RedBalckTreeNode
class MyRedBalckTreeNode {
constructor(key = null, value = null, left = null, right = null) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.color = MyRedBlackTree.RED; // MyRedBlackTree.BLACK;
}
// @Override toString 2018-11-25-jwl
toString() {
return (
this.key.toString() +
'--->' +
this.value.toString() +
'--->' +
(this.color ? '红色节点' : '绿色节点')
);
}
}
// 自定义红黑树 RedBlackTree
class MyRedBlackTree {
constructor() {
MyRedBlackTree.RED = true;
MyRedBlackTree.BLACK = false;
this.root = null;
this.size = 0;
}
// 判断节点node的颜色
isRed(node) {
// 定义:空节点颜色为黑色
if (!node) return MyRedBlackTree.BLACK;
return node.color;
}
// node x
// / \ 左旋转 / \
// T1 x ---------> node T3
// / \ / \
// T2 T3 T1 T2
leftRotate(node) {
const x = node.right;
// 左旋转过程
node.right = x.left;
x.left = node;
// 染色过程
x.color = node.color;
node.color = MyRedBlackTree.RED;
// 返回这个 x
return x;
}
// 颜色翻转 当前节点变红 左右孩子变黑
// 表示当前节点须要继续向上进行融合
flipColors(node) {
node.color = MyRedBlackTree.RED;
node.left.color = MyRedBlackTree.BLACK;
node.right.color = MyRedBlackTree.BLACK;
}
// node x
// / \ 右旋转 / \
// x T2 -------> y node
// / \ / \
// y T1 T1 T2
rightRotate(node) {
const x = node.left;
// 右翻转过程
node.left = x.right;
x.right = node;
// 染色过程
x.color = node.color;
node.color = MyRedBlackTree.RED;
// 返回这个 x
return x;
}
// 比较的功能
compare(keyA, keyB) {
if (keyA === null || keyB === null)
throw new Error("key is error. key can't compare.");
if (keyA > keyB) return 1;
else if (keyA < keyB) return -1;
else return 0;
}
// 根据key获取节点 -
getNode(node, key) {
// 先解决最基本的问题
if (node === null) return null;
// 开始将复杂的问题 逐渐缩小规模
// 从而求出小问题的解,最后构建出原问题的解
switch (this.compare(node.key, key)) {
case 1: // 向左找
return this.getNode(node.left, key);
break;
case -1: // 向右找
return this.getNode(node.right, key);
break;
case 0: // 找到了
return node;
break;
default:
throw new Error(
'compare result is error. compare result : 0、 一、 -1 .'
);
break;
}
}
// 添加操做 +
add(key, value) {
this.root = this.recursiveAdd(this.root, key, value);
this.root.color = MyRedBlackTree.BLACK;
}
// 添加操做 递归算法 -
recursiveAdd(node, key, value) {
// 解决最简单的问题
if (node === null) {
this.size++;
return new MyRedBalckTreeNode(key, value);
}
// 将复杂的问题规模逐渐变小,
// 从而求出小问题的解,从而构建出原问题的答案
if (this.compare(node.key, key) > 0)
node.left = this.recursiveAdd(node.left, key, value);
else if (this.compare(node.key, key) < 0)
node.right = this.recursiveAdd(node.right, key, value);
else node.value = value;
return node;
}
// 删除操做 返回被删除的元素 +
remove(key) {
let node = this.getNode(this.root, key);
if (node === null) return null;
this.root = this.recursiveRemove(this.root, key);
return node.value;
}
// 删除操做 递归算法 +
recursiveRemove(node, key) {
// 解决最基本的问题
if (node === null) return null;
if (this.compare(node.key, key) > 0) {
node.left = this.recursiveRemove(node.left, key);
return node;
} else if (this.compare(node.key, key) < 0) {
node.right = this.recursiveRemove(node.right, key);
return node;
} else {
// 当前节点的key 与 待删除的key的那个节点相同
// 有三种状况
// 1. 当前节点没有左子树,那么只有让当前节点的右子树直接覆盖当前节点,就表示当前节点被删除了
// 2. 当前节点没有右子树,那么只有让当前节点的左子树直接覆盖当前节点,就表示当前节点被删除了
// 3. 当前节点左右子树都有, 那么又分两种状况,使用前驱删除法或者后继删除法
// 1. 前驱删除法:使用当前节点的左子树上最大的那个节点覆盖当前节点
// 2. 后继删除法:使用当前节点的右子树上最小的那个节点覆盖当前节点
if (node.left === null) {
let rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
} else if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
} else {
let predecessor = this.maximum(node.left);
node.left = this.removeMax(node.left);
this.size++;
// 开始嫁接 当前节点的左右子树
predecessor.left = node.left;
predecessor.right = node.right;
// 将当前节点从根节点剔除
node = node.left = node.right = null;
this.size--;
// 返回嫁接后的新节点
return predecessor;
}
}
}
// 删除操做的两个辅助函数
// 获取最大值、删除最大值
// 之前驱的方式 来辅助删除操做的函数
// 获取最大值
maximum(node) {
// 不再能往右了,说明当前节点已是最大的了
if (node.right === null) return node;
// 将复杂的问题渐渐减少规模,从而求出小问题的解,最后用小问题的解构建出原问题的答案
return this.maximum(node.right);
}
// 删除最大值
removeMax(node) {
// 解决最基本的问题
if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
}
// 开始化归
node.right = this.removeMax(node.right);
return node;
}
// 查询操做 返回查询到的元素 +
get(key) {
let node = this.getNode(this.root, key);
if (node === null) return null;
return node.value;
}
// 修改操做 +
set(key, value) {
let node = this.getNode(this.root, key);
if (node === null) throw new Error(key + " doesn't exist.");
node.value = value;
}
// 返回是否包含该key的元素的判断值 +
contains(key) {
return this.getNode(this.root, key) !== null;
}
// 返回映射中实际的元素个数 +
getSize() {
return this.size;
}
// 返回映射中是否为空的判断值 +
isEmpty() {
return this.size === 0;
}
// @Override toString() 2018-11-05-jwl
toString() {
let mapInfo = `MyBinarySearchTreeMap: size = ${this.size}, data = [ `;
document.body.innerHTML += `MyBinarySearchTreeMap: size = ${ this.size }, data = [ <br/><br/>`;
// 以非递归的前序遍历 输出字符串
let stack = new MyLinkedListStack();
stack.push(this.root);
if (this.root === null) stack.pop();
while (!stack.isEmpty()) {
let node = stack.pop();
if (node.left !== null) stack.push(node.left);
if (node.right !== null) stack.push(node.right);
if (node.left === null && node.right === null) {
mapInfo += ` ${node.toString()} \r\n`;
document.body.innerHTML += ` ${node.toString()} <br/><br/>`;
} else {
mapInfo += ` ${node.toString()}, \r\n`;
document.body.innerHTML += ` ${node.toString()}, <br/><br/>`;
}
}
mapInfo += ` ] \r\n`;
document.body.innerHTML += ` ] <br/><br/>`;
return mapInfo;
}
}
复制代码
在红黑树中添加新节点等价于在二三树的一个二或三节点上融合一个新的元素
在二节点上进行融合操做,
若是新添加的这个节点比最后找到的叶子节点的元素小,
那么对应在红黑树中的操做就是直接添加为当前节点的左孩子便可;
若是新添加的这个节点比最后找到的叶子节点的元素大,
那么对应在红黑树中的操做是 首先直接添加为当前节点的右孩子,
而后再进行左旋转操做,让新添加的节点做为根节点,
而原来的根节点做为新添加的节点的左孩子节点便可;
左旋转操做须要将新的根节点与原来的根节点的颜色对调,
而后将原来的根节点染色为红色,最后,
左旋转的过程当中并不会去维护红黑树的性质,
左旋转的做用只是让这两个节点对应成二三树中的三节点。
在三节点上进行融合操做,
若是新添加的这个节点比根节点要大,
那么对应在红黑树中的操做就是直接添加为根节点的右孩子,
以后进行一下颜色的翻转操做便可;
若是新添加的这个节点比根节点要小而且比根节点的左孩子要小,
那么对应在红黑树中的操做就是先直接添加为根节点的左孩子的左孩子,
而后进行一下右旋转,
旋转右旋转操做须要将新的根节点与原来的根节点的颜色对调,
而后将原来的根节点染色为红色,
也是同样,旋转操做不会去维护红黑树的性质,
右旋转的做用只是让这三个节点对应成二三树中的临时四节点,
以后进行一下颜色的翻转操做便可;
若是新添加的这个节点比根节点要小而且比根节点的左孩子要大,
那么对应在红黑树中的操做就是先直接添加为根节点的左孩子的右孩子,
而后对这个根节点的左孩子进行一个左旋转操做,
左旋转操做以后就以下图所示,
就变成了新添加的这个节点比根节点要小而且比根节点的左孩子要小
的状况,
那么就对这个根节点进行一个右旋转操做,
再来一个颜色翻转操做便可。
// 中括号为黑色节点,大括号为红色节点
// 原来是这样的
// [42]
// /
// {37}
// \
// {40}
// 对根节点的左孩子 进行左旋转后 染色后
// [42]
// /
// {40}
// /
// {37}
// 对根节点 进行右旋转后 染色后
// [40]
// / \
// {37} {42}
//
// 对根节点 颜色翻转后
// {40}
// / \
// [37] [42]
复制代码
红黑树添加节点逻辑分布图
下图中的逻辑只要分为三种
是否须要左旋转、右旋转、颜色翻转,
当当前节点的右孩子为红色而且当前节点的左孩子不为红色时,那么就须要进行左旋转;
当当前节点的左孩子为红色而且当前节点的左孩子的左孩子也为红色时,那么就须要进行右旋转;
当当前节点的左右孩子全都是红色时,那么就须要进行颜色翻转;
这三种逻辑并不是是互斥的,而是相吸,每一次都须要进行这三种判断,
下图也是按照这样的顺序来进行步骤的执行的。
// 中括号为黑色节点,大括号为红色节点
// 融合二节点
// 步骤一 步骤二 步骤三
// [null] [node] [node] [X]
// ----> ----> \ ----> /
// {X} {node}
// 首次添加节点 ----> 添加节点X ----> 左旋转
//
// 状况一 若是红黑树的根节点为空
// 那么 步骤一
// 状况二 若是红黑树的根节点不为空 新添加的节点小于根节点
// 那么 步骤一 --> 步骤三
// 状况三 若是红黑树的根节点不为空 新添加的节点大于根节点
// 那么 步骤一 --> 步骤二 --> 步骤三
// 融合三节点
//
// 步骤1 步骤2 步骤3 步骤4 步骤5
// [node] [node] [node] [Y] {Y}
// / ----> / ----> / ----> / \ ----> / \
//{X} {X} {Y} {X} {node} [X] [node]
// \ /
// {Y} {X}
// 添加节点Y ----> 左旋转 ----> 右旋转 ----> 颜色翻转
//
// 状况一 若是新添加的这个新节点 大于 根节点
// 那么步骤1 --> 步骤4 --> 步骤5
// 状况二 若是新添加的这个节点比根节点要小而且比根节点的左孩子要小
// 那么步骤1 --> 步骤3 --> 步骤4 --> 步骤5
// 状况三 若是新添加的这个节点比根节点要小而且比根节点的左孩子要大
// 那么步骤1 --> 步骤2 --> 步骤3 --> 步骤4 --> 步骤5
复制代码
红黑树添加节点时维护性质的时机
MyRedBlackTree
// 自定义红黑树节点 RedBalckTreeNode
class MyRedBalckTreeNode {
constructor(key = null, value = null, left = null, right = null) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.color = MyRedBlackTree.RED; // MyRedBlackTree.BLACK;
}
// @Override toString 2018-11-25-jwl
toString() {
return (
this.key.toString() +
'--->' +
this.value.toString() +
'--->' +
(this.color ? '红色节点' : '绿色节点')
);
}
}
// 自定义红黑树 RedBlackTree
class MyRedBlackTree {
constructor() {
MyRedBlackTree.RED = true;
MyRedBlackTree.BLACK = false;
this.root = null;
this.size = 0;
}
// 判断节点node的颜色
isRed(node) {
// 定义:空节点颜色为黑色
if (!node) return MyRedBlackTree.BLACK;
return node.color;
}
// node x
// / \ 左旋转 / \
// T1 x ---------> node T3
// / \ / \
// T2 T3 T1 T2
leftRotate(node) {
const x = node.right;
// 左旋转过程
node.right = x.left;
x.left = node;
// 染色过程
x.color = node.color;
node.color = MyRedBlackTree.RED;
// 返回这个 x
return x;
}
// 颜色翻转 当前节点变红 左右孩子变黑
// 表示当前节点须要继续向上进行融合
flipColors(node) {
node.color = MyRedBlackTree.RED;
node.left.color = MyRedBlackTree.BLACK;
node.right.color = MyRedBlackTree.BLACK;
}
// node x
// / \ 右旋转 / \
// x T2 -------> y node
// / \ / \
// y T1 T1 T2
rightRotate(node) {
const x = node.left;
// 右翻转过程
node.left = x.right;
x.right = node;
// 染色过程
x.color = node.color;
node.color = MyRedBlackTree.RED;
// 返回这个 x
return x;
}
// 比较的功能
compare(keyA, keyB) {
if (keyA === null || keyB === null)
throw new Error("key is error. key can't compare.");
if (keyA > keyB) return 1;
else if (keyA < keyB) return -1;
else return 0;
}
// 根据key获取节点 -
getNode(node, key) {
// 先解决最基本的问题
if (!node) return null;
// 开始将复杂的问题 逐渐缩小规模
// 从而求出小问题的解,最后构建出原问题的解
switch (this.compare(node.key, key)) {
case 1: // 向左找
return this.getNode(node.left, key);
break;
case -1: // 向右找
return this.getNode(node.right, key);
break;
case 0: // 找到了
return node;
break;
default:
throw new Error(
'compare result is error. compare result : 0、 一、 -1 .'
);
break;
}
}
// 添加操做 +
add(key, value) {
this.root = this.recursiveAdd(this.root, key, value);
this.root.color = MyRedBlackTree.BLACK;
}
// 添加操做 递归算法 -
recursiveAdd(node, key, value) {
// 解决最简单的问题
if (!node) {
this.size++;
return new MyRedBalckTreeNode(key, value);
}
// 将复杂的问题规模逐渐变小,
// 从而求出小问题的解,从而构建出原问题的答案
if (this.compare(node.key, key) > 0)
node.left = this.recursiveAdd(node.left, key, value);
else if (this.compare(node.key, key) < 0)
node.right = this.recursiveAdd(node.right, key, value);
else {
node.value = value;
return node;
}
// 红黑树性质的维护
// 是否须要左旋转
// 若是当前节点的右孩子是红色 而且 左孩子不是红色
if (this.isRed(node.right) && !this.isRed(node.left))
node = this.leftRotate(node);
// 是否须要右旋转
// 若是当前节点的左孩子是红色 而且 左孩子的左孩子也是红色
if (this.isRed(node.left) && this.isRed(node.left.left))
node = this.rightRotate(node);
// 是否须要颜色的翻转
// 当前节点的左孩子和右孩子全都是红色
if (this.isRed(node.left) && this.isRed(node.right))
this.flipColors(node);
// 最后返回这个node
return node;
}
// 删除操做 返回被删除的元素 +
remove(key) {
let node = this.getNode(this.root, key);
if (!node) return null;
this.root = this.recursiveRemove(this.root, key);
return node.value;
}
// 删除操做 递归算法 +
recursiveRemove(node, key) {
// 解决最基本的问题
if (!node) return null;
if (this.compare(node.key, key) > 0) {
node.left = this.recursiveRemove(node.left, key);
return node;
} else if (this.compare(node.key, key) < 0) {
node.right = this.recursiveRemove(node.right, key);
return node;
} else {
// 当前节点的key 与 待删除的key的那个节点相同
// 有三种状况
// 1. 当前节点没有左子树,那么只有让当前节点的右子树直接覆盖当前节点,就表示当前节点被删除了
// 2. 当前节点没有右子树,那么只有让当前节点的左子树直接覆盖当前节点,就表示当前节点被删除了
// 3. 当前节点左右子树都有, 那么又分两种状况,使用前驱删除法或者后继删除法
// 1. 前驱删除法:使用当前节点的左子树上最大的那个节点覆盖当前节点
// 2. 后继删除法:使用当前节点的右子树上最小的那个节点覆盖当前节点
if (node.left === null) {
let rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
} else if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
} else {
let predecessor = this.maximum(node.left);
node.left = this.removeMax(node.left);
this.size++;
// 开始嫁接 当前节点的左右子树
predecessor.left = node.left;
predecessor.right = node.right;
// 将当前节点从根节点剔除
node = node.left = node.right = null;
this.size--;
// 返回嫁接后的新节点
return predecessor;
}
}
}
// 删除操做的两个辅助函数
// 获取最大值、删除最大值
// 之前驱的方式 来辅助删除操做的函数
// 获取最大值
maximum(node) {
// 不再能往右了,说明当前节点已是最大的了
if (!node.right) return node;
// 将复杂的问题渐渐减少规模,从而求出小问题的解,最后用小问题的解构建出原问题的答案
return this.maximum(node.right);
}
// 删除最大值
removeMax(node) {
// 解决最基本的问题
if (!node.right) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
}
// 开始化归
node.right = this.removeMax(node.right);
return node;
}
// 查询操做 返回查询到的元素 +
get(key) {
let node = this.getNode(this.root, key);
if (!node) return null;
return node.value;
}
// 修改操做 +
set(key, value) {
let node = this.getNode(this.root, key);
if (!node) throw new Error(key + " doesn't exist.");
node.value = value;
}
// 返回是否包含该key的元素的判断值 +
contains(key) {
return this.getNode(this.root, key) !== null;
}
// 返回映射中实际的元素个数 +
getSize() {
return this.size;
}
// 返回映射中是否为空的判断值 +
isEmpty() {
return this.size === 0;
}
// @Override toString() 2018-11-05-jwl
toString() {
let mapInfo = `MyBinarySearchTreeMap: size = ${this.size}, data = [ `;
document.body.innerHTML += `MyBinarySearchTreeMap: size = ${ this.size }, data = [ <br/><br/>`;
// 以非递归的前序遍历 输出字符串
let stack = new MyLinkedListStack();
stack.push(this.root);
if (this.root === null) stack.pop();
while (!stack.isEmpty()) {
let node = stack.pop();
if (node.left !== null) stack.push(node.left);
if (node.right !== null) stack.push(node.right);
if (node.left === null && node.right === null) {
mapInfo += ` ${node.toString()} \r\n`;
document.body.innerHTML += ` ${node.toString()} <br/><br/>`;
} else {
mapInfo += ` ${node.toString()}, \r\n`;
document.body.innerHTML += ` ${node.toString()}, <br/><br/>`;
}
}
mapInfo += ` ] \r\n`;
document.body.innerHTML += ` ] <br/><br/>`;
return mapInfo;
}
}
复制代码
O(nlogn)
级别的,Main
// main 函数
class Main {
constructor() {
this.alterLine('RedBlackTree Comparison Area');
const n = 2000000;
const myBSTMap = new MyBinarySearchTreeMap();
const myAVLTree = new MyAVLTree();
const myRedBlackTree = new MyRedBlackTree();
let performanceTest1 = new PerformanceTest();
const random = Math.random;
let arrNumber = new Array(n);
// 循环添加随机数的值
for (let i = 0; i < n; i++) arrNumber[i] = Math.floor(n * random());
this.alterLine('MyBSTMap Comparison Area');
const myBSTMapInfo = performanceTest1.testCustomFn(function() {
// 添加
for (const word of arrNumber)
myBSTMap.add(word, String.fromCharCode(word));
});
// 总毫秒数:4771
console.log(myBSTMapInfo + ' 节点个数:' + myBSTMap.getSize());
this.show(myBSTMapInfo + ' 节点个数:' + myBSTMap.getSize());
this.alterLine('MyAVLTree Comparison Area');
// const that = this;
const myAVLTreeInfo = performanceTest1.testCustomFn(function() {
for (const word of arrNumber)
myAVLTree.add(word, String.fromCharCode(word));
});
// 总毫秒数:6226
console.log(myAVLTreeInfo + ' 节点个数:' + myAVLTree.getSize());
this.show(myAVLTreeInfo + ' 节点个数:' + myAVLTree.getSize());
this.alterLine('MyRedBlackTree Comparison Area');
const myRedBlackTreeInfo = performanceTest1.testCustomFn(function() {
for (const word of arrNumber)
myRedBlackTree.add(word, String.fromCharCode(word));
});
// 总毫秒数:6396
console.log(
myRedBlackTreeInfo + ' 节点个数:' + myRedBlackTree.getSize()
);
this.show(
myRedBlackTreeInfo + ' 节点个数:' + myRedBlackTree.getSize()
);
}
// 将内容显示在页面上
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();
};
复制代码