JavaScript算法(二)

本文详细介绍了JavaScript中的树结构,包括深度优先遍历(如DFS和二叉树的先序、中序、后序遍历)、广度优先遍历(BFS),以及如何在前端遍历JSON对象。还涉及了使用堆(如最小堆和最大堆)解决特定问题,如找到数组中的第K个最大元素和前K个高频元素。
摘要由CSDN通过智能技术生成

系列文章目录

JavaScript算法(一)


目录


前言

JavaScript算法,树、堆相关知识


9.树

  • 一种分层数据的抽象模型
  • 前端工作中常见的树包括:DOM树、级联选择、树形控件
  • JS中没有树,可以用Object和Array构建树
  • 树的常用操作:深度/广度优先遍历、先中后序遍历

深度优先遍历:尽可能深的搜索树的分支

  • 访问根节点
  • 对跟节点的children 挨个进行深度优先遍历
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((child) => {dfs(child)})
}

dfs(tree);

广度优先遍历:先访问离根节点最近的节点

  • 新建一个队列,把根节点入队
  • 把队头出队并访问
  • 把队头的children挨个入队
  • 重复第二、三步,直到队列为空
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)
        })
    }
}

bfs(tree);

二叉树

  • 树中每个节点最多只能有两个子节点
  • JS中通常用Object模拟二叉树
const binaryTree = {
    val: 1,
    left:{
        val: 2,
        left: null,
        right: null,
    },
    right:{
        val: 3,
        left: null,
        right: null,
    },
}

先序遍历

const bt = {
    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 preorder1 = (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 inorder1 = (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 stack = [root];
    const outputStack = [];
    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);
    }
}
// leetCode 104 二叉树的最大深度
// 输入 [3,9,20,null,null,15,7] 输出3
var maxDepth = function(root){
    let res = 0;
    const dfs = (n,l) => {
        if(!n) return ;
        if(!n.left && !n.right){
            res = Math.max(res,l);
        }
        dfs(n.left, l+1);
        dfs(n.right, l+1);
    }
    dfs(root,1);
    return res;
}

// leetCode 111 二叉树的最小深度(广度优先遍历)
// 输入 [3,9,20,null,null,15,7] 输出2
var minDepth = function(root){
    if(!root) return 0;
    const q = [[root,1]];
    while(q.length){
        const [n,len] = q.shift();
        if(!n.left && !n.right){
            return len;
        }
        if(n.left) q.push([n.length, len+1]);
        if(n.right) q.push([n.right, len+1]);
    }
}

// leetCode 102 二叉树的层序遍历(广度优先遍历,记录层级)
// 输入[3,9,20,null,null,15,7] 输出[[3],[9,20],[15,7]]
var levelOrder = function(root){
    if(!root) return [];
    const q = [[root,0]];
    const res = [];
    while(q.length){
        const [n,level] = q.shift();
        if(!res[level]){
            res.push([n.val]);
        }else{
            res[level].push(n.val);
        }
        if(n.left)q.push([n.left, level+1]);
        if(n.right)q.push([n.right, level+1]);
    }
    return res;
}
var levelOrder1 = function(root){
    if(!root) return [];
    const q = [root];
    const res = [];
    while(q.length){
        let len = q.length;
        res.push([]);
        while(len--){
            const n = q.shift();
            res[res.length - 1].push(n.val);
            if(n.left)q.push(n.left);
            if(n.right)q.push(n.right);
        }
    }
    return res;
}

// leetCode 94 二叉树的中序遍历(左根右)
// 输入[1,null,2,3] 输出[1,3,2]
var inorderTraversal = function(root){
    const res = [];
    const rec = (n)=>{
        if(!n)return;
        rec(n.left);
        res.push(n.val);
        rec(n.right);
    }
    rec(root);
    return res;
}
// 迭代版
var inorderTraversal1 = function(root){
    const res = [];
    const stack = [];
    let p = root;
    while(stack.length || p){
        while(p){
            stack.push(p);
            p = p.left;
        }
        const n = stack.pop();
        res.push(n.val);
        p = n.right;
    }
    return res;
}

// leetCode 112 路径总和(深度优先遍历)
// 给定目标和,计算二叉树是否有满足目标和的路径
var hasPathSum = function(root,sum){
    if(!root) return false;
    let res = false;
    const dfs = (n,s) => {
        if(!n.left && !n.right && s === sum){
            res = true;
        }
        if(n.left) dfs(n.left, s+n.left.val);
        if(n.right) dfs(n.right, s+n.right.val);
    }
    dfs(root,root.val);
    return res;
}

前端与树:遍历JSON的所有节点值

const json = {
    a:{ b: { c: 1}},
    d: [1,2],
};

const dfs = (n, path) => {
    console.log(n,path);
    Object.keys(n).forEach(k => {
        dfs(n[k], path.concat(k));
    });
}
dfs(json,[]);

10.堆

  • 堆是一种特殊的完全二叉树(每层都填满,不填满仅缺少最后一层的右节点)
  • 所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点

// leetCode 215 数组中的第K个最大元素
// 构建一个最小堆,并依次把数组的值插入堆中;当堆的容量超过k,就删除堆顶
// 输入[3,2,1,5,6,4] k=2 输出5
// 最小堆类
class MinHeap {
    constructor() {
        this.heap = [];
    }
    swap(i1,i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = 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(value){
        this.heap.push(value);
        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;
    }
}

var findKthLargest = function(nums,k){
    const h = new MinHeap();
    nums.forEach(n => {
        h.insert(n);
        if(h.size() > k){
            h.pop();
        }
    });
    return h.peek();
}

// leetCode 347 前k个高频元素
// 输入[1,1,1,2,2,3],k=2 输出[1,2]
//Map方法
var topKFrequent = function(nums,k){
    const map = new Map();
    nums.forEach(n => {
        map.set(n,map.has(n) ? map.get(n)+1:1);
    })
    const list = Array.from(map).sort((a,b)=> b[1]-a[1]);
    return list.slice(0,k).map(n=>n[0]);
}

// 最小堆
// MinHeap类,shiftUp/shiftDown,需要换成比较value
var topKFrequent1 = function(nums,k){
    const map = new Map();
    nums.forEach(n => {
        map.set(n,map.has(n) ? map.get(n)+1:1);
    })
    const h = new MinHeap();
    map.forEach((value,key)=>{
        h.insert({value,key});
        if(h.size() > k){
            h.pop();
        }
    })
    return h.heap.map(a => a.key);
}

总结

JavaScript算法

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值