数据结构
栈
- 后进先出
- Javascript 中可以利用 Array 数组实现栈结构
class Stack {
constructor() {
this.stack = []
}
push(item) {
this.stack.push(item)
}
pop() {
if (this.stack.length) return this.stack.pop()
throw new Error('stack is empty')
}
peek() {
if (this.stack.length) return this.stack[this.stack.length - 1]
throw new Error('stack is empty')
}
isEmpty() {
return this.stack.length === 0
}
size() {
return this.stack.length
}
}
队列
- 先进先出
- Javascript 中可以利用 Array 数组实现队列结构
class Queue {
constructor() {
this.queue = []
}
push(item) {
this.queue.push(item)
}
shift() {
if (this.queue.length) return this.queue.shift()
throw new Error('queue is empty')
}
peek() {
if (this.queue.length) return this.queue[0]
throw new Error('queue is empty')
}
isEmpty() {
return this.queue.length === 0
}
size() {
return this.queue.length
}
}
链表
- 元素存储不连续,用 next 指针连在一起
- Javascript 中可以利用 Object 或 Function 实现单个节点,最终组成链表
链表与数组的对比
- 数组:增删非首尾元素时需要移动元素
- 链表:增删非首尾元素,只需要更改 next 的指向,不需要移动元素
const node1 = { val: 'node1' }
const node2 = { val: 'node2' }
const node3 = { val: 'node3' }
node1.next = node2
node2.next = node3
function ListNode(val) {
this.val = val
this.next = null
}
const node1 = new ListNode(1)
const node2 = new ListNode(2)
node1.next = node2
集合
- 一种无序且唯一的数据结构
- Javascript 中可以利用 ES6 中的 Set 实现集合
const mySet = new Set()
mySet.add(1)
mySet.add('2')
const obj = { a: 1, b: 2 }
mySet.add(obj)
mySet.delete('2')
const flag = mySet.has(obj)
for(let item of mySet) console.log(item)
for(let item of mySet.keys()) console.log(item)
for(let item of mySet.values()) console.log(item)
for(let [key, value] of mySet.entries()) console.log(key, value)
字典
- 一种存储唯一值的数据结构,但是是以键值对的形式来存储
- Javascript 中可以利用 ES6 中的 Map 实现字典
const map = new Map()
map.set('a', 1)
map.set('b', 2)
map.set('b', 22)
map.delete('a')
map.clear()
console.log(map.get('a'))
树
- 一种分层数据的抽象模型
- Javascript 中可以利用 Object 实现树
const tree = {
val: 'a',
children: [
{
val: 'b',
children: [
{
val: 'd',
children: [],
},
{
val: 'e',
children: [],
}
],
},
{
val: 'c',
children: [
{
val: 'f',
children: [],
},
{
val: 'g',
children: [],
}
],
}
],
};
深度优先遍历
const dfs = (root) => {
console.log(root.val)
root.children.forEach(dfs)
}
广度优先遍历
const bfs = (root) => {
const q = [root]
while (q.length > 0) {
const n = q.shift()
console.log(n.val)
n.children.forEach(child => {
q.push(child)
})
}
}
二叉树
const binaryTree = {
val: 1,
left: {
val: 2,
left: {
val: 4,
left: null,
right: null,
},
right: {
val: 5,
left: null,
right: null,
},
},
right: {
val: 3,
left: {
val: 6,
left: null,
right: null,
},
right: {
val: 7,
left: null,
right: null,
},
},
}
先序遍历
- 访问根节点
- 先对根节点的左子树进行先序遍历
- 再对根节点的右子树进行先序遍历
const preorder = (root) => {
if (!root) return
console.log(root.val)
preorder(root.left)
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
inorder(root.left)
console.log(root.val)
inorder(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
postorder(root.left)
postorder(root.right)
console.log(root.val)
}
const postorder = (root) => {
if (!root) return
const outputStack = []
const stack = [root]
while (stack.length) {
const n = stack.pop()
outputStack.push(n)
if (n.left) stack.push(n.left)
if (n.right) stack.push(n.right)
}
while(outputStack.length){
const n = outputStack.pop()
console.log(n.val)
}
}
图
- 图是网络结构的抽象模型,是一组由边连接的节点
- 图的表示法:邻接矩阵、邻接表、关联矩阵
const graph = {
0: [1, 2],
1: [2],
2: [0, 3],
3: [3]
}
module.exports = graph
深度优先遍历
- 先访问根节点
- 对根节点的没访问过的相邻节点逐个进行深度优先遍历
const graph = require('./graph')
const visited = new Set()
const dfs = (n) => {
console.log(n)
visited.add(n)
graph[n].forEach(c => {
if(!visited.has(c)){
dfs(c)
}
})
}
广度优先遍历
- 新建一个队列,把根节点入队
- 把队头出队并访问
- 把队头的没访问过的相邻节点入队
- 重复第二、三步,直到队列为空
const graph = require('./graph')
const visited = new Set()
const q = [2]
visited.add(2)
while (q.length) {
const n = q.shift()
console.log(n)
graph[n].forEach(c => {
if (!visited.has(c)) {
q.push(c)
visited.add(c)
}
})
}
堆
- 一种特殊的完全二叉树
- 所有的节点都大于等于或小于等于它的子节点。分别称为最大堆或最小堆
- Javascript 中可以利用 Array 实现堆
规律
- 假设当前节点下标为
index
- 左侧子节点的位置是
2 * index + 1
- 右侧子节点的位置是
2 * index + 2
- 父节点的位置是
(index - 1) / 2
最小堆类
- 插入
- 将值插入堆的底部,即数组的尾部
- 然后上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值
- 删除堆顶
- 用数组尾部元素替换堆顶
- 然后下移:将新堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶
class MinHeap {
constructor() {
this.heap = []
}
swap(item1, item2) {
const temp = this.heap[item1]
this.heap[item1] = this.heap[item2]
this.heap[item2] = temp
}
getParentIndex(i) {
return (i - 1) >> 1
}
getLeftIndex(i) {
return i * 2 + 1
}
getRightIndex(i) {
return i * 2 + 2
}
shiftUp(index) {
if (index == 0) return
const parentIndex = this.getParentIndex(index)
if (this.heap[parentIndex] > this.heap[index]) {
this.swap(parentIndex, index)
this.shiftUp(parentIndex)
}
}
shiftDown(index) {
const leftIndex = this.getLeftIndex(index)
const rightIndex = this.getRightIndex(index)
if (this.heap[leftIndex] < this.heap[index]) {
this.swap(leftIndex, index)
this.shiftDown(leftIndex)
}
if (this.heap[rightIndex] < this.heap[index]) {
this.swap(rightIndex, index)
this.shiftDown(rightIndex)
}
}
insert(item) {
this.heap.push(item)
this.shiftUp(this.heap.length - 1)
}
pop() {
this.heap[0] = this.heap.pop()
this.shiftDown(0)
}
peek() {
return this.heap[0]
}
size() {
return this.heap.length
}
}
搜索与排序
冒泡排序
- 比较所有相邻元素,如果第一个比第二个大,则交换它们
- 每轮结束后,可以保证参与排序的最后一个数是最大的
- 执行 n-1轮,就可以完成排序
const bubbleSort = (arr) => {
const res = [...arr]
for (let i = 0; i < res.length - 1; i += 1) {
for (let j = 0; j < res.length - 1 - i; j += 1) {
if (res[j] > res[j + 1]) {
const temp = res[j]
res[j] = res[j + 1]
res[j + 1] = temp
}
}
}
return res
}
选择排序
- 找到数组中的最小值,选中它并将其放置在第一位
- 接着找到第二小的值,选中它并将其放置在第二位
- 以此类推,执行 n - 1 轮
const selectionSort = (arr) => {
const res = [...arr]
for (let i = 0; i < res.length - 1; i += 1) {
let indexMin = i
for (let j = i; j < res.length; j += 1) {
if (res[j] < res[indexMin]) {
indexMin = j
}
}
if (indexMin !== i) {
const temp = res[i]
res[i] = res[indexMin]
res[indexMin] = temp
}
}
return res
}
归并排序
- 分:把数组分成两半,再递归地对子数组进行“分”操作,直到分成一个个单独的数
- 合:把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组
const mergeSort = (arr) => {
const rec = (arr) => {
if (arr.length === 1) return arr
const mid = Math.floor(arr.length / 2)
const left = arr.slice(0, mid)
const right = arr.slice(mid, arr.length)
const orderLeft = rec(left)
const orderRight = rec(right)
const res = []
while (orderLeft.length || orderRight.length) {
if (orderLeft.length && orderRight.length) {
res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift())
} else if (orderLeft.length) {
res.push(orderLeft.shift())
} else if (orderRight.length) {
res.push(orderRight.shift())
}
}
return res
}
return rec(arr)
}
快速排序
- 分区:从数组中任意选择一个“基准”,所有比基准小的元素放在基准前面,比基准大的元素放在后面
- 递归:递归地对基准前后的子数组进行分区
const quickSort = (arr) => {
const rec = (arr) => {
if (arr.length === 1) return arr
const left = []
const right = []
const mid = arr[0]
for (let i = 1; i < arr.length; i += 1) {
if (arr[i] < mid) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return [...rec(left), mid, ...rec(right)]
}
return rec(arr)
}
顺序搜索
- 遍历数组,找到跟目标值相等的元素,就返回它的下标
- 遍历结束后,如果没有搜索到目标值,就返回 -1
Array.prototype.sequentialSearch = function (item) {
for (let i = 0; i < this.length; i += 1) {
if (this[i] === item) return i
}
return -1
}
const res = [1, 2, 3, 4, 5].sequentialSearch(0)
二分搜索
- 前提:数组是有序数组
- 从数组的中间元素开始,如果中间元素正好是目标值,则搜索结束
- 如果目标值大于或小于中间元素,则在大于或小于中间元素的那一半数组中搜索
Array.prototype.binarySearch = function (item) {
let low = 0
let high = this.length - 1
while (low <= high) {
const mid = Math.floor((low + high) / 2)
const element = this[mid]
if (element < item) {
low = mid + 1
} else if (element > item) {
high = mid - 1
} else {
return mid
}
}
return -1
}
const res = [1, 2, 3, 4, 5].binarySearch(0)