学习JavaScript数据结构与算法 2015.10.pdf
《学习JavaScript数据结构与算法第3版》高清中文PDF.pdf
《Learning JavaScript Data Structures and Algorithms3rd》英文PDF.pdf
源码.zip: https://github.com/loiane/javascript-datastructures-algorithms
环境
launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Chrome",
"url": "http://localhost:8092/",
"webRoot": "${workspaceRoot}"
},
{
"type": "node",
"request": "launch",
"name": "Mocha JS",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--compilers",
"js:babel-core/register",
"--colors",
"${workspaceRoot}/test/js/**/*.spec.js"
],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "Mocha TS",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"-r",
"ts-node/register",
"--colors",
"${workspaceRoot}/test/ts/**/**/*.spec.ts"
],
"protocol": "auto",
"internalConsoleOptions": "openOnSessionStart"
}
]
}
settings.json
{
"typescript.tsdk": "node_modules/typescript/lib"
}
tasks.json
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "go",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "dev",
"group": {
"kind": "test",
"isDefault": true
}
}
]
}
检查在各个浏览器中哪些 JS 特性可用
ES2015(ES6):http://kangax.github.io/compat-table/es6/
ES2016+:http://kangax.github.io/compat-table/es2016plus/
Chrome 开发开启支持ESnext:chrome://flags/#enable-javascript-harmony
ESM:http://caniuse.com/#feat=es6-module
<script type="module" src="17-ES2015-ES6-Modules.js"></script> // 在浏览器中使用 import 关键字
<script nomodule src="./lib/17-ES2015-ES6-Modules-bundle.js"></script> // 保证不支持ESM的浏览器向后兼容
TS
文档:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html
在线编辑器:https://www.typescriptlang.org/play/index.html
js 文件对代码进行错误及类型检测
// @ts-check
数组
delete numbers[0] 等同于 numbers[0] = undefined
类型数组则用于存储单一类型的数据
为了保证元素排列有序,会占用更多的内存空间
栈
后进先出(LIFO),新元素都靠近栈顶,旧元素都接近栈底
栈也被用在编程语言的编译器和内存中保存变量、方法调用等,也被用于浏览器历史记录 (浏览器的返回按钮)
- 用数组来保存栈里的元素
- 对元素的插入和删除功能进行限制,使其遵循LIFO
- 实现以下方法
- push(element(s)): 添加一个(或几个)新元素到栈顶
- push(element(s)): 添加一个(或几个)新元素到栈顶
- pop(): 移除栈顶的元素,同时返回被移除的元素
- peek(): 返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它)
- isEmpty(): 如果栈里没有任何元素就返回 true,否则返回 false
- clear(): 移除栈里的所有元素
size(): 返回栈里的元素个数。该方法和数组的 length 属性很类似。
<br /> <br />
class Stack {
constructor() {
this.items = []
}
push = element => (
this.items.push(element)
)
pop = element => (
this.items.pop(element)
)
peek = element => (
this.items[this.items.length - 1]
)
isEmpty = () => (
this.items.length === 0
)
size = () => (
this.items.length
)
clear = () => {
this.items = []
}
toString = () => (
this.items.toString()
)
}
const stack = new Stack();
stack.push(5)
stack.push(8)
console.log(stack.peek())
stack.push(12)
console.log(stack.size())
console.log(stack.isEmpty())
stack.pop()
console.log(stack.size())
以对象形式实现:除了 toString 方法,我们创建的其他方法的复杂度均为 O(1),代表我们可以 直接找到目标元素并对其进行操作(push、pop 或 peek)。
class Stack {
constructor() {
this.count = 0;
this.items = {};
}
push= element => {
this.items[this.count] = element;
this.count++;
return element;
}
size = () => this.count
isEmpty = () => this.count === 0
pop = () => {
if(this.isEmpty()) return undefined;
this.count--;
const result = this.items[this.count]
delete this.items[this.count]
return result;
}
peek = () => {
if(this.isEmpty()) return undefined;
return this.items[this.count - 1];
}
clear = () => {
this.items = {};
this.count = 0;
}
toString = () => {
if(this.isEmpty()) return '';
let objString = this.items[0];
for(let i = 1; i < this.count; i ++) {
objString = `${objString}, ${this.items[i]}`;
}
return objString;
}
}
ES2015 类是基于原型的。尽管基于原型 的类能节省内存空间并在扩展方面优于基于函数的类,但这种方式不能声明私有属性(变量)或 方法。
JavaScript 实现私有属性
- 在属性名称之前加上一个下划线(_)
- 限定作用域 Symbol 实现类: Symbol是不可变的,可用作对象的属性
- WeakMap 实现类
- 添加#作为前缀来声明私有属性的类属性提案
Symbol 实现类
const items = Symbol('stackItems')
class Stack {
constructor() {
this[_items] = []
}
// 栈的方法
push = element => (
this[_items].push(element)
)
}
<br /> <br />
const stack = new Stack()
stack.push(0)
stack.push(1)
let objectSymbols = Object.getOwnPropertySymbols(stack)
console.log(objectSymbols.length)
console.log(objectSymbols[0])
stack[objectSymbols[0]].push(1)
stack.print()
WeakMap 实现类
const items = new WeakMap(); // WeakMap 可以存储键值对,其中键是对象
class Stack {
constructor() {
_items.set(this, [])
}
push = element => {
const s = items.get(this)
return s.push(element)
}
pop = () => {
const s = items.get(this)
return s.pop()
}
}
类属性提案
class Stack {
#count = 0
#items = 0
}
十进制转二进制
function decimaToBinary(decNumber) {
const remStack = new Stack()
let number = decNumber
let rem;
let binaryString = ''
while(number > 0) {
rem = Math.floor(number % 2)
remStack.push(rem)
number = Math.floor(number / 2)
}
while(number === 0) {
binaryString += remStack.pop().toString
}
return binaryString
}
console.log(decimaToBinary(233))
console.log(decimaToBinary(19))
console.log(decimaToBinary(1000))
进制转换
十进制转二进制:余数是0或1
十进制转八进制:余数是0-7
十进制转十六进制:余数是0-9,A-F
十进制转三十六进制:余数是0-9,A-Z
function decimaToBinary(decNumber, base) {
const remStack = new Stack()
const digits = ‘0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ’;
let number = decNumber
let rem;
let binaryString = ''
if (!(base>2 || base < 36)) return '';
while(number > 0) {
rem = Math.floor(number % base)
remStack.push(rem)
number = Math.floor(number / 2)
}
while(number === 0) {
binaryString += digits[remStack.pop()].toString
}
return binaryString
}
队列
先进先出
class Queue {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
enqueue = element => {
this.items[this.count] = element;
this.count++;
return element;
}
dequeue = () => {
if (!this.items[this.lowestCount])
return;
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
this.lowestCount++;
return result;
}
peek = () => {
if (this.isEmpty)
return void 0;
return this.items[this.lowestCount];
}
isEmpty = () => this.count === this.lowestCount
size = () => this.count - this.lowestCount
clear = () => {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
toString = () => {
if (this.isEmpty())
return '';
let objString = `${this.items[this.lowestCount]}`;
for (let i = this.lowestCount + 1; i < this.count; i++) {
objString = `${objString},${this.items[i]}`;
}
return objString;
}
}
const queue = new Queue()
console.log(queue.isEmpty())
queue.enqueue('John')
queue.enqueue('Jack')
console.log(queue.toString())
queue.enqueue('Camila')
console.log(queue.toString())
console.log(queue.size())
console.log(queue.isEmpty())
queue.dequeue()
queue.dequeue()
console.log(queue.toString())
双端队列
是一种允许从前端和后端添加和移除元素的特殊队列
class Deque {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
addFront = element => {
if(this.isEmpty()){
this.addBack(element)
} else (this.lowestCount > 0) {
this.lowestCount--;
this.items[this.lowestCount] = element
} else {
// this.lowestCount === 0
for(let i=count;i>0;i--) {
this.items[i]= this.items[i-1]
}
this.lowestCount = 0;
this.items[0] = element;
this.count++;
}
}
addBack = element => {
this.items[this.count] = element;
this.count++;
return element;
}
removeFront = () => {
if (!this.items[this.lowestCount])
return;
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
this.lowestCount++;
return result;
}
removeBack = () => {
}
peekFront = () => {
if (this.isEmpty) return void 0;
return this.items[this.lowestCount];
}
peekBack = () => {
if (this.isEmpty) return void 0;
return this.items[this.count - 1];
}
isEmpty = () => this.count === this.lowestCount
size = () => this.count - this.lowestCount
clear = () => {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
toString = () => {
if (this.isEmpty())
return '';
let objString = `${this.items[this.lowestCount]}`;
for (let i = this.lowestCount + 1; i < this.count; i++) {
objString = `${objString},${this.items[i]}`;
}
return objString;
}
}
击鼓传花
function hotPotato(list, num){
const queue = new Queue();
const elimitatedList = [];
for (let i = 0; i < list.length; i++) {
queue.enqueue(list[i])
}
while(queue.size > 1){
for (let i = 0; i < num; i++) {
queue.enqueue(queue.dequeue());
}
elimitatedList(queue.dequeue());
}
return {
elimitated: elimitatedList,
winner: queue.dequeue()
}
}
回文检查器
回文是正反都能读通的单词、词组。
方法一:将字符串反向排列并检查它和原字符串是否相同。
方法二:双端队列的两端字符相同
function palindromeChecker(str){
if (!str || str.length === 0) return false;
const deque = new Deque();
const lowerStr = str.toLocaleLowerCase().split(' ').join('');// 去掉所有的空格
let isEqual = true;
let firstChar;
let lastChar;
for(let i = 0; i<lowerStr.length; i++){
deque.addBack(lowerStr.charAt(i))
}
while(deque.size() > 1 && isEqual) {
firstChar=deque.removeFront();
lastChar=deque.removeBack();
isEqual= firstChar === lastChar
}
return isEqual;
}
链表
动态数据结构,可以随意添加或移除项,它会按需扩容。有序的元素集合,不同于数组,链表中的元素在内存中并不是连续放置的,每个元素由一个存储元素本身的节点和一个指向下一个元素的引用组成。
class Node {
constructor(element){
this.element = element;
this.next = undefined;
}
}
function defaultEquals(a, b) {
return a === b;
}
class LinkedList {
constructor(equalsFn = defaultEquals){
this.count = 0;
this.head = undefined;
this.equalsFn = equalsFn
}
push = element => {
const node = new Node(element);
let current;
if (this.head == null) {
// 向链表添加第一个元素
this.head = node;
} else {
// 向链表追加一个元素
current = this.head;
// 链表只有第一个元素的引用,因此需要循环访问链表,直到找到最后一项
while (current.next !== null) { // 链表的最后一个节点的下一个元素始终是 undefined 或 null
current = current.next; // 向前移动指针
}
// 将其next赋为新元素,建立链接
current = current.next;
}
this.count++;
}
insert = (element, position) => {}
// 迭代列表直到到达目标索引
getElementAt = index => {
}
// 根据元素的值移除元素
remove = element => {}
indexOf = element => {}
// 从特定位置移除元素
removeAt = index => {
// 检查越界值
if(index >= 0 && index < this.count) {
let current = this.head;
if () {
// 移除第一项
this.head = current.next;
} else {
// 移除其它项
let previous;
// 迭代链表的节点,直到到达目标位置
for (let i = 0; i < index; i++) {
previous = current;
current = current.next;
}
//将previous 与 current 的下一项链接起来,跳过 current,从而移除它
previous.next = current.next;
}
this.count--;
return current.element;
} else {
return undefined;
}
}
isEmpty = () => {}
size = () => {}
toString = () => {}
}
树
二叉搜索树(BinarySearchTree)
只允许你在左侧节点存储比父节点小的值,在右侧节点存储比父节点大的值。
export class Node {
constructor(key) {
this.key = key // 键是树对节点的称呼
this.left = null
this.right = null
}
}
class BST {
constructor () {
this.root = null
}
insert (key) {
if (this.root === null) {
this.root = new Node(key)
} else {
this.insertNode(this.root, key)
}
}
insertNode (node, key) {
if (node.key > key) {
if (node.left == null) {
node.left = new Node(key)
} else {
this.insertNode(node.left, key)
}
} else {
if (node.right == null) {
node.right = new Node(key)
} else {
this.insertNode(node.right, key)
}
}
}
inOrder (cb) {
this.inOrderNode(this.root, cb)
}
inOrderNode (node, cb) {
if (node !== null) {
this.inOrderNode(node.left, cb)
cb(node.key)
this.inOrderNode(node.right, cb)
}
}
search (key) {
return this.searchNode(this.root, key)
}
min () {
return this.minNode(this.root)
}
minNode (node) {
if (node.left === null) {
return node.key
}
while (node.left !== null) {
return this.minNode(node.left)
}
}
searchNode (node, key) {
if (node === null) {
return false
}
if (node.key > key) {
return this.searchNode(node.left, key)
} else if (node.key < key) {
return this.searchNode(node.right, key)
} else {
return true
}
}
}
const tree = new BST()
tree.insert(6)
tree.insert(7)
tree.insert(2)
tree.insert(16)
tree.insert(1)
console.log(tree, tree.inOrder((key) => console.log(key)), tree.min())
中序遍历:以上行顺序访问BST所有节点的遍历方式,也就是