0

0

构建平衡二叉树:非BST的左到右插入策略

花韻仙語

花韻仙語

发布时间:2025-11-29 16:07:24

|

440人浏览过

|

来源于php中文网

原创

构建平衡二叉树:非BST的左到右插入策略

本文详细探讨了如何在非二叉搜索树(bst)场景下,实现一个平衡且按从左到右顺序填充节点的二叉树插入功能。文章首先阐述了此类插入与传统bst插入的区别及常见误区,接着提出了一种基于树当前大小的二进制表示来确定新节点插入路径的策略。通过迭代方式实现高效的插入操作,确保树的结构始终保持平衡和从左到右的填充顺序。

引言:非二叉搜索树的插入挑战

在数据结构领域,二叉树是一种基础且重要的结构。然而,大多数关于二叉树插入的示例都集中在二叉搜索树(BST)上,其中节点根据其值进行排序(左子节点小于父节点,右子节点大于父节点),且通常不允许重复值。

本文将探讨一种不同的场景:如何在普通的二叉树中实现插入功能,同时满足以下两个关键要求:

  1. 平衡性: 树的结构应尽可能保持平衡,避免退化为链表。
  2. 从左到右填充: 新节点应按层级从左到右的顺序填充,类似于完全二叉树的填充方式,但无需严格的完全二叉树定义。

值得注意的是,在此场景下,我们不关心节点值的排序,也不限制重复值。同时,为了深入理解底层机制,我们将尝试在不直接使用队列或列表等辅助数据结构的情况下实现这一功能。

理解平衡二叉树的“从左到右”插入

“从左到右”插入的目的是在添加新节点时,尽可能地保持树的紧凑和平衡。这意味着当一个层级未完全填满时,新节点应该优先填充该层级的空位,并且总是从左侧开始。一旦当前层级填满,新节点将进入下一层级的最左侧位置。

以下是理想的插入效果示例,它展示了节点如何按顺序(这里以数字为例,但实际值不影响结构)从左到右填充树:

│       ┌── 7
│   ┌── 3
│   │   └── 6
│   │        
└── 1
    │       
    │   ┌── 5
    │   │   └── 10
    └── 2   ┌── 9
        └── 4
            └── 8

在这个结构中,节点1是根,2是1的左子节点,3是1的右子节点,4是2的左子节点,5是2的右子节点,以此类推。这种结构保证了树的平衡性,并且每一层都尽可能从左向右填充。

常见误区:递归插入的局限性

许多初学者在尝试实现这种插入时,可能会直观地采用递归方式,并尝试通过简单的条件判断来决定向左或向右插入。例如,以下是一个常见的尝试:

// 假设 TreeNode 是一个包含 int data 和 TreeNode left/right 的类
class TreeNode {
    int data;
    TreeNode left;
    TreeNode right;

    public TreeNode(int data) {
        this.data = data;
        this.left = null;
        this.right = null;
    }

    // 初始尝试的插入方法
    TreeNode insert(int data, TreeNode root, boolean isLeft){
        if(root == null){
            return new TreeNode(data); // 如果根为空,创建新根
        }
        else if(root.left == null){
            root.left = new TreeNode(data);
        }
        else if(root.right == null){
            root.right = new TreeNode(data);
        }
        else{
            // 尝试通过 isLeft 标志递归向下
            if(isLeft){
                insert(data, root.right, false); // 这里的递归调用没有更新 root.right
            }
            else{
                insert(data, root.left, true); // 这里的递归调用没有更新 root.left
            }
        }
        return root;
    }
}

这段代码的问题在于,当遇到一个左右子节点都不为空的节点时,它会根据 isLeft 标志尝试向左或向右子树递归。然而,递归调用 insert(data, root.right, false) 或 insert(data, root.left, true) 的返回值并没有被赋给 root.right 或 root.left。这意味着,即使递归调用成功地在子树中创建了新节点,父节点也无法“感知”到这个变化,导致新节点实际上并未连接到树中。此外,即使修复了赋值问题,这种简单的递归逻辑也难以保证“从左到右”的层级填充,它往往会沿着某条路径深入,导致树结构不平衡。

Codiga
Codiga

可自定义的静态代码分析检测工具

下载

核心策略:利用树的大小和二进制表示

要实现从左到右的平衡插入,我们需要一种系统性的方法来确定下一个插入点。一个巧妙的解决方案是利用树的当前节点总数(即树的大小 size)及其二进制表示。

在一个完全二叉树中,我们可以将节点从1开始按层级、从左到右进行编号。例如:

      1
     / \
    2   3
   / \ / \
  4  5 6  7

如果我们想插入第 N+1 个节点,那么这个节点在完全二叉树中的逻辑编号就是 N+1。这个编号的二进制表示,可以为我们指明从根节点到新节点父节点的路径。

路径确定规则:

  1. 获取 N+1 的二进制表示。
  2. 忽略最高位(MSB),因为它总是 1,代表根节点本身。
  3. 从第二位开始,将 0 解释为向左遍历,将 1 解释为向右遍历。
  4. 这些位序列将引导我们从根节点遍历到新节点应该插入的父节点。

示例: 假设当前树有 4 个节点(size = 4),我们想插入第 5 个节点。

  1. N+1 = 5。
  2. 5 的二进制是 101。
  3. 忽略最高位 1,剩下 01。
  4. 0 表示向左,1 表示向右。所以路径是:根 -> 左子节点 -> 右子节点。 这意味着新节点 5 应该插入到 根的左子节点的右子节点 位置。

迭代式插入方法的实现

基于上述策略,我们可以实现一个迭代式的插入方法。这种方法通常比递归更清晰,因为它避免了递归深度和溢出的风险,并且更容易控制遍历过程。

首先,定义一个简单的 TreeNode 类:

// TreeNode.java
public class TreeNode {
    int data;
    TreeNode left;
    TreeNode right;

    public TreeNode(int data) {
        this.data = data;
        this.left = null;
        this.right = null;
    }

    // toString 方法用于打印树结构,这里省略具体实现,
    // 假定它能生成类似问题描述中的可视化输出。
    @Override
    public String toString() {
        return "TreeNode{" +
               "data=" + data +
               ", left=" + (left != null ? left.data : "null") +
               ", right=" + (right != null ? right.data : "null") +
               '}';
    }

    // 核心插入方法
    public TreeNode insert(int data, int size) {
        // 如果当前节点是 null,这通常意味着是第一次插入,
        // 但此方法预期在非空根节点上调用,所以此情况应在外部处理或作为特殊情况。
        // 这里假设 'this' 总是有效的根节点。

        TreeNode currentNode = this; // 从当前节点(通常是根)开始遍历

        // 获取 (size + 1) 的二进制表示。
        // (size + 1) 是下一个要插入节点的逻辑索引。
        // >> 1 移除最低位,然后 substring(1) 移除最高位。
        // 这样剩下的二进制字符串就是从根节点到新节点父节点的路径。
        // 例如:size=4, size+1=5 (101)。 (5>>1)=2 (10)。 "10".substring(1) -> "0"。
        // 路径 "0" 意味着从根向左走一步。
        String bits = Integer.toBinaryString((size + 1) >> 1).substring(1);

        // 遍历路径位,找到新节点的父节点
        for (char bit : bits.toCharArray()) {
            if (bit == '1') { // '1' 表示向右
                currentNode = currentNode.right;
            } else { // '0' 表示向左
                currentNode = currentNode.left;
            }
        }

        // 此时 currentNode 是新节点的父节点
        // 根据完全二叉树的填充规则,如果父节点的左子节点为空,则插入到左侧;
        // 否则,插入到右侧。这对应于 (size + 1) 的最低位。
        if (currentNode.left == null) {
            currentNode.left = new TreeNode(data);
        } else {
            currentNode.right = new TreeNode(data);
        }
        return this; // 返回根节点(通常是调用方法的对象本身)
    }
}

代码解析:

  1. TreeNode currentNode = this;: 插入操作从当前 TreeNode 实例(通常是树的根)开始。
  2. String bits = Integer.toBinaryString((size + 1) >> 1).substring(1);: 这是核心逻辑。
    • size + 1:表示下一个要插入的节点的逻辑索引(从1开始)。
    • >> 1:对 size + 1 进行右移一位操作。这会移除 size + 1 的最低位。
    • Integer.toBinaryString(...):将结果转换为二进制字符串。
    • .substring(1):移除二进制字符串的最高位(即最左边的

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

315

2023.08.02

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

254

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

206

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1463

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

617

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

548

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

543

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

159

2025.07.29

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

36

2026.01.14

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.5万人学习

C# 教程
C# 教程

共94课时 | 6.7万人学习

Java 教程
Java 教程

共578课时 | 46万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号