关于树的易混淆概念

树的层数、高度、深度等概念是数据结构中非常重要的基础知识,但它们有时会让人感到混淆。


1. 树的基本结构

我们先来看一棵简单的二叉树:

        A
       / \
      B   C
     / \   \
    D   E   F
  • A 是根节点。
  • BCA 的子节点。
  • DEF 是叶子节点(没有子节点的节点)。

2. 树的层数(Level)

定义

  • 层数 是从根节点开始计数的,根节点所在的层为第 1 层
  • 每向下一层,层数加 1。

计算方式

  • 第 1 层:根节点 A
  • 第 2 层:BC
  • 第 3 层:DEF

总层数

  • 总层数是从根节点到最深叶子节点的层数。
  • 在这棵树中,总层数为 3

用途

  • 层数常用于描述节点在树中的位置,例如“节点 E 在第 3 层”。

3. 树的高度(Height)

定义

  • 高度 是从某个节点到其最远叶子节点的最长路径上的边数。
  • 如果从根节点开始计算,则称为整棵树的高度。

计算方式

  • 对于每个节点:
    • 叶子节点的高度为 0(因为没有子节点)。
    • 非叶子节点的高度 = 其子节点的最大高度 + 1。
  • 整棵树的高度 = 根节点的高度。

例子

  • 节点 F 是叶子节点,高度为 0
  • 节点 C 的高度 = 子节点 F 的高度 + 1 = 1
  • 节点 B 的高度 = 子节点 DE 的最大高度 + 1 = 1
  • 节点 A 的高度 = 子节点 BC 的最大高度 + 1 = 2

因此,这棵树的高度为 2

用途

  • 树的高度常用于衡量树的平衡性或复杂度。例如,在 AVL 树中,要求左右子树的高度差不超过 1。

4. 节点的深度(Depth)

定义

  • 深度 是从根节点到某个节点的路径上的边数。
  • 根节点的深度为 0

计算方式

  • 根节点 A 的深度为 0
  • 节点 BC 的深度为 1(从 ABC 的路径上有 1 条边)。
  • 节点 DEF 的深度为 2

用途

  • 深度常用于描述节点在树中的纵向位置。例如,“节点 F 的深度为 2”。

5. 树的宽度(Width)

定义

  • 宽度 是指树中某一层实际存在的最大节点数 。
  • 它通常用来描述树的“横向扩展程度”。

注意:我们需要遍历每一层,统计每层的节点数,并取其中的最大值作为树的最大宽度。换句话说,树的宽度并不关心某一层理论上最多可以放多少节点,而是关注当前层实际有多少节点。

计算方式

  • 第 1 层:只有根节点 A,宽度为 1
  • 第 2 层:有节点 BC,宽度为 2
  • 第 3 层:有节点 DEF,宽度为 3

因此,这棵树的最大宽度为 3

用途

  • 树的宽度常用于分析空间复杂度。例如,在 BFS 中,队列的空间需求取决于树的最大宽度。

6. 树的总结

概念定义计算方式示例值(以本文树为例)
层数从根节点开始计数,根节点为第 1 层。按层遍历,逐层加 1。总层数 = 3
高度从某个节点到其最远叶子节点的最长路径上的边数。叶子节点高度为 0,非叶子节点高度 = 子节点最大高度 + 1。树的高度 = 2
深度从根节点到某个节点的路径上的边数。根节点深度为 0,其他节点深度 = 父节点深度 + 1。节点 F 的深度 = 2
宽度某一层实际存在的最大节点数。按层统计节点数,取最大值。最大宽度 = 3

7. 代码实现

计算树的高度

public int treeHeight(TreeNode root) {
    if (root == null) {
        return 0; // 空树的高度为 0
    }
    int leftHeight = treeHeight(root.left);
    int rightHeight = treeHeight(root.right);
    return Math.max(leftHeight, rightHeight) + 1;
}

计算节点的深度

public int nodeDepth(TreeNode root, TreeNode target) {
    if (root == null) {
        return -1; // 目标节点不存在
    }
    if (root == target) {
        return 0; // 找到目标节点,深度为 0
    }
    int leftDepth = nodeDepth(root.left, target);
    if (leftDepth != -1) {
        return leftDepth + 1; // 在左子树中找到目标节点
    }
    int rightDepth = nodeDepth(root.right, target);
    if (rightDepth != -1) {
        return rightDepth + 1; // 在右子树中找到目标节点
    }
    return -1; // 目标节点不在树中
}

计算树的最大宽度

public int maxWidth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    int maxWidth = 0;

    while (!queue.isEmpty()) {
        int size = queue.size();
        maxWidth = Math.max(maxWidth, size); // 更新最大宽度

        for (int i = 0; i < size; i++) {
            TreeNode cur = queue.poll();
            if (cur.left != null) {
                queue.offer(cur.left);
            }
            if (cur.right != null) {
                queue.offer(cur.right);
            }
        }
    }
    return maxWidth;
}

8. 总结

  • 层数:从根节点开始计数,根节点为第 1 层。
  • 高度:从某个节点到最远叶子节点的最长路径上的边数。
  • 深度:从根节点到某个节点的路径上的边数。
  • 宽度:某一层实际的最大节点数。
### 抽象语法混淆原理与入门 抽象语法(AST)作为源代码的中间表示形式,在程序分析、转换以及优化方面扮演着重要角色。对于JavaScript而言,通过工具如Babel可以方便地操作AST来进行各种变换工作,包括但不限于代码混淆和反混淆。 #### AST反混淆概念混淆是指将经过某种方式加密或者难以阅读理解的代码恢复到接近原始状态的过程。当涉及到基于AST的操作时,这意味着要识别并逆转那些用于使代码变得复杂难懂的技术手段。这些技术可能涉及重命名变量名、插入无意义语句或其他任何旨在增加理解和调试难度的方法[^1]。 #### 实现AST反混淆的关键步骤 为了有效地利用AST进行反混淆,有几个核心概念和技术值得掌握: - **解析**: 使用像`@babel/parser`这样的库可以从给定的JS代码片段生成对应的AST结构。这一步骤至关重要,因为它提供了后续所有工作的基础数据结构。 - **遍历与模式匹配**: `@babel/traverse`允许开发者高效地访问每一个节点,并且可以根据特定条件筛选感兴趣的元素。这对于定位潜在的混淆手法非常有用。 - **替换/删除有害节点**: 一旦发现了可疑或不必要的部分,则可以通过编写相应的逻辑来移除它们或是将其替换成更清晰读的内容。 - **重构表达式**: 对于某些类型的混淆来说,简单的字符替换并不足以解决问题。此时就需要深入研究如何重组复杂的表达式以便更好地反映其意图。 - **重新生成代码**: 经过上述处理之后,最终还需要借助`@babel/generator`把修改后的AST再转化回可执行的人类友好型代码文件[^4]。 #### 工具链介绍 在实际项目中实施以上策略离不开一系列强大的开源软件包支持。除了前面提到过的几个主要组件外,还有其他辅助性的资源可以帮助加速开发进度: - Babel本身不仅是一个编译器框架,同时也提供了一个活跃社区维护下的插件生态系统,其中不乏专门针对不同场景定制的功能扩展。 - AST Explorer作为一个在线平台,使得探索和实验变得更加直观简单。用户可以直接上传待测试的目标脚本,即时查看所形成的形视图及其变化情况[^2]。 #### 学习路径建议 想要深入了解这个领域的新手应该遵循如下路线逐步积累经验: - 掌握基本理论知识,熟悉ECMAScript标准文档中的相关内容; - 动手实践常用API接口调用,尝试完成小型练习任务; - 关注行业动态,积极参与讨论交流群组获取最新资讯; - 阅读高质量案例分享文章,借鉴他人成功经验和技巧总结。 ```javascript // 示例:使用Babel API加载一段混淆后的JS代码并打印出根级函数声明的名字列表 const babel = require('@babel/core'); const t = require("@babel/types"); const traverse = require("@babel/traverse").default; let code = 'function a(){var _0x9a8b=["hello","world"];console[_0x9a8b[0]](_0x9a8b[1]);}'; let result = babel.parseSync(code); traverse(result, { FunctionDeclaration(path){ console.log(t.functionDeclarationIdentifier(path.node).name); // 输出:"a" } }); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值