【从蛋壳到满天飞】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 (一个一个的工程)java
所有源代码已上传 github,点击我吧,光看文章可以掌握两成,动手敲代码、动脑思考、画图才能够掌握八成。node
本文章适合 对数据结构想了解而且感兴趣的人群,文章风格一如既往如此,就以为手机上看起来比较方便,这样显得比较有条理,整理这些笔记加源码,时间跨度也算将近半年时间了,但愿对想学习数据结构的人或者正在学习数据结构的人群有帮助。python
MySet
void add (e)
: 不能添加剧复元素void remove (e)
boolean conatains (e)
int getSize ()
boolean isEmpty ()
(class: MyBinarySearchTree, class: MyBSTSet, class: Main)
c++
MyBinarySearchTreegit
// 自定义二分搜索树节点
class MyBinarySearchTreeNode {
constructor(element, left = null, right = null) {
// 实际存储的元素
this.element = element;
// 当前节点的左子树
this.left = left;
// 当前节点的右子树
this.right = right;
}
}
// 自定义二分搜索树
class MyBinarySearchTree {
constructor() {
this.root = null;
this.size = 0;
}
// 添加元素到二分搜索树中 +
add(element) {
if (element === null) throw new Error("element is null. can't store.");
this.root = this.recursiveAdd(this.root, element);
}
// 添加元素到二分搜索树中 递归算法 -
recursiveAdd(node, newElement) {
// 解决最基本的问题 也就是递归函数调用的终止条件
if (node === null) {
this.size++;
return new MyBinarySearchTreeNode(newElement);
}
// 1. 当前节点的元素比新元素大
// 那么新元素就会被添加到当前节点的左子树去
// 2. 当前节点的元素比新元素小
// 那么新元素就会被添加到当前节点的右子树去
// 3. 当前节点的元素比新元素相等
// 什么都不作了,由于目前不添加剧复的元素
if (this.compare(node.element, newElement) > 0)
node.left = this.recursiveAdd(node.left, newElement);
else if (this.compare(node.element, newElement) < 0)
node.right = this.recursiveAdd(node.right, newElement);
else {
}
// 将复杂问题分解成多个性质相同的小问题,
// 而后求出小问题的答案,
// 最终构建出原问题的答案
return node;
}
// 判断二分搜索树中是否包含某个元素 +
contains(element) {
if (this.root === null) throw new Error("root is null. can't query.");
return this.recursiveContains(this.root, element);
}
// 判断二分搜索树种是否包含某个元素 递归算法 -
recursiveContains(node, element) {
if (node === null) return false;
// 当前节点元素比 要搜索的元素 大
if (this.compare(node.element, element) > 0)
return this.recursiveContains(node.left, element);
else if (this.compare(node.element, element) < 0)
// 当前元素比 要搜索的元素 小
return this.recursiveContains(node.right, element);
// 两个元素相等
else return true;
}
// 找到二分搜索树中的最大值的元素 +
maximum() {
if (this.size === 0) throw new Error('binary search tree is empty.');
return this.recursiveMaximum(this.root).element;
}
// 找到二分搜索树中的最大值的元素的节点 递归算法 -
recursiveMaximum(node) {
// 解决最基本的问题 向右走再也走不动了,说明当前节点就是最大值节点。
if (node.right === null) return node;
return this.recursiveMaximum(node.right);
}
// 删除二分搜索树中最大值的元素的节点,并返回这个节点的元素 +
removeMax() {
let maxElement = this.maximum();
this.root = this.recursiveRemoveMax(this.root);
return maxElement;
}
// 删除二分搜索树中最大值的元素的节点,并返回这个节点 递归算法 -
recursiveRemoveMax(node) {
if (node.right === null) {
// 先存 当前这个节点的左子树,
// 由于可能当前这个节点仅仅没有右子树,只有左子树,
// 那么左子树能够替代当前这个节点。
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
}
node.right = this.recursiveRemoveMax(node.right);
return node;
}
// 找到二分搜索树中的最小值 +
minimum() {
if (this.size === 0) throw new Error('binary search tree is empty.');
return this.recursiveMinimum(this.root).element;
}
// 找到二分搜索树中的最小值的元素的节点 递归算法 -
recursiveMinimum(node) {
if (node.left === null) return node;
return this.recursiveMinimum(node.left);
}
// 删除二分搜索树中最小值的元素的节点,并返回这个节点的元素 +
removeMin() {
let leftNode = this.minimum();
this.root = this.recursiveRemoveMin(this.root);
return leftNode;
}
// 删除二分搜索树中最小值的元素的节点,并返回这个节点 递归算法 -
recursiveRemoveMin(node) {
// 解决最简单的问题
if (node.left === null) {
let rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
}
// 将复杂的问题拆分为性质相同的小问题,
// 而后求出这些小问题的解后构建出原问题的答案
node.left = this.recursiveRemoveMin(node.left);
return node;
}
// 删除二分搜索树上的任意节点
remove(element) {
this.root = this.recursiveRemove(this.root, element);
}
// 删除二分搜索树上的任意节点 递归算法
// 返回删除对应元素节点后新的二分搜索树的根
recursiveRemove(node, element) {
if (node === null) return null;
// 当前节点的元素值比待删除的元素小 那么就向当前节点的右子树中去找
if (this.compare(node.element, element) < 0) {
node.right = this.recursiveRemove(node.right, element);
return node;
} else if (this.compare(node.element, element) > 0) {
// 向当前节点的左子树中去找
node.left = this.recursiveRemove(node.left, element);
return node;
} else {
// 若是找到了相同值的节点了,开始进行相应的处理
// 若是这个节点左子树为空,那么就让这个节点的右子树覆盖当前节点
if (node.left === null) {
let rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
}
// 若是当前节点的右子树为空,那么就让这个节点的左子树覆盖当前节点
if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
}
// 若是当前节点的左右子树都不为空,那么就开始特殊操做
// 1. 先找到当前节点右子树上最小的那个节点,保存起来
// 2. 而后删除掉当前节点右子树上最小的那个节点,
// 3. 让保存起来的那个节点覆盖掉当前节点
// 1. 也就是保存起来的那个节点的right = 删除掉当前节点右子树上最小的节点后返回的那个节点
// 2. 再让保存起来的那个节点的left = 当前节点的left
// 4. 解除当前节点及其left和right,全都赋值为null,这样就至关于把当前节点从二分搜索树中剔除了
// 5. 返回保存的这个节点
let successtor = this.recursiveMinimum(node.right);
successtor.right = this.recursiveRemoveMin(node.right);
// 恢复removeMin 操做的this.size -- 带来的影响
this.size++;
successtor.left = node.left;
// 开始正式的删除当前节点的操做
node = node.left = node.right = null;
this.size--;
// 返回当前保存的节点
return successtor;
}
}
// 前序遍历 +
preOrder(operator) {
this.recursivePreOrder(this.root, operator);
}
// 前序遍历 递归算法 -
recursivePreOrder(node, operator) {
if (node === null) return;
// 调用一下操做方法
operator(node.element);
console.log(node, node.element);
// 继续递归遍历左右子树
this.recursivePreOrder(node.left, operator);
this.recursivePreOrder(node.right, operator);
}
// 前序遍历 非递归算法 +
nonRecursivePreOrder(operator) {
let stack = new MyLinkedListStack();
stack.push(this.root);
let node = null;
while (!stack.isEmpty()) {
// 出栈操做
node = stack.pop();
operator(node.element); // 访问当前的节点
console.log(node.element);
// 栈是先入后出的,把须要后访问的节点 先放进去,先访问的节点后放进去
// 前序遍历是访问当前节点,而后再遍历左子树,最后遍历右子树
if (node.right !== null) stack.push(node.right);
if (node.left !== null) stack.push(node.left);
}
}
// 中序遍历 +
inOrder(operator) {
this.recursiveInOrder(this.root, operator);
}
// 中序遍历 递归算法 -
recursiveInOrder(node, operator) {
if (node == null) return;
this.recursiveInOrder(node.left, operator);
operator(node.element);
console.log(node.element);
this.recursiveInOrder(node.right, operator);
}
// 后序遍历 +
postOrder(operator) {
this.recursivePostOrder(this.root, operator);
}
// 后序遍历 递归算法 -
recursivePostOrder(node, operator) {
if (node == null) return;
this.recursivePostOrder(node.left, operator);
this.recursivePostOrder(node.right, operator);
operator(node.element);
console.log(node.element);
}
// 层序遍历
levelOrder(operator) {
let queue = new MyLinkedListQueue();
queue.enqueue(this.root);
let node = null;
while (!queue.isEmpty()) {
node = queue.dequeue();
operator(node.element);
console.log(node.element);
// 队列 是先进先出的,因此从左往右入队
// 栈 是后进先出的, 因此从右往左入栈
if (node.left !== null) queue.enqueue(node.left);
if (node.right !== null) queue.enqueue(node.right);
}
}
// 获取二分搜索树中节点个数 +
getSize() {
return this.size;
}
// 返回二分搜索树是否为空的bool值 +
isEmpty() {
return this.size === 0;
}
// 新增一个比较的方法,专门用来比较新增的元素大小 -
// 第一个元素比第二个元素大 就返回 1
// 第一个元素比第二个元素小 就返回 -1
// 第一个元素比第二个元素相等 就返回 0
compare(elementA, elementB) {
if (elementA === null || elementB === null)
throw new Error("element is null. can't compare.");
// 先直接写死
if (elementA > elementB) return 1;
else if (elementA < elementB) return -1;
else return 0;
}
// 输出二分搜索树中的信息
// @Override toString 2018-11-03-jwl
toString() {
let treeInfo = '';
treeInfo += this.getBinarySearchTreeString(this.root, 0, treeInfo);
return treeInfo;
}
// 写一个辅助函数,用来生成二分搜索树信息的字符串
getBinarySearchTreeString(node, depth, treeInfo, pageContent = '') {
//之前序遍历的方式
if (node === null) {
treeInfo += this.getDepthString(depth) + 'null \r\n';
pageContent = this.getDepthString(depth) + 'null<br /><br />';
document.body.innerHTML += `${pageContent}`;
return treeInfo;
}
treeInfo += this.getDepthString(depth) + node.element + '\r\n';
pageContent =
this.getDepthString(depth) + node.element + '<br /><br />';
document.body.innerHTML += `${pageContent}`;
treeInfo = this.getBinarySearchTreeString(
node.left,
depth + 1,
treeInfo
);
treeInfo = this.getBinarySearchTreeString(
node.right,
depth + 1,
treeInfo
);
return treeInfo;
}
// 写一个辅助函数,用来生成递归深度字符串
getDepthString(depth) {
let depthString = '';
for (var i = 0; i < depth; i++) {
depthString += '-- ';
}
return depthString;
}
}
复制代码
MyBSTSetgithub
// 自定义二分搜索树集合Set
class MyBinarySearchTreeSet {
constructor() {
// 借用二分搜索树来实现这个接口
this.myBinarySearchTree = new MyBinarySearchTree();
}
// 添加元素
add(element) {
this.myBinarySearchTree.add(element);
}
// 移除元素
remove(element) {
this.myBinarySearchTree.remove(element);
}
// 是否包含这个元素
contains(element) {
return this.myBinarySearchTree.contains(element);
}
// 遍历操做
// 第一个参数 是回掉函数,
// 第二个参数 是遍历的方式 深度优先遍历(前pre、中in、后post),广度优先遍历(层序level)
each(operator, method) {
// 遍历方式默认是非递归的前序遍历,
// 其它的遍历方式就是递归的前、中、后、层序遍历。
switch (method) {
case 'pre':
this.myBinarySearchTree.preOrder(operator);
break;
case 'in':
this.myBinarySearchTree.inOrder(operator);
break;
case 'post':
this.myBinarySearchTree.postOrder(operator);
break;
case 'level':
this.myBinarySearchTree.levelOrder(operator);
break;
default:
this.myBinarySearchTree.nonRecursivePreOrder(operator);
break;
}
}
// 获取集合中实际的元素个数
getSize() {
return this.myBinarySearchTree.getSize();
}
// 返回集合是否为空的bool值
isEmpty() {
return this.myBinarySearchTree.isEmpty();
}
}
复制代码
Main面试
// main 函数
class Main {
constructor() {
this.alterLine('MyBinarySearchTreeSet Area');
{
let n = 5;
let set = new MyBinarySearchTreeSet();
let random = Math.random;
let temp = null;
for (var i = 0; i < n; i++) {
temp = random();
set.add(n * n * n * temp);
set.add(n * n * n * temp);
set.add(n * n * n * temp);
set.add(n * n * n * temp);
set.add(n * n * n * temp);
set.add(n * n * n * temp);
set.add(n * n * n * temp);
}
console.log(set.getSize());
this.show(set.getSize());
let array = new MyArray(n);
set.each(element => {
console.log(element);
this.show(element);
array.add(element);
});
for (var i = 0; i < array.getSize(); i++) {
set.remove(array.get(i));
}
console.log(set.getSize());
this.show(set.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();
};
复制代码
集合设计的是一个接口,算法
二分搜索树和链表都属于动态数据结构数据库
// 二分搜索树的Node
class Node {
e; // Element
left; // Node
right; // Node
}
// 链表的Node
class Node {
e; // Element
next; // Node
}
复制代码
MySet
void add (e)
: 不能添加剧复元素void remove (e)
boolean conatains (e)
int getSize ()
boolean isEmpty ()
( class: MyLinkedList, class: MyLinkedListSet)
MyLinkedList
// 自定义链表节点
class MyLinkedListNode {
constructor(element = null, next = null) {
this.element = element;
this.next = next;
}
// 将一个数组对象 转换为一个链表 而且追加到当前节点上
appendToLinkedListNode(array) {
let head = null;
if (this.element === null) {
// 头部添加
head = this;
head.element = array[0];
head.next = null;
} else {
// 插入式
head = new MyLinkedListNode(array[0], null);
head.next = this.next;
this.next = head;
}
// 添加节点的方式 头部添加、尾部添加、中间插入
// 尾部添加节点的方式
for (var i = 1; i < array.length; i++) {
head.next = new MyLinkedListNode(array[i], null);
head = head.next;
}
}
//@override
// toString 2018-10-20-jwl
toString() {
return this.element.toString();
}
}
// 自定义链表
class MyLinkedList {
constructor() {
this.dummyHead = new MyLinkedListNode(null, null);
this.size = 0;
}
// 获取链表中实际的节点个数
getSize() {
return this.size;
}
// 判断链表是否为空
isEmpty() {
return this.size === 0;
}
// 在链表头添加节点
addFirst(element) {
// let node = new MyLinkedListNode(element, null);
// node.next = this.head;
// this.head = node;
// this.size ++;
// 改用虚拟头节点
this.insert(0, element);
}
// 在链表指定索引处插入节点
insert(index, element) {
if (index < 0 || index > this.size) {
throw new Error('add error. index < 0 or index > size');
}
// 第一个prev就是dummyHead
let prev = this.dummyHead;
// 以前变量i(索引)之因此要从 1 开始,由于索引为0的那个节点就是head,循环就不须要从0开始了,
// 如今索引之因此要从 0 开始, 由于初始化时 多增长了一个虚拟的头节点
// (由于这个索引为0的节点并非dummyHead,dummyHead这个节点并不记录为链表中的实际节点),
// 小于index是由于要找到指定索引位置的前一个节点
// 循环是由于 要继续找到指定索引处的节点的前一个节点
for (var i = 0; i < index; i++) {
// 不停的切换引用,直到找到对应索引处节点的下一个节点
prev = prev.next;
}
let node = new MyLinkedListNode(element, null);
node.next = prev.next;
prev.next = node;
this.size++;
}
// 扩展:在链表最后一个节点的位置添加节点
addLast(element) {
this.insert(this.size, element);
}
// 获取指定索引位置的元素
get(index) {
// 判断索引合法性
if (index < 0 || index >= this.size) {
throw new Error('get error. index < 0 or index >= size');
}
// 若是你要找指定索引节点的前一个节点 就使用dummyHead
// 若是你要找到指定索引节点 就使用dummyHead.next
// 由于duumyHead并非第一个节点,由于它是一个虚拟节点,
// dummyHead.next才是真正被记录的第一个节点。
let node = this.dummyHead.next;
for (var i = 0; i < index; i++) {
node = node.next;
}
return node.element;
}
// 获取头节点的元素
getFirst() {
return this.get(0);
}
// 获取尾节点的元素
getLast() {
return this.get(this.size - 1);
}
// 设置指定索引位置的元素值
set(index, element) {
// 判断索引合法性
if (index < 0 || index >= this.size) {
throw new Error('get error. index < 0 or index >= size');
}
// 从第一个真正被记录的节点开始,从0开始
let node = this.dummyHead.next;
// 索引为 0 时,实际上切换到的节点 它的索引为 1
// i < index ,当索引为 index-1 时, 实际上切换到的节点 它的索引为index
for (let i = 0; i < index; i++) {
// 每一次切换 都只是改变引用
// 不的在链表中找下一个节点
node = node.next;
}
node.element = element;
}
// 全部节点中是否有包含该元素
contains(element) {
let node = this.dummyHead;
while (node.next !== null) {
if (node.next.element === element) return true;
// 不停的向下切换
node = node.next;
}
return false;
}
// 删除指定索引位置的节点
remove(index) {
// 验证索引的合法性
if (index < 0 || index >= this.size) {
throw new Error('remove error. index < 0 or index > this.size');
}
let node = this.dummyHead;
for (let i = 0; i < index; i++) {
node = node.next;
}
// 待删除的节点
let delNode = node.next;
// 给待删除那个节点的前一个的节点的next引用替换为
// 但删除的这个节点的next
node.next = delNode.next;
// 或者这样也行
// node.next = node.next.next;
// 临时存储待删除的那个节点里的元素
let element = delNode.element;
// 清空 待删除的节点
delNode = null;
this.size--;
return element;
}
// 扩展:移除链表头的元素
removeFirst() {
return this.remove(0);
}
// 扩展:移除链表尾部的元素
removeLast() {
return this.remove(this.size - 1);
}
// 输出链表中的信息
// @Override toString 2018-10-21-jwl
toString() {
let arrInfo = `LinkedList: size = ${this.size},\n`;
arrInfo += `data = front [`;
let node = this.dummyHead.next;
while (node.next !== null) {
arrInfo += `${node.element}->`;
node = node.next;
}
arrInfo += 'NULL] tail';
// 在页面上展现
document.body.innerHTML += `${arrInfo}<br /><br /> `;
return arrInfo;
}
}
复制代码
MyLinkedListSet
// 自定义链表集合Set
class MyLinkedListSet {
//
constructor() {
this.myLinkedList = new MyLinkedList();
}
add(element) {
if (!this.myLinkedList.contains(element))
this.myLinkedList.addFirst(element);
}
remove(element) {
this.myLinkedList.removeElement(element);
}
contains(element) {
return this.myLinkedList.contains(element);
}
each(operator) {
let size = this.myLinkedList.getSize();
for (var i = 0; i < size; i++) {
operator(this.myLinkedList.get(i));
}
}
getSize() {
return this.myLinkedList.getSize();
}
isEmpty() {
return this.myLinkedList.isEmpty();
}
}
复制代码
O(n)
,O(h) or O(log n)
,h = log2 (n+1) = O(log2 n)
,O(log2 n)
1*n、2*n、100*n、10000*n
,它们都是线性的一个关系,O(log n)
。O(log n)
,O(log n)
实际上是一个最优的状况,O(log n)
这个级别,[1,2,3,4,5,6]
,O(n)
这样的性能,O(log n)
的时间复杂度,O(n)
,O(h)
,log n
,若是很是的不巧,那么这个 h 等于 n。n=16
的时候,logn 让它取 2 为底,log2 n
的值为 4,n 的值就是 16。n=1024
的时候,logn 仍是让它取 2 为底,log2 n
的值为 10,n=100万
这个级别的数据的话,logn 仍是让它取 2 为底,log2 n
的值为 20,O(log n)
这个算法和O(n)
这个算法,O(log n)
这个算法花一秒时间就跑完的话,O(n)
这个算法就须要花 5 万秒,大概 14 个小时,O(log n)
这个算法要花一天的时间跑完这个程序,O(n)
这个算法就须要花 5 万天,大概 137 年的事件才能跑出结果,O(log n)
算法的程序就跑出结果了,O(n)
算法的程序你这一生都跑不出结果来。O(log n)
和O(n)
差很少,O(n)
O(1)
,O(n)
。O(n)
O(n)
O(n)
O(n)
O(h) or O(log n)
高度
,O(h)
这个级别的,O(h) or O(log n)
O(h) or O(log n)
2^(1-1) + 2^(2-1) + ... + 2^(h-1)
= 1 x (1-2^(h)) / (1-2) = 2^(h) - 1 = n
。h = log2 (n+1) = O(log2 n)
,O(log2 n)
1*n、2*n、100*n、10000*n
,它们都是线性的一个关系,O(log n)
。(class: MyLinkedList, class: MyBinarySearchTree, class: PerformanceTest,
class: MyLinkedListSet, class:MyBSTSet , class: Main)
MyLinkedList
// 自定义链表节点
class MyLinkedListNode {
constructor(element = null, next = null) {
this.element = element;
this.next = next;
}
// 将一个数组对象 转换为一个链表 而且追加到当前节点上
appendToLinkedListNode(array) {
let head = null;
if (this.element === null) {
// 头部添加
head = this;
head.element = array[0];
head.next = null;
} else {
// 插入式
head = new MyLinkedListNode(array[0], null);
head.next = this.next;
this.next = head;
}
// 添加节点的方式 头部添加、尾部添加、中间插入
// 尾部添加节点的方式
for (var i = 1; i < array.length; i++) {
head.next = new MyLinkedListNode(array[i], null);
head = head.next;
}
}
//@override
// toString 2018-10-20-jwl
toString() {
return this.element.toString();
}
}
// 自定义链表
class MyLinkedList {
constructor() {
this.dummyHead = new MyLinkedListNode(null, null);
this.size = 0;
}
// 获取链表中实际的节点个数
getSize() {
return this.size;
}
// 判断链表是否为空
isEmpty() {
return this.size === 0;
}
// 在链表头添加节点
addFirst(element) {
// let node = new MyLinkedListNode(element, null);
// node.next = this.head;
// this.head = node;
// this.size ++;
// 改用虚拟头节点
this.insert(0, element);
}
// 在链表指定索引处插入节点
insert(index, element) {
if (index < 0 || index > this.size) {
throw new Error('add error. index < 0 or index > size');
}
// 第一个prev就是dummyHead
let prev = this.dummyHead;
// 以前变量i(索引)之因此要从 1 开始,由于索引为0的那个节点就是head,循环就不须要从0开始了,
// 如今索引之因此要从 0 开始, 由于初始化时 多增长了一个虚拟的头节点
// (由于这个索引为0的节点并非dummyHead,dummyHead这个节点并不记录为链表中的实际节点),
// 小于index是由于要找到指定索引位置的前一个节点
// 循环是由于 要继续找到指定索引处的节点的前一个节点
for (var i = 0; i < index; i++) {
// 不停的切换引用,直到找到对应索引处节点的下一个节点
prev = prev.next;
}
let node = new MyLinkedListNode(element, null);
node.next = prev.next;
prev.next = node;
this.size++;
}
// 扩展:在链表最后一个节点的位置添加节点
addLast(element) {
this.insert(this.size, element);
}
// 获取指定索引位置的元素
get(index) {
// 判断索引合法性
if (index < 0 || index >= this.size) {
throw new Error('get error. index < 0 or index >= size');
}
// 若是你要找指定索引节点的前一个节点 就使用dummyHead
// 若是你要找到指定索引节点 就使用dummyHead.next
// 由于duumyHead并非第一个节点,由于它是一个虚拟节点,
// dummyHead.next才是真正被记录的第一个节点。
let node = this.dummyHead.next;
for (var i = 0; i < index; i++) {
node = node.next;
}
return node.element;
}
// 获取头节点的元素
getFirst() {
return this.get(0);
}
// 获取尾节点的元素
getLast() {
return this.get(this.size - 1);
}
// 设置指定索引位置的元素值
set(index, element) {
// 判断索引合法性
if (index < 0 || index >= this.size) {
throw new Error('get error. index < 0 or index >= size');
}
// 从第一个真正被记录的节点开始,从0开始
let node = this.dummyHead.next;
// 索引为 0 时,实际上切换到的节点 它的索引为 1
// i < index ,当索引为 index-1 时, 实际上切换到的节点 它的索引为index
for (let i = 0; i < index; i++) {
// 每一次切换 都只是改变引用
// 不的在链表中找下一个节点
node = node.next;
}
node.element = element;
}
// 全部节点中是否有包含该元素
contains(element) {
let node = this.dummyHead;
while (node.next !== null) {
if (node.next.element === element) return true;
// 不停的向下切换
node = node.next;
}
return false;
}
// 删除指定索引位置的节点
remove(index) {
// 验证索引的合法性
if (index < 0 || index >= this.size) {
throw new Error('remove error. index < 0 or index > this.size');
}
let node = this.dummyHead;
for (let i = 0; i < index; i++) {
node = node.next;
}
// 待删除的节点
let delNode = node.next;
// 给待删除那个节点的前一个的节点的next引用替换为
// 但删除的这个节点的next
node.next = delNode.next;
// 或者这样也行
// node.next = node.next.next;
// 临时存储待删除的那个节点里的元素
let element = delNode.element;
// 清空 待删除的节点
delNode = null;
this.size--;
return element;
}
// 扩展:移除链表头的元素
removeFirst() {
return this.remove(0);
}
// 扩展:移除链表尾部的元素
removeLast() {
return this.remove(this.size - 1);
}
// 新增:根据元素来删除链表中的元素 2018-11-05
removeElement(element) {
let prev = this.dummyHead;
while (prev.next !== null) {
if (prev.next.element === element) break;
prev = prev.next;
}
if (prev.next !== null) {
let delNode = prev.next;
prev.next = delNode.next;
delNode = null;
this.size--;
}
}
// 输出链表中的信息
// @Override toString 2018-10-21-jwl
toString() {
let arrInfo = `LinkedList: size = ${this.size},\n`;
arrInfo += `data = front [`;
let node = this.dummyHead.next;
while (node.next !== null) {
arrInfo += `${node.element}->`;
node = node.next;
}
arrInfo += 'NULL] tail';
// 在页面上展现
document.body.innerHTML += `${arrInfo}<br /><br /> `;
return arrInfo;
}
}
复制代码
MyBinarySearchTree
// 自定义二分搜索树节点
class MyBinarySearchTreeNode {
constructor(element, left = null, right = null) {
// 实际存储的元素
this.element = element;
// 当前节点的左子树
this.left = left;
// 当前节点的右子树
this.right = right;
}
}
// 自定义二分搜索树
class MyBinarySearchTree {
constructor() {
this.root = null;
this.size = 0;
}
// 添加元素到二分搜索树中 +
add(element) {
if (element === null) throw new Error("element is null. can't store.");
this.root = this.recursiveAdd(this.root, element);
}
// 添加元素到二分搜索树中 递归算法 -
recursiveAdd(node, newElement) {
// 解决最基本的问题 也就是递归函数调用的终止条件
if (node === null) {
this.size++;
return new MyBinarySearchTreeNode(newElement);
}
// 1. 当前节点的元素比新元素大
// 那么新元素就会被添加到当前节点的左子树去
// 2. 当前节点的元素比新元素小
// 那么新元素就会被添加到当前节点的右子树去
// 3. 当前节点的元素比新元素相等
// 什么都不作了,由于目前不添加剧复的元素
if (this.compare(node.element, newElement) > 0)
node.left = this.recursiveAdd(node.left, newElement);
else if (this.compare(node.element, newElement) < 0)
node.right = this.recursiveAdd(node.right, newElement);
else {
}
// 将复杂问题分解成多个性质相同的小问题,
// 而后求出小问题的答案,
// 最终构建出原问题的答案
return node;
}
// 判断二分搜索树中是否包含某个元素 +
contains(element) {
if (this.root === null) throw new Error("root is null. can't query.");
return this.recursiveContains(this.root, element);
}
// 判断二分搜索树种是否包含某个元素 递归算法 -
recursiveContains(node, element) {
if (node === null) return false;
// 当前节点元素比 要搜索的元素 大
if (this.compare(node.element, element) > 0)
return this.recursiveContains(node.left, element);
else if (this.compare(node.element, element) < 0)
// 当前元素比 要搜索的元素 小
return this.recursiveContains(node.right, element);
// 两个元素相等
else return true;
}
// 找到二分搜索树中的最大值的元素 +
maximum() {
if (this.size === 0) throw new Error('binary search tree is empty.');
return this.recursiveMaximum(this.root).element;
}
// 找到二分搜索树中的最大值的元素的节点 递归算法 -
recursiveMaximum(node) {
// 解决最基本的问题 向右走再也走不动了,说明当前节点就是最大值节点。
if (node.right === null) return node;
return this.recursiveMaximum(node.right);
}
// 删除二分搜索树中最大值的元素的节点,并返回这个节点的元素 +
removeMax() {
let maxElement = this.maximum();
this.root = this.recursiveRemoveMax(this.root);
return maxElement;
}
// 删除二分搜索树中最大值的元素的节点,并返回这个节点 递归算法 -
recursiveRemoveMax(node) {
if (node.right === null) {
// 先存 当前这个节点的左子树,
// 由于可能当前这个节点仅仅没有右子树,只有左子树,
// 那么左子树能够替代当前这个节点。
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
}
node.right = this.recursiveRemoveMax(node.right);
return node;
}
// 找到二分搜索树中的最小值 +
minimum() {
if (this.size === 0) throw new Error('binary search tree is empty.');
return this.recursiveMinimum(this.root).element;
}
// 找到二分搜索树中的最小值的元素的节点 递归算法 -
recursiveMinimum(node) {
if (node.left === null) return node;
return this.recursiveMinimum(node.left);
}
// 删除二分搜索树中最小值的元素的节点,并返回这个节点的元素 +
removeMin() {
let leftNode = this.minimum();
this.root = this.recursiveRemoveMin(this.root);
return leftNode;
}
// 删除二分搜索树中最小值的元素的节点,并返回这个节点 递归算法 -
recursiveRemoveMin(node) {
// 解决最简单的问题
if (node.left === null) {
let rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
}
// 将复杂的问题拆分为性质相同的小问题,
// 而后求出这些小问题的解后构建出原问题的答案
node.left = this.recursiveRemoveMin(node.left);
return node;
}
// 删除二分搜索树上的任意节点
remove(element) {
this.root = this.recursiveRemove(this.root, element);
}
// 删除二分搜索树上的任意节点 递归算法
// 返回删除对应元素节点后新的二分搜索树的根
recursiveRemove(node, element) {
if (node === null) return null;
// 当前节点的元素值比待删除的元素小 那么就向当前节点的右子树中去找
if (this.compare(node.element, element) < 0) {
node.right = this.recursiveRemove(node.right, element);
return node;
} else if (this.compare(node.element, element) > 0) {
// 向当前节点的左子树中去找
node.left = this.recursiveRemove(node.left, element);
return node;
} else {
// 若是找到了相同值的节点了,开始进行相应的处理
// 若是这个节点左子树为空,那么就让这个节点的右子树覆盖当前节点
if (node.left === null) {
let rightNode = node.right;
node.right = null;
this.size--;
return rightNode;
}
// 若是当前节点的右子树为空,那么就让这个节点的左子树覆盖当前节点
if (node.right === null) {
let leftNode = node.left;
node.left = null;
this.size--;
return leftNode;
}
// 若是当前节点的左右子树都不为空,那么就开始特殊操做
// 1. 先找到当前节点右子树上最小的那个节点,保存起来
// 2. 而后删除掉当前节点右子树上最小的那个节点,
// 3. 让保存起来的那个节点覆盖掉当前节点
// 1. 也就是保存起来的那个节点的right = 删除掉当前节点右子树上最小的节点后返回的那个节点
// 2. 再让保存起来的那个节点的left = 当前节点的left
// 4. 解除当前节点及其left和right,全都赋值为null,这样就至关于把当前节点从二分搜索树中剔除了
// 5. 返回保存的这个节点
let successtor = this.recursiveMinimum(node.right);
successtor.right = this.recursiveRemoveMin(node.right);
// 恢复removeMin 操做的this.size -- 带来的影响
this.size++;
successtor.left = node.left;
// 开始正式的删除当前节点的操做
node = node.left = node.right = null;
this.size--;
// 返回当前保存的节点
return successtor;
}
}
// 前序遍历 +
preOrder(operator) {
this.recursivePreOrder(this.root, operator);
}
// 前序遍历 递归算法 -
recursivePreOrder(node, operator) {
if (node === null) return;
// 调用一下操做方法
operator(node.element);
console.log(node, node.element);
// 继续递归遍历左右子树
this.recursivePreOrder(node.left, operator);
this.recursivePreOrder(node.right, operator);
}
// 前序遍历 非递归算法 +
nonRecursivePreOrder(operator) {
let stack = new MyLinkedListStack();
stack.push(this.root);
let node = null;
while (!stack.isEmpty()) {
// 出栈操做
node = stack.pop();
operator(node.element); // 访问当前的节点
console.log(node.element);
// 栈是先入后出的,把须要后访问的节点 先放进去,先访问的节点后放进去
// 前序遍历是访问当前节点,而后再遍历左子树,最后遍历右子树
if (node.right !== null) stack.push(node.right);
if (node.left !== null) stack.push(node.left);
}
}
// 中序遍历 +
inOrder(operator) {
this.recursiveInOrder(this.root, operator);
}
// 中序遍历 递归算法 -
recursiveInOrder(node, operator) {
if (node == null) return;
this.recursiveInOrder(node.left, operator);
operator(node.element);
console.log(node.element);
this.recursiveInOrder(node.right, operator);
}
// 后序遍历 +
postOrder(operator) {
this.recursivePostOrder(this.root, operator);
}
// 后序遍历 递归算法 -
recursivePostOrder(node, operator) {
if (node == null) return;
this.recursivePostOrder(node.left, operator);
this.recursivePostOrder(node.right, operator);
operator(node.element);
console.log(node.element);
}
// 层序遍历
levelOrder(operator) {
let queue = new MyLinkedListQueue();
queue.enqueue(this.root);
let node = null;
while (!queue.isEmpty()) {
node = queue.dequeue();
operator(node.element);
console.log(node.element);
// 队列 是先进先出的,因此从左往右入队
// 栈 是后进先出的, 因此从右往左入栈
if (node.left !== null) queue.enqueue(node.left);
if (node.right !== null) queue.enqueue(node.right);
}
}
// 获取二分搜索树中节点个数 +
getSize() {
return this.size;
}
// 返回二分搜索树是否为空的bool值 +
isEmpty() {
return this.size === 0;
}
// 新增一个比较的方法,专门用来比较新增的元素大小 -
// 第一个元素比第二个元素大 就返回 1
// 第一个元素比第二个元素小 就返回 -1
// 第一个元素比第二个元素相等 就返回 0
compare(elementA, elementB) {
if (elementA === null || elementB === null)
throw new Error("element is null. can't compare.");
// 先直接写死
if (elementA > elementB) return 1;
else if (elementA < elementB) return -1;
else return 0;
}
// 输出二分搜索树中的信息
// @Override toString 2018-11-03-jwl
toString() {
let treeInfo = '';
treeInfo += this.getBinarySearchTreeString(this.root, 0, treeInfo);
return treeInfo;
}
// 写一个辅助函数,用来生成二分搜索树信息的字符串
getBinarySearchTreeString(node, depth, treeInfo, pageContent = '') {
//之前序遍历的方式
if (node === null) {
treeInfo += this.getDepthString(depth) + 'null \r\n';
pageContent = this.getDepthString(depth) + 'null<br /><br />';
document.body.innerHTML += `${pageContent}`;
return treeInfo;
}
treeInfo += this.getDepthString(depth) + node.element + '\r\n';
pageContent =
this.getDepthString(depth) + node.element + '<br /><br />';
document.body.innerHTML += `${pageContent}`;
treeInfo = this.getBinarySearchTreeString(
node.left,
depth + 1,
treeInfo
);
treeInfo = this.getBinarySearchTreeString(
node.right,
depth + 1,
treeInfo
);
return treeInfo;
}
// 写一个辅助函数,用来生成递归深度字符串
getDepthString(depth) {
let depthString = '';
for (var i = 0; i < depth; i++) {
depthString += '-- ';
}
return depthString;
}
}
复制代码
PerformanceTest
// 性能测试
class PerformanceTest {
constructor() {}
// 对比都列
testQueue(queue, openCount) {
let startTime = Date.now();
let random = Math.random;
for (var i = 0; i < openCount; i++) {
queue.enqueue(random() * openCount);
}
while (!queue.isEmpty()) {
queue.dequeue();
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 对比栈
testStack(stack, openCount) {
let startTime = Date.now();
let random = Math.random;
for (var i = 0; i < openCount; i++) {
stack.push(random() * openCount);
}
while (!stack.isEmpty()) {
stack.pop();
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 对比集合
testSet(set, openCount) {
let startTime = Date.now();
let random = Math.random;
let arr = [];
let temp = null;
// 第一遍测试
for (var i = 0; i < openCount; i++) {
temp = random();
// 添加剧复元素,从而测试集合去重的能力
set.add(temp * openCount);
set.add(temp * openCount);
arr.push(temp * openCount);
}
for (var i = 0; i < openCount; i++) {
set.remove(arr[i]);
}
// 第二遍测试
for (var i = 0; i < openCount; i++) {
set.add(arr[i]);
set.add(arr[i]);
}
while (!set.isEmpty()) {
set.remove(arr[set.getSize() - 1]);
}
let endTime = Date.now();
// 求出两次测试的平均时间
let avgTime = Math.ceil((endTime - startTime) / 2);
return this.calcTime(avgTime);
}
// 计算运行的时间,转换为 天-小时-分钟-秒-毫秒
calcTime(result) {
//获取距离的天数
var day = Math.floor(result / (24 * 60 * 60 * 1000));
//获取距离的小时数
var hours = Math.floor((result / (60 * 60 * 1000)) % 24);
//获取距离的分钟数
var minutes = Math.floor((result / (60 * 1000)) % 60);
//获取距离的秒数
var seconds = Math.floor((result / 1000) % 60);
//获取距离的毫秒数
var milliSeconds = Math.floor(result % 1000);
// 计算时间
day = day < 10 ? '0' + day : day;
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
milliSeconds =
milliSeconds < 100
? milliSeconds < 10
? '00' + milliSeconds
: '0' + milliSeconds
: milliSeconds;
// 输出耗时字符串
result =
day +
'天' +
hours +
'小时' +
minutes +
'分' +
seconds +
'秒' +
milliSeconds +
'毫秒' +
' <<<<============>>>> 总毫秒数:' +
result;
return result;
}
}
复制代码
MyLinkedListSet
// 自定义链表集合Set
class MyLinkedListSet {
//
constructor() {
this.myLinkedList = new MyLinkedList();
}
add(element) {
if (!this.myLinkedList.contains(element))
this.myLinkedList.addFirst(element);
}
remove(element) {
this.myLinkedList.removeElement(element);
}
contains(element) {
return this.myLinkedList.contains(element);
}
each(operator) {
let size = this.myLinkedList.getSize();
for (var i = 0; i < size; i++) {
operator(this.myLinkedList.get(i));
}
}
getSize() {
return this.myLinkedList.getSize();
}
isEmpty() {
return this.myLinkedList.isEmpty();
}
}
复制代码
MyBSTSet
// 自定义二分搜索树集合Set
class MyBinarySearchTreeSet {
constructor() {
// 借用二分搜索树来实现这个接口
this.myBinarySearchTree = new MyBinarySearchTree();
}
// 添加元素
add(element) {
this.myBinarySearchTree.add(element);
}
// 移除元素
remove(element) {
this.myBinarySearchTree.remove(element);
}
// 是否包含这个元素
contains(element) {
return this.myBinarySearchTree.contains(element);
}
// 遍历操做
// 第一个参数 是回掉函数,
// 第二个参数 是遍历的方式 深度优先遍历(前pre、中in、后post),广度优先遍历(层序level)
each(operator, method) {
// 遍历方式默认是非递归的前序遍历,
// 其它的遍历方式就是递归的前、中、后、层序遍历。
switch (method) {
case 'pre':
this.myBinarySearchTree.preOrder(operator);
break;
case 'in':
this.myBinarySearchTree.inOrder(operator);
break;
case 'post':
this.myBinarySearchTree.postOrder(operator);
break;
case 'level':
this.myBinarySearchTree.levelOrder(operator);
break;
default:
this.myBinarySearchTree.nonRecursivePreOrder(operator);
break;
}
}
// 获取集合中实际的元素个数
getSize() {
return this.myBinarySearchTree.getSize();
}
// 返回集合是否为空的bool值
isEmpty() {
return this.myBinarySearchTree.isEmpty();
}
}
复制代码
Main
// main 函数
class Main {
constructor() {
this.alterLine('Set Comparison Area');
let myLinkedListSet = new MyLinkedListSet();
let myBinarySearchTreeSet = new MyBinarySearchTreeSet();
let performanceTest = new PerformanceTest();
let myLinkedListSetInfo = performanceTest.testSet(
myLinkedListSet,
5000
);
let myBinarySearchTreeSetInfo = performanceTest.testSet(
myBinarySearchTreeSet,
5000
);
this.alterLine('MyLinkedListSet Area');
console.log(myLinkedListSetInfo);
this.show(myLinkedListSetInfo);
this.alterLine('MyBinarySearchTreeSet Area');
console.log(myBinarySearchTreeSetInfo);
this.show(myBinarySearchTreeSetInfo);
}
// 将内容显示在页面上
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(logn)
这种级别,804.惟一摩尔斯密码词
网址:https://leetcode-cn.com/problems/unique-morse-code-words/
解答
// 答题
class Solution {
// leetcode 804. 惟一摩尔斯密码词
uniqueMorseRepresentations(words) {
/** * @param {string[]} words * @return {number} * 使用本身的二分搜索树来实现 */
var uniqueMorseRepresentations = function(words) {
// 摩斯码
const codes = [
'.-',
'-...',
'-.-.',
'-..',
'.',
'..-.',
'--.',
'....',
'..',
'.---',
'-.-',
'.-..',
'--',
'-.',
'---',
'.--.',
'--.-',
'.-.',
'...',
'-',
'..-',
'...-',
'.--',
'-..-',
'-.--',
'--..'
];
const myBinarySearchTreeSet = new MyBinarySearchTreeSet();
let content = '';
// 获取起始字符的aceii码,
// 从而能够求出某个单词的每个字符在字母表中占的位置索引,
// 根据这些位置索引就能够在摩斯表中找到相应的摩斯码,
// 一个单词就是一组摩斯码,而后使用set添加,就能够直接实现去重的操做了
const start = 'a'.charCodeAt(0);
for (const word of words) {
for (const w of word) content += codes[w.charCodeAt(0) - start];
myBinarySearchTreeSet.add(content);
content = '';
}
return myBinarySearchTreeSet.getSize();
};
/** * @param {string[]} words * @return {number} * 使用系统内置的Set集合类 */
var uniqueMorseRepresentations = function(words) {
// 摩斯码
const codes = [
'.-',
'-...',
'-.-.',
'-..',
'.',
'..-.',
'--.',
'....',
'..',
'.---',
'-.-',
'.-..',
'--',
'-.',
'---',
'.--.',
'--.-',
'.-.',
'...',
'-',
'..-',
'...-',
'.--',
'-..-',
'-.--',
'--..'
];
const set = new Set();
let content = '';
// 获取起始字符的aceii码,
// 从而能够求出某个单词的每个字符在字母表中占的位置索引,
// 根据这些位置索引就能够在摩斯表中找到相应的摩斯码,
// 一个单词就是一组摩斯码,而后使用set添加,就能够直接实现去重的操做了
const start = 'a'.charCodeAt(0);
for (const word of words) {
for (const w of word) content += codes[w.charCodeAt(0) - start];
set.add(content);
content = '';
}
return set.size;
};
return uniqueMorseRepresentations(words);
}
}
// main 函数
class Main {
constructor() {
this.alterLine('leetcode 804.惟一摩尔斯密码词');
let s = new Solution();
let words = ['gin', 'zen', 'gig', 'msg'];
this.show(s.uniqueMorseRepresentations(words));
}
// 将内容显示在页面上
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();
};
复制代码
高中数学里的函数就能够理解成是一种映射
f(x)=2*x+1
,在映域中每取出一个值,映射关系你也能够把它称之为字典
生活中的映射的应用
dict:key ----> value
字典:单词 ----> 释意
名册:身份证号 ----> 人
车辆管理:车牌号 ----> 车
数据库:id ----> 信息
词频统计:单词 ----> 频率
存储(键,值)数据对的数据结构(key,value)
能够很是容易的使用链表或者二分搜索树来实现映射。
// 链表实现时的Node
class Node {
key; // Key
value; //Value
Node next;// Node
}
// 二分搜索树实现时的Node
class Node {
key; // Key
value; //Value
left;// Node
right;// Node
}
复制代码
MyMap
void add(k, v)
V remove(k)
boolean contains(k)
V get(k)
void set(k, v)
int getSize()
boolean isEmpty()
(class: MyLinkedListMap)
MyLinkedListMap
// 自定义链表映射节点 LinkedListMapNode
class MyLinkedListMapNode {
constructor(key = null, value = null, next = null) {
this.key = key;
this.value = value;
this.next = next;
}
// @Override toString 2018-11-5-jwl
toString() {
return this.key.toString() + '---------->' + this.value.toString();
}
}
// 自定义链表映射 Map
class MyLinkedListMap {
constructor() {
this.dummyHead = new MyLinkedListMapNode();
this.size = 0;
}
// 根据key获取节点 -
getNode(key) {
let cur = this.dummyHead.next;
while (cur !== null) {
if (cur.key === key) return cur;
cur = cur.next;
}
return null;
}
// 添加操做 +
add(key, value) {
let node = this.getNode(key);
// 这个节点若是存在就 覆盖值便可
if (node !== null) node.value = value;
else {
// 若是不存在,那么就在头部添加如下
let newNode = new MyLinkedListMapNode(key, value);
newNode.next = this.dummyHead.next;
this.dummyHead.next = newNode;
this.size++;
}
}
// 删除操做 返回被删除的元素 +
remove(key) {
let prev = this.dummyHead;
// 循环查找
while (prev.next !== null) {
if (prev.next.key === key) break;
prev = prev.next;
}
// 若是触碰了break, 那就知足条件
if (prev.next !== null) {
let delNode = prev.next;
prev.next = delNode.next;
let value = delNode.value;
devNode = delNode.next = null;
this.size--;
return value;
}
// 若是没有触屏break 那就返回空值回去
return null;
}
// 查询操做 返回查询到的元素 +
get(key) {
let node = this.getNode(key);
if (node === null) return null;
return node.value;
}
// 修改操做 +
set(key, value) {
let node = this.getNode(key);
if (node === null) throw new Error(key + " doesn't exist.");
node.value = value;
}
// 返回是否包含该key的元素的判断值 +
contains(key) {
return this.getNode(key) !== null;
}
// 返回映射中实际的元素个数 +
getSize() {
return this.size;
}
// 返回映射中是否为空的判断值 +
isEmpty() {
return this.size === 0;
}
// @Override toString() 2018-11-05-jwl
toString() {
let mapInfo = `MyLinkedListMap: size = ${this.size}, data = [ `;
document.body.innerHTML += `MyLinkedListMap: size = ${ this.size }, data = [ <br/><br/>`;
let cur = this.dummyHead.next;
for (var i = 0; i < this.size - 1; i++) {
mapInfo += ` ${cur.toString()}, \r\n`;
document.body.innerHTML += ` ${cur.toString()}, <br/><br/>`;
cur = cur.next;
}
if (cur !== null) {
mapInfo += ` ${cur.toString()} \r\n`;
document.body.innerHTML += ` ${cur.toString()} <br/><br/>`;
}
mapInfo += ` ] \r\n`;
document.body.innerHTML += ` ] <br/><br/>`;
return mapInfo;
}
}
复制代码
(class: MyBSTMap)
MyBSTMap
// 自定义二分搜索树树映射节点 TreeMapNode
class MyBinarySearchTreeMapNode {
constructor(key = null, value = null, left = null, right = null) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
// @Override toString 2018-11-5-jwl
toString() {
return this.key.toString() + '---------->' + this.value.toString();
}
}
// 自定义二分搜索树映射 Map
class MyBinarySearchTreeMap {
constructor() {
this.root = null;
this.size = 0;
}
// 比较的功能
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);
}
// 添加操做 递归算法 -
recursiveAdd(node, key, value) {
// 解决最简单的问题
if (node === null) {
this.size++;
return new MyBinarySearchTreeMapNode(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;
}
}
复制代码
O(n)
O(n)
O(1)
,O(n)
。O(n)
O(n)
O(n)
O(n)
O(n)
O(n)
O(h) or O(log n)
O(h) or O(log n)
高度
,O(h)
这个级别的,O(h) or O(log n)
O(h) or O(log n)
O(h) or O(log n)
class: MyLinkedListMap, class: MyBSTMap , class: PerformanceTest, class: Main)
MyLinkedListMap
// 自定义链表映射节点 LinkedListMapNode
class MyLinkedListMapNode {
constructor(key = null, value = null, next = null) {
this.key = key;
this.value = value;
this.next = next;
}
// @Override toString 2018-11-5-jwl
toString() {
return this.key.toString() + '---------->' + this.value.toString();
}
}
// 自定义链表映射 Map
class MyLinkedListMap {
constructor() {
this.dummyHead = new MyLinkedListMapNode();
this.size = 0;
}
// 根据key获取节点 -
getNode(key) {
let cur = this.dummyHead.next;
while (cur !== null) {
if (cur.key === key) return cur;
cur = cur.next;
}
return null;
}
// 添加操做 +
add(key, value) {
let node = this.getNode(key);
// 这个节点若是存在就 覆盖值便可
if (node !== null) node.value = value;
else {
// 若是不存在,那么就在头部添加如下
let newNode = new MyLinkedListMapNode(key, value);
newNode.next = this.dummyHead.next;
this.dummyHead.next = newNode;
this.size++;
}
}
// 删除操做 返回被删除的元素 +
remove(key) {
let prev = this.dummyHead;
// 循环查找
while (prev.next !== null) {
if (prev.next.key === key) break;
prev = prev.next;
}
// 若是触碰了break, 那就知足条件
if (prev.next !== null) {
let delNode = prev.next;
prev.next = delNode.next;
let value = delNode.value;
delNode = delNode.next = null;
this.size--;
return value;
}
// 若是没有触屏break 那就返回空值回去
return null;
}
// 查询操做 返回查询到的元素 +
get(key) {
let node = this.getNode(key);
if (node === null) return null;
return node.value;
}
// 修改操做 +
set(key, value) {
let node = this.getNode(key);
if (node === null) throw new Error(key + " doesn't exist.");
node.value = value;
}
// 返回是否包含该key的元素的判断值 +
contains(key) {
return this.getNode(key) !== null;
}
// 返回映射中实际的元素个数 +
getSize() {
return this.size;
}
// 返回映射中是否为空的判断值 +
isEmpty() {
return this.size === 0;
}
// @Override toString() 2018-11-05-jwl
toString() {
let mapInfo = `MyLinkedListMap: size = ${this.size}, data = [ `;
document.body.innerHTML += `MyLinkedListMap: size = ${ this.size }, data = [ <br/><br/>`;
let cur = this.dummyHead.next;
for (var i = 0; i < this.size - 1; i++) {
mapInfo += ` ${cur.toString()}, \r\n`;
document.body.innerHTML += ` ${cur.toString()}, <br/><br/>`;
cur = cur.next;
}
if (cur !== null) {
mapInfo += ` ${cur.toString()} \r\n`;
document.body.innerHTML += ` ${cur.toString()} <br/><br/>`;
}
mapInfo += ` ] \r\n`;
document.body.innerHTML += ` ] <br/><br/>`;
return mapInfo;
}
}
复制代码
MyBSTMap
// 自定义二分搜索树树映射节点 TreeMapNode
class MyBinarySearchTreeMapNode {
constructor(key = null, value = null, left = null, right = null) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
// @Override toString 2018-11-5-jwl
toString() {
return this.key.toString() + '---------->' + this.value.toString();
}
}
// 自定义二分搜索树映射 Map
class MyBinarySearchTreeMap {
constructor() {
this.root = null;
this.size = 0;
}
// 比较的功能
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);
}
// 添加操做 递归算法 -
recursiveAdd(node, key, value) {
// 解决最简单的问题
if (node === null) {
this.size++;
return new MyBinarySearchTreeMapNode(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;
}
}
复制代码
PerformanceTest
// 性能测试
class PerformanceTest {
constructor() {}
// 对比都列
testQueue(queue, openCount) {
let startTime = Date.now();
let random = Math.random;
for (var i = 0; i < openCount; i++) {
queue.enqueue(random() * openCount);
}
while (!queue.isEmpty()) {
queue.dequeue();
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 对比栈
testStack(stack, openCount) {
let startTime = Date.now();
let random = Math.random;
for (var i = 0; i < openCount; i++) {
stack.push(random() * openCount);
}
while (!stack.isEmpty()) {
stack.pop();
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 对比集合
testSet(set, openCount) {
let startTime = Date.now();
let random = Math.random;
let arr = [];
let temp = null;
// 第一遍测试
for (var i = 0; i < openCount; i++) {
temp = random();
// 添加剧复元素,从而测试集合去重的能力
set.add(temp * openCount);
set.add(temp * openCount);
arr.push(temp * openCount);
}
for (var i = 0; i < openCount; i++) {
set.remove(arr[i]);
}
// 第二遍测试
for (var i = 0; i < openCount; i++) {
set.add(arr[i]);
set.add(arr[i]);
}
while (!set.isEmpty()) {
set.remove(arr[set.getSize() - 1]);
}
let endTime = Date.now();
// 求出两次测试的平均时间
let avgTime = Math.ceil((endTime - startTime) / 2);
return this.calcTime(avgTime);
}
// 对比映射
testMap(map, openCount) {
let startTime = Date.now();
let array = new MyArray();
let random = Math.random;
let temp = null;
let result = null;
for (var i = 0; i < openCount; i++) {
temp = random();
result = openCount * temp;
array.add(result);
array.add(result);
array.add(result);
array.add(result);
}
for (var i = 0; i < array.getSize(); i++) {
result = array.get(i);
if (map.contains(result)) map.add(result, map.get(result) + 1);
else map.add(result, 1);
}
for (var i = 0; i < array.getSize(); i++) {
result = array.get(i);
map.remove(result);
}
let endTime = Date.now();
return this.calcTime(endTime - startTime);
}
// 计算运行的时间,转换为 天-小时-分钟-秒-毫秒
calcTime(result) {
//获取距离的天数
var day = Math.floor(result / (24 * 60 * 60 * 1000));
//获取距离的小时数
var hours = Math.floor((result / (60 * 60 * 1000)) % 24);
//获取距离的分钟数
var minutes = Math.floor((result / (60 * 1000)) % 60);
//获取距离的秒数
var seconds = Math.floor((result / 1000) % 60);
//获取距离的毫秒数
var milliSeconds = Math.floor(result % 1000);
// 计算时间
day = day < 10 ? '0' + day : day;
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
milliSeconds =
milliSeconds < 100
? milliSeconds < 10
? '00' + milliSeconds
: '0' + milliSeconds
: milliSeconds;
// 输出耗时字符串
result =
day +
'天' +
hours +
'小时' +
minutes +
'分' +
seconds +
'秒' +
milliSeconds +
'毫秒' +
' <<<<============>>>> 总毫秒数:' +
result;
return result;
}
}
复制代码
Main
// main 函数
class Main {
constructor() {
this.alterLine('Map Comparison Area');
let myLinkedListMap = new MyLinkedListMap();
let myBinarySearchTreeMap = new MyBinarySearchTreeMap();
let systemMap = new Map();
let performanceTest = new PerformanceTest();
systemMap.remove = systemMap.delete;
systemMap.contains = systemMap.has;
systemMap.add = systemMap.set;
systemMap.isEmpty = () => systemMap.size === 0;
systemMap.getSize = () => systemMap.size;
let myLinkedListMapInfo = performanceTest.testMap(
myLinkedListMap,
50000
);
let myBinarySearchTreeMapInfo = performanceTest.testMap(
myBinarySearchTreeMap,
50000
);
let systemMapInfo = performanceTest.testMap(systemMap, 50000);
this.alterLine('MyLinkedListMap Area');
console.log(myLinkedListMapInfo);
this.show(myLinkedListMapInfo);
this.alterLine('MyBinarySearchTreeMap Area');
console.log(myBinarySearchTreeMapInfo);
this.show(myBinarySearchTreeMapInfo);
this.alterLine('SystemMap Area');
console.log(systemMapInfo);
this.show(systemMapInfo);
}
// 将内容显示在页面上
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();
};
复制代码
MySet
void add (e)
: 不能添加剧复元素void remove (e)
boolean conatains (e)
int getSize ()
boolean isEmpty ()
MyMap
void add(k, v)
V remove(k)
boolean contains(k)
V get(k)
void set(k, v)
int getSize()
boolean isEmpty()
349.两个数组的交集
https://leetcode-cn.com/problems/intersection-of-two-arrays/
350.两个数组的交集 II
https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/
https://leetcode-cn.com/tag/hash-table/
(class: Solution, class: Solution)
两道题目
349.两个数组的交集
350.两个数组的交集 II
// 答题
class Solution {
// leetcode 349. 两个数组的交集
intersection(nums1, nums2) {
/** * @param {number[]} nums1 * @param {number[]} nums2 * @return {number[]} */
var intersection = function(nums1, nums2) {
let set = new Set();
let arr = [];
for (const num of nums1) set.add(num);
for (const num of nums2) {
if (set.has(num)) {
arr.push(num);
set.delete(num);
}
}
return arr;
};
return intersection(nums1, nums2);
}
// leetcode 350.两个数组的交集 II
intersect(nums1, nums2) {
/** * @param {number[]} nums1 * @param {number[]} nums2 * @return {number[]} */
var intersect = function(nums1, nums2) {
let map = new Map();
let arr = [];
for (const num of nums1) {
if (map.has(num)) map.set(num, map.get(num) + 1);
else map.set(num, 1);
}
for (const num of nums2) {
if (map.has(num)) {
arr.push(num);
let result = map.get(num) - 1;
map.set(num, result);
if (result === 0) map.delete(num);
}
}
return arr;
};
return intersect(nums1, nums2);
}
}
// main 函数
class Main {
constructor() {
this.alterLine('leetcode 349. 两个数组的交集');
let s = new Solution();
var nums1 = [1, 2, 2, 1],
nums2 = [2, 2];
var nums3 = [4, 9, 5],
nums4 = [9, 4, 9, 8, 4];
console.log('[' + s.intersection(nums1, nums2) + ']');
console.log('[' + s.intersection(nums3, nums4) + ']');
this.show('[' + s.intersection(nums1, nums2) + ']');
this.show('[' + s.intersection(nums3, nums4) + ']');
this.alterLine('leetcode 350. 两个数组的交集 II');
console.log('[' + s.intersect(nums1, nums2) + ']');
console.log('[' + s.intersect(nums3, nums4) + ']');
this.show('[' + s.intersect(nums1, nums2) + ']');
this.show('[' + s.intersect(nums3, nums4) + ']');
}
// 将内容显示在页面上
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();
};
复制代码