1. 栈 —— 后进先出
-
JavaScript 实现栈的方式:
// 数组
class Stack {
#items
constructor() {
this.#items = []
}
push(element) { // 添加元素
this.#items.push(element)
}
pop() { // 删除元素,并返回被删除的元素
return this.#items.pop()
}
peek() { // 返回栈顶元素
return this.#items[this.#items.length - 1]
}
isEmpty() {
return this.#items.length === 0
}
}
// 对象
class Stack {
constructor() {
this.count = 0;
this.items = {};
}
// 方法
push(element) {
this.items[this.count] = element;
this.count++;
}
pop() {
if (this.isEmpty()) {
return undefined;
}
this.count--;
const result = this.items[this.count];
delete this.items[this.count];
return result;
}
peak() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.count - 1];
}
size() {
return this.count;
}
isEmpty() {
return this.count === 0;
}
clear() {
this.items = {};
this.count = 0;
// 或者利用pop()移除所有元素
// while (!this.isEmpty()) {
// this.pop()
// }
}
toString() {
if (this.isEmpty()) {
return '';
}
let objString = `${this.items[0]}`;
for (let i = 1; i < this.count; i++) {
objString = `${objString}, ${this.items[i]}`
// console.log(objString, i);
}
return objString;
}
}
-
应用:
// 十进制转二进制
function decimalToBinary(decNumber) {
const remStack = new Stack()
let num = decNumber;
while (num > 0) {
remStack.push(num % 2)
num = parseInt(num / 2)
}
console.log(remStack.toString()); // 0,1,0,1
let res = ''
while (!remStack.isEmpty()) {
res += remStack.pop()
}
return Number(res)
}
console.log(decimalToBinary(10)) // 1010
console.log(decimalToBinary(233)) // 11101001
console.log(decimalToBinary(1000)) // 1111101000
// 十进制转为任意进制
function baseConverter(decNumber, base) {
const remStack = new Stack()
const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let num = decNumber
let res = ''
if (!(base >= 2 && base <= 36)) {
return '';
}
while (num > 0) {
remStack.push(num % base)
num = parseInt(num / base)
}
while (!remStack.isEmpty()) {
res += digits[remStack.pop()]
}
return res;
}
console.log(baseConverter(100345, 2)); // 11000011111111001
console.log(baseConverter(100345, 8)); // 303771
console.log(baseConverter(100345, 16)); // 187F9
console.log(baseConverter(100345, 35)); // 2BW0
2. 队列 —— 先进先出
-
JavaScript 封装队列
class Queue {
#items
#count
#frontiest
constructor() {
this.#items = {}
this.#count = 0
this.#frontiest = 0
}
enqueue(element) {
// 添加队列
this.#items[this.#count] = element
this.#count++
}
dequeue() {
// 出队
if (this.isEmpty()) {
return undefined
}
const res = this.#items[this.#frontiest]
delete this.#items[this.#frontiest]
this.#frontiest++
return res;
}
peek() {
return this.#items[this.#frontiest]
}
isEmpty() {
return this.#count - this.#frontiest === 0
}
size() {
return this.#count - this.#frontiest
}
toString() {
if (this.isEmpty()) {
return ''
}
let objString = this.#items[this.#frontiest]
for (let i = this.#frontiest + 1; i < this.#count; i++) {
objString += `, ${this.#items[i]}`
}
return objString
}
}
3. 链表 —— 用next 指针连在一起
-
JavaScript 中实现链表
const a = {val: 'a'}
const b = {val: 'b'}
const c = {val: 'c'}
const d = {val: 'd'}
a.next = b;
b.next = c;
c.next = d;
d.next = null;
-
遍历链表
// 创建一个指针
let p = a;
while (p) {
console.log(p.val)
p = p.next; // 移动指针
}
-
链表插值
const e = {val: 'e'}
c.next = e;
e.next = d;
-
删除
// 知晓被删除元素前一个元素,将前一个元素的next,指向被删除元素后一个元素
c.next = d;
// 不知晓被删元素的上一个元素,把被删元素的下一个元素值复制给被删的元素,再将指针指向下下一个元素
cur.val = cur.next.val;
cur.next = cur.next.next;
-
js 中的原型链
-
原型链的本质是链表
-
原型链上的节点是各种原型对象,如:Function.prototype、Object.prototype
-
原型链通过 __proto__ 属性连接各种原型对象
-
如果 A 沿着原型链能找到 B.prototype,那么 A instanceof B 为 true
-
如果在 A 对象上没有找到 x 属性,那么会沿着原型链找 x 属性,直到 null
1. obj -> Object.prototype -> null
2. func -> Function.prototype -> Object.prototype -> null
3. arr -> Array.prototype -> Object.prototype -> null
-
面试题:instanceof 的原理,并用代码实现
const instanceOf = (A, B) => { let p = A; while (p) { if (p === B.protopype) { return true } p = p.__proto__; } return false; }
-
4. 集合 —— Set 无序且唯一
-
JS 中用
new Set()
实现 -
常用操作:
-
去重
const arr = [1, 2, 2, 3] const res = [...new Set(arr)]
-
判断存在
const set = new Set([1, 3, 4]) const res = set.has(2) // false
-
求交集
const arr1 = [1, 2, 3, 3] const arr2 = [2, 2, 4, 4] const res = [...new Set(arr1)].filter(n => new Set(arr2).has(n)); const res = [...new Set(arr1)].filter(n => arr2.includes(n))
-
添加
set.add(1)
-
删除
set.delete(5)
-
获取集合大小
set.size()
-
集合转数组
-
[...new Set([1, 2])]
-
Array.from(new Set([1, 2]))
-
-
求差集
[...mySet].filter(v => !mySet2.has(v))
-
求交集
[...myset].filter(v => myset2.has(v))
-
5. 字典 —— Map 以键值对的形式存储
-
JS 中的实现
new Map()
-
基本操作
-
增
m.set('a', 'aa')
-
删
m.delete('a')
-
改
m.set('a', 'hahahaha')
-
查
m.get('a')
-
6. 树 —— 一种分层数据的抽象模型
-
JS 中创建树:Object 或 Array
-
常用操作
-
深度优先遍历——先访问根节点;对根节点的children进行深度优先遍历
const dfs = (root) => { console.log(root.val) if (root.children) { root.children.forEach(dfs) } }
-
广度优先遍历——新建队列,把根节点入队;把队头出队访问;把队头的children挨个入队;重复上述
const bfs = (root) => { const q = [root] while (q.length) { const n = q.shift() console.log(n.val) n.children.forEach(v => { q.push(v) }) } }
-
先中后序遍历
-
先序遍历 —— 根左右
// 递归版 const preorder = (root) => { if (!root) return; console.log(root.val) if (root.left) preorder(root.left) if (root.right) preorder(root.right) } // 非递归版——利用栈的先进后出,先将根节点入栈,再出栈,将左节点和右节点入栈 const preorder = (root) => { if (!root) return const stack = [root] while (stack.length) { const n = stack.pop() console.log(n.val) if (n.right) stack.push(n.right) if (n.left) stack.push(n.left) } }
-
中序遍历 —— 左根右
// 递归版 const inorder = (root) => { if (!root) return; if (root.left) preorder(root.left) console.log(root.val) if (root.right) preorder(root.right) } // 非递归版 const inorder = (root) => { if (!root) return const stack = [] let p = root; while (stack.length || p) { // 把左节点压入栈中 while (p) { stack.push(p) p = p.left } // 将左节点出战访问 const n = stack.pop() console.log(n.val) // 将指针指向右节点,继续中序遍历 p = n.right } }
-
后序遍历 —— 左右根
// 递归版 const postorder = (root) => { if (!root) return; if (root.left) preorder(root.left) if (root.right) preorder(root.right) console.log(root.val) } // 非递归版——先进行先序遍历,压入栈中,再依次出栈 const postorder = (root) => { if (!root) return const stack = [root] const res = [] while (stack.length) { const n = stack.pop() res.push(n.val) if (n.left) stack.push(n.left) if (n.right) stack.push(n.right) } while (res.length) { console.log(res.pop()) } }
-
-
7. 图 —— 网络结构
-
图是网络结构的抽象模型,是一组由边连接的节点
-
图可以表示任何二元关系,如道路、航班等
-
JS 中可以用 Object 和 Array构建图
-
图的表示方法:
-
邻接矩阵
-
邻接表
-
关联矩阵
-
-
图的深度优先遍历
-
访问根节点
-
对根节点的没访问过的相邻节点挨个进行深度优先遍历
const graph = { 0: [1, 2], 1: [2], 2: [0, 3], 3: [3] } const visited = new Set() const dfs = (n) => { console.log(n); visited.add(n) graph[n].forEach(v => { if (!visited.has(v)) { dfs(v) } }) } dfs(2)
-
-
图的广度优先遍历
-
新建队列,把根节点入队
-
把对头出队访问
-
把队头的没访问过的相邻节点入队
-
重复,直到队列为空
const graph = { 0: [1, 2], 1: [2], 2: [0, 3], 3: [3] } const bfs = (n) => { const visited = new Set() visited.add(n) const q = [n] while (q.length) { const node = q.shift() console.log(n) graph[n].forEach(c => { if (!visited.has(c)) { q.push(c); visited.add(c) } }) } }
-
8. 堆 —— 特殊的完全二叉树
堆是完全二叉树,分为大根堆(每一棵子树的最大值都是头节点)、小根堆(每一棵子树的最大值都是头节点)。
重点:
-
堆结构就是用数组实现的完全二叉树结构
-
完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
-
完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
-
优先级队列结构,就是堆结构
-
数组和堆的对应 当前为i
-
左边子节点为
i * 2 + 1
-
右边子节点为
i * 2 + 2
-
父节点为
(i - 1) / 2
-
-
连续给你数字,使这些数字变成大根堆 heapInsert() --- O(logN)
function heap_insert(arr, index) {
let fa = Math.trunc((index - 1) / 2);
while (arr[index] > arr[fa]) {
swap(arr, index, fa);
index = fa;
fa = Math.trunc((index - 1) / 2);
}
}
-
在大根堆中,拿掉最大值,使剩下的依然保持大根堆 heapify() --- O(logN)
function heapify(arr, index, heapSize) {
// 左孩子
let left = index * 2 + 1;
while (left < heapSize) { // 判断是否有孩子,左孩子是否越界
// 两个孩子谁大, 谁把下标给maxVal 得先判断右孩子是否存在
let maxVal = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
// 父亲和较大孩子的值谁大, 谁把下标给maxVal
maxVal = arr[index] > arr[maxVal] ? index : maxVal;
// 父值等于最大值, 直接break
if (maxVal == index) break;
swap(arr, index, maxVal);
index = maxVal;
left = index * 2 + 1;
}
}