-
链表
特点:
查找节点 O(n);插入、删除节点O(1);
应用场景:
- 操作系统内的动态内存分配
- LRU缓存淘汰算法
// 生成链表
var MyLinkedList = function() {
this.head = null
this.tail = null
this.length = 0
};
var ListNode = function(val) {
this.val = val
this.next = null
}
MyLinkedList.prototype.get = function(index) {
if (index >=0 && index < this.length) {
let i = 0
let cur = this.head
while (i < index) {
cur = cur.next
i ++
}
return cur.val
} else {
return -1
}
};
MyLinkedList.prototype.addAtHead = function(val) {
const lastHead = this.head
const node = new ListNode(val)
this.head = node
this.head.next = lastHead
if (!this.tail) {
this.tail = node
this.tail.next = null
}
this.length ++
};
MyLinkedList.prototype.addAtTail = function(val) {
const lastTail = this.tail
const node = new ListNode(val)
this.tail = node
if (lastTail) {
lastTail.next = this.tail
}
if (!this.head) {
this.head = node
this.head.next = null
}
this.length ++
};
MyLinkedList.prototype.addAtIndex = function(index, val) {
if (index === this.length) {
this.addAtTail(val)
} else if (index <= 0) {
this.addAtHead(val)
} else if (index > 0 && index < this.length) {
let i = 0
let prev = this.head
while (i < index - 1) {
prev = prev.next
i ++
}
const node = new ListNode(val)
node.next = prev.next
prev.next = node
this.length ++
}
};
MyLinkedList.prototype.deleteAtIndex = function(index) {
if (index > 0 && index < this.length) {
let i = 0
let prev = null
let cur = this.head
while (i < index) {
prev = cur
cur = cur.next
i ++
}
prev.next = cur.next
if (index === this.length - 1) {
this.tail = prev
}
this.length --
} else if (index === 0) {
this.head = this.head.next
this.length --
}
};
// 打印
const output = function(head) {
let cur = head
while (cur) {
console.log(cur.val)
cur = cur.next || null
}
};
-
队列
应用场景:
- CPU超线程技术 (虚拟四核)
- 线程池 任务队列 (缓冲区)
单调队列
解决区间最值问题
入队
队尾入队,将之前破坏单调性的元素都从队尾移出(维护单调性)
出队
如果对首元素超出区间范围,则将元素从队首出队
特性
队首元素永远是(滑动窗口)区间最值
// m为滑动窗口大小
const monotoneQueue = (arr, m) => {
let queue = []
for (let i = 0; i < arr.length; i++) {
while (queue.length && arr[queue[queue.length - 1]] > arr[i]) {
queue.pop()
}
queue.push(i)
if (i - queue[0] === m) queue.shift()
if (i + 1 < m) continue
console.log(arr[queue[0]])
}
}
-
栈
特点:事件之间的完全包含关系
应用场景:
- 操作系统线程栈
- 表达式求值
单调栈
维护最近(大/小)关系
const monotoneStack = (arr) => {
// 单调递增
let stack = []
// 存放向前能找到最近的小于值
let prev = new Array(arr.length).fill(-1)
// 存放向后能找到最近的小于值
let next = new Array(arr.length).fill(arr.length)
for (let i=0; i<arr.length; i++) {
while (stack.length && arr[stack[stack.length-1]] > arr[i]) {
next[stack[stack.length-1]] = i
stack.pop()
}
if (!stack.length) {
prev[i] = -1
} else {
prev[i] = stack[stack.length-1]
}
stack.push(i)
}
console.log(prev)
console.log(next)
}
// input [6,7,9,0,8,3,4,5,1,2]
// prev [-1,0,1,-1,3,3,5,6,3,8]
// next [3,3,3,10,5,8,8,8,10,10]
-
树
分类
二叉树
- 每个节点度最多为2
- 度为0的节点比度为2的节点多1个
完全二叉树
- 只有在最后一层的右侧有空节点的树
- 编号为 i 的子节点=> 左孩子:2i;右孩子:2i+1
- 可以用连续空间存储(数组)
满二叉树
只有度为0和度为2的节点
完美二叉树
每一层都是满的
应用
节点代表集合,边代表关系,多用于处理查找
| 堆 | 维护集合最值得神兵利器 | |
| 完全二叉树 | 优先队列 | 维护集合最值得神兵利器 |
| 字典树 | 字符串及相关转换问题得神兵利器 | |
| 多叉树/森林 | AC自动机 | 字符串及相关转换问题得神兵利器 |
| 并查集 | 连通性问题的神兵利器 | |
| 二叉排序树 | AVL树 | 语言标准库中重要的数据检索容器的底层实现 |
| 2-3树 | 语言标准库中重要的数据检索容器的底层实现 | |
| 红黑树 | 语言标准库中重要的数据检索容器的底层实现 | |
| B-树/B+树 | 文件系统,数据库 底层重要数据结构 |
递归
- 数据归纳法 -> 结构归纳法
- 赋予递归函数一个明确的意义
- 思考边界条件
- 实现递归过程
// 生成树
function TreeNode(val) {
this.val = val
this.left = this.right = null
}
const creatTree = function(src) {
let root = new TreeNode()
let result = new TreeNode()
result = null
let queue = []
for (let i = 0; i < src.length; i++) {
if (i == 0) {
root = new TreeNode(src[i])
result = root
queue.push(root)
}
if (queue.length) {
root = queue.shift()
} else {
break
}
if (i + 1 < src.length && src[i + 1] !== null) {
root.left = new TreeNode(src[i + 1])
queue.push(root.left)
}
if (i + 2 < src.length && src[i + 2] !== null) {
root.right = new TreeNode(src[i + 2])
queue.push(root.right)
}
i = i + 1
}
return result
}
堆
是一种完全二叉树,处理集合最值
小顶堆
任意三节点中父节点值都小于两个子节点的值
大顶堆
任意三节点中父节点值都大于两个子节点的值
// 实现大顶堆
class MaxHeap {
constructor(data = []) {
this.data = data
// this.comparator = (a,b) => a-b
this.heapify()
}
heapify() {
if (this.size() < 2) return
for(let i=1;i < this.size(); i++) {
this.bubbleUp(i)
}
}
peek() {
if (this.size === 0) return null
return this.data[0]
}
push(value) {
this.data.push(value)
this.bubbleUp(this.size() - 1)
}
pop() {
if (this.size === 0) {
return null
}
const result = this.data[0]
const last = this.data.pop()
if (this.size() !== 0) {
this.data[0] = last
this.bubbelDown(0)
}
return result
}
swap(index1, index2) {
[this.data[index1], this.data[index2]] = [this.data[index2], this.data[index1]]
}
size() {
return this.data.length
}
bubbleUp(index) {
while(index > 0) {
const parentInd = Math.floor((index-1) / 2)
if (this.data[parentInd] < this.data[index]) {
this.swap(index, parentInd)
index = parentInd
} else {
break;
}
}
}
bubbelDown(index) {
const lastIndex = this.size() - 1
while(true) {
const leftIndex = index * 2 + 1;
const rightIndex = index * 2 + 2;
let findIndex = index
if (leftIndex<= lastIndex && this.data[leftIndex] > this.data[findIndex]) {
findIndex = leftIndex
}
if (rightIndex<= lastIndex && this.data[rightIndex] > this.data[findIndex]) {
findIndex = rightIndex
}
if (index !== findIndex) {
this.swap(index, findIndex)
index = findIndex
} else {
break
}
}
}
}
并查集
Quick-Find算法
染色法:记录每个节点的颜色,把连接到一起的点染成同一个颜色
Quick-Union算法
树形结构:记录每个节点的父节点编号,将连接到一起的两个点,其中一个的父节点指向另一个
优化1(平衡性优化)
合并两个集合时,数量多的集合根节点作为新集合的根节点
优化2(路径压缩)✔
当当前节点的父节点不为根节点时,当前节点的父节点指向父节点的父节点(节点上移)
| Algorithm | Constructor | Union | Find |
|---|---|---|---|
| Quick-Find | N | N | 1 |
| Quick-Union | N | Tree height | Tree height |
| Weighted Quick-Union | N | 1gN | 1gN |
| Weighted Quick-Union With Path Compression | N | Near to 1 | Near to 1 |
class UnionSet {
constructor(size) {
this.set = new Array(size)
this.cnt = new Array(size)
for(let i=0; i<size; i++) {
this.set[i] = i
this.cnt[i] = 1
}
}
get(a) {
return this.set[a] = this.set[a] === a ? a : this.get(this.set[a])
}
merge(a, b) {
this.cnt[this.get(b)] += this.cnt[this.get(a)]
this.set[this.get(a)] = this.get(b)
}
}