前端算法

数据结构

  • 后进先出
  • 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)  // true

// 遍历
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
        // return Math.floor((i - 1) / 2)
    }
    
    // 获取左子节点下标
    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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值