web前端算法之二叉树

本文详细介绍了二叉树在数据结构中的应用,包括二叉搜索树、排序算法(如快速排序和归并排序)、表达式求值、决策树、数据压缩以及在编译器中的语法分析。同时,展示了二叉树的构造、节点连接、遍历方法(前序、后序、中序)以及还原和修改操作的示例。
摘要由CSDN通过智能技术生成

二叉树使用场景

  • 数据结构:二叉树是一种常见的数据结构,可以用于存储和组织数据。例如,二叉搜索树是一种特殊的二叉树,它可以快速地查找、插入和删除数据。
  • 排序算法:二叉树可以用于实现一些排序算法,例如快速排序和归并排序。在这些算法中,二叉树被用来将数据划分为独立的子集,并对这些子集进行排序。
  • 表达式求值:二叉树可以用于表示数学表达式,并对其进行求值。在这种情况下,二叉树的每个节点表示一个运算符或操作数,而节点的子节点表示操作数。
  • 实现决策树:在机器学习中,决策树是一种重要的算法,它可以用于分类和回归问题。在这种情况下,二叉树被用来表示决策过程,每个节点表示一个决策条件,而子节点表示不同的结果。
  • 数据压缩:在数据压缩中,二叉树可以用于编码和解码数据。在这种情况下,二叉树的每个节点表示一个符号或一组符号的编码,而子节点表示编码的上下文。
    编译器:在编译器中,二叉树被用来表示源代码的结构,并对源代码进行语法分析和语义分析。在这种情况下,二叉树的每个节点表示一个语法或语义结构,而子节点表示语法或语义规则。

用代码描述二叉树

图1、二叉树

// 构造二叉树
const TreeNode = function(value) {
    this.value = value;
    this.left = null;
    this.right = null;
}
// 创建节点
const a = new TreeNode('A');
const b = new TreeNode('B');
const c = new TreeNode('C');
const d = new TreeNode('D');
const e = new TreeNode('E');
const f = new TreeNode('F');
const g = new TreeNode('G');

// 连接节点
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right = g;
console.log("构建的二叉树如下:", a);

二叉树遍历

前序遍历
  • 概念: 根节点 -> 左子树 -> 右子树
  • 图1中二叉树前序遍历:[‘A’,’ B’,’ D’, ’ E’, ’ C’, ’ F’, ’ G’]
后序遍历
  • 概念: 左子树 -> 右子树 -> 根节点
  • 图1中二叉树后序遍历: [’ D’, ’ E’, ’ B’, ’ F’, ’ G’, ’ C’, ‘A’]
中序遍历
  • 概念: 左子树 -> 根节点 -> 右子树
  • 图1中二叉树后序遍历:[’ D’, ’ B’, ’ C’, ‘A’, ’ F’, ’ C’, ’ G’]

二叉树还原

  • 注意:有中序遍历情况 + 前序或者后序遍历才能还原二叉树
  • 前序遍历+中序遍历 还原二叉树
/**
 * 通过前序、中序还原二叉树
 * @param {array} qian 前序遍历值数组
 * @param {array} zhong 中序遍历值数组
 * @returns 根节点 
 */
function toTreeFromQianzhong(qian, zhong) {
    if(qian !== null && qian.length > 0 && zhong !== null && zhong.length > 0 && qian.length === zhong.length){
        // 通过前序遍历获取根节点
        const node = new TreeNode(qian[0]);
        // 中序处理:得到左子树与右子树
        const nodePos = zhong.indexOf(node.value);
        const zLeft = zhong.slice(0, nodePos);
        const zRight = zhong.slice(nodePos+1, zhong.length);
        // 前序处理:得到左子树与右子树
        const leftEnd = 1+ zLeft.length;
        const qLeft = qian.slice(1, leftEnd);
        const qRight = qian.slice(leftEnd, qian.length);
        // 递归处理左右子树
        node.left = toTreeFromQianzhong(qLeft, zLeft);
        node.right = toTreeFromQianzhong(qRight, zRight);
        return node;
    }else{
        return null;
    }    
}
const qianList = ['A',' B',' D', ' e', ' C', ' F', ' G'];
const zhongList = [' D', ' B', ' C', 'A', ' F', ' C', ' G'];
const qzRoot = toTreeFromQianzhong(qianList, zhongList);
console.log("前序中序还原二叉树", qzRoot);
  • 后序遍历+中序遍历 还原二叉树
/**
 * 通过后序、中序还原二叉树
 * @param {array} hou 后序遍历值数组
 * @param {array} zhong 中序遍历值数组
 * @returns 
 */
function toTreeFromHouzhong(hou, zhong) {
    if(hou !== null && hou.length > 0 && zhong !== null && zhong.length > 0 && hou.length === zhong.length){
        // 通过后序遍历获取根节点
        const node = new TreeNode(hou[hou.length - 1]);
        // 中序处理:得到左子树与右子树
        const nodePos = zhong.indexOf(node.value);
        const zLeft = zhong.slice(0, nodePos);
        const zRight = zhong.slice(nodePos+1, zhong.length);
        // 前序处理:得到左子树与右子树
        const leftEnd = 0 + zLeft.length;
        const hLeft = hou.slice(0, leftEnd);
        const hRight = hou.slice(leftEnd, hou.length - 1);
        // 递归处理左右子树
        node.left = toTreeFromHouzhong(hLeft, zLeft);
        node.right = toTreeFromHouzhong(hRight, zRight);
        return node;
    }else{
        return null;
    }    
}
const zhongList = [' D', ' B', ' C', 'A', ' F', ' C', ' G'];
const houList = [' D', ' e', ' B', ' F', ' G', ' C', 'A'];
const hzRoot = toTreeFromHouzhong(houList, zhongList);
console.log("后序、中序还原二叉树", hzRoot);

二叉树diff算法(区分左右子树)

/**
 * 比较两棵二叉树的的diff算法
 * 注意:区分左右子树情况
 * 添加: {type: 'add', old: null,  cur: null}
 * 修改: {type: 'modify', old: null,  cur: null}
 * 删除: {type: 'delete', old: null,  cur: null}
 */
function lrDiffNode(node1, node2, diff) {
    if(node1 === null && node2 === null){
        // 没有变化,并且已经结束
        return diff;
    }else if(node1 !== null && node2 === null){
        // 删除情况,并且已经结束
        diff.push({type: 'delete', old: node1,  cur: node2});
        return diff;
    }else if(node1 === null && node2 !== null){
        // 添加情况,并且已经结束
        diff.push({type: 'add', old: node1,  cur: node2});
        return diff;
    }else{
        // node1与node2都不为空情况下
        if(node1 === node2){
            // 完全相同
            return diff;
        }else{
            // 比对左右子树
            const diffLr = ()=>{
                lrDiffNode(node1.left, node2.left, diff);
                lrDiffNode(node1.right, node2.right, diff);
            };
            if(node1.value === node2.value){
                // 值相同,那么比较左右子树
                diffLr();
                return diff;
            }else{
                // 记录不同信息
                diff.push({type: 'modify', old: node1,  cur: node2});
                // 继续比对左右子树
                diffLr();
                return diff;
            }
        }
    }
}

// 删除:f节点
const qianList1= ['A',' B',' D', ' e', ' C', ' G'];
const zhongList1 = [' D', ' B', ' C', 'A', ' C', ' G'];
const houList1 = [' D', ' e', ' B',  ' G', ' C', 'A'];
const newTreeRoot = toTreeFromQianzhong(qianList1, zhongList1);
console.log("diff: 删除f节点", lrDiffNode(a, newTreeRoot, []));

// 将b节点修改为h
const qianList_modify_b_h = ['A','h',' D', ' e', ' C', ' F', ' G'];
const zhongList_modify_b_h = [' D', 'h', ' C', 'A', ' F', ' C', ' G'];
const houList_modify_b_h = [' D', ' e', 'h', ' F', ' G', ' C', 'A'];
const treeRoot_b_h = toTreeFromHouzhong(houList_modify_b_h, zhongList_modify_b_h);
console.log("diff: b节点修改为h", lrDiffNode(a, treeRoot_b_h, []));

二叉树查找

  • 深度优先算法
/**
 * 查找二叉树中元素(深度优先)- 寻找的路径其实就是前序遍历
 * @param root 根节点
 * @param findValue 查找的值
 * @returns 
 */
const findNode = function(root, findValue) {
    if(root!== null){
        if(findValue !== null && findValue !== void 0){
            console.log("深度优先过程:" + root.value);
            if(root.value === findValue){
                return root;
            }else{
                // 分别在左右子树中寻找
                return findNode(root.left, findValue) || findNode(root.right, findValue);
            }
        }else{
            return null;
        }
    }else{
        return null;
    }
}
console.log("find[深度优先]:在tree中找出g元素", findNode(a, ' F'));
  • 广度优先算法
/**
 * 查找二叉树中元素(广度优先)
 * @param nodes 
 * @param findValue 
 * @returns 
 */
const findNodeByScope = function(nodes, findValue) {
    if(nodes!== null && nodes.length > 0){
        const nextFindNodes = [];
        for (let node of nodes) {
            console.log("广度优先过程:" + node.value);
            if(node.value === findValue){
                return node;
            }else{
                nextFindNodes.push(node.left);
                nextFindNodes.push(node.right);
            }
        }
        return findNodeByScope(nextFindNodes, findValue);
    }else{
        return null;
    }
}
console.log("find[广度优先]: 在tree中找出g元素", findNodeByScope([a], ' G'));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光速度的进程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值