讲师中心 微信公众号
AI工具推荐 视频效率加速

java代码怎样实现红黑树及插入删除操作 java代码红黑树的应用实现方法​

酷强姑娘_7442

酷强姑娘_7442

发布时间:2025-08-14 10:14:01

|

374人浏览过

|

来源于php中文网

原创

红黑树通过颜色约束和旋转操作维持平衡,确保插入、删除和查找的时间复杂度均为O(log N)。其核心在于五条性质的维护,插入时新节点为红色并进行着色与旋转修复,删除黑色节点后需通过四种情况的调整恢复平衡。Java中TreeMap和TreeSet基于红黑树实现,提供有序存储与高效操作,适用于频繁增删查的场景。实现难点在于修复逻辑的正确处理、NIL哨兵节点管理及指针更新的准确性,调试时需结合图形化跟踪与边界条件验证。

java代码怎样实现红黑树及插入删除操作 java代码红黑树的应用实现方法​

红黑树(Red-Black Tree)在Java中实现,本质上是构建一个自平衡的二叉查找树。它通过对节点颜色(红或黑)的约束,确保了树在插入和删除操作后依然保持平衡,从而保证了查找、插入、删除等操作的平均和最坏时间复杂度都为O(log N)。这使得它成为需要高效、稳定性能的有序数据结构的首选。

实现一个红黑树,核心在于理解其五条性质以及如何在插入和删除后通过重新着色和旋转来维护这些性质。这听起来有点像是在玩一场复杂的魔方游戏,每一步操作都可能打乱平衡,但总有办法通过特定的“手法”将其恢复。

红黑树的Java实现及插入删除操作

要实现红黑树,我们首先需要定义节点结构,它至少包含键、值、颜色、父节点、左子节点和右子节点。

enum Color { RED, BLACK }

class Node<K, V> {
    K key;
    V value;
    Color color;
    Node<K, V> parent;
    Node<K, V> left;
    Node<K, V> right;

    Node(K key, V value) {
        this.key = key;
        this.value = value;
        this.color = Color.RED; // 新插入的节点默认为红色
        this.parent = null;
        this.left = null;
        this.right = null;
    }
}

public class RedBlackTree<K extends Comparable<K>, V> {
    private Node<K, V> root;
    private final Node<K, V> NIL; // 哨兵节点,代表空节点,颜色为黑色

    public RedBlackTree() {
        NIL = new Node<>(null, null); // 使用NIL节点简化边界条件处理
        NIL.color = Color.BLACK;
        root = NIL;
    }

    // 辅助方法:左旋、右旋、获取兄弟节点等
    private void leftRotate(Node<K, V> x) {
        Node<K, V> y = x.right;
        x.right = y.left;
        if (y.left != NIL) {
            y.left.parent = x;
        }
        y.parent = x.parent;
        if (x.parent == NIL) {
            root = y;
        } else if (x == x.parent.left) {
            x.parent.left = y;
        } else {
            x.parent.right = y;
        }
        y.left = x;
        x.parent = y;
    }

    private void rightRotate(Node<K, V> y) {
        Node<K, V> x = y.left;
        y.left = x.right;
        if (x.right != NIL) {
            x.right.parent = y;
        }
        x.parent = y.parent;
        if (y.parent == NIL) {
            root = x;
        } else if (y == y.parent.right) {
            y.parent.right = x;
        } else {
            y.parent.left = x;
        }
        x.right = y;
        y.parent = x;
    }

    // ... 其他辅助方法如 transplant, findMin, getSibling等
}

插入操作 (insert)

立即学习“Java免费学习笔记(深入)”;

插入一个新节点通常从标准的二叉查找树插入开始。新节点总是被着色为红色。之所以选择红色,是因为这能最大程度地减少对红黑树性质的破坏——它只会违反“红色节点的子节点必须是黑色”这一条性质。如果新节点是黑色,则可能导致路径上的黑色节点数量不平衡,修复起来会更复杂。

插入后,我们需要调用

insertFixup
方法来恢复红黑树的性质。这个修复过程主要处理三种情况:

  1. 父节点是黑色: 完美,无需任何操作,红黑树性质未被破坏。
  2. 父节点是红色: 这违反了性质4(红色节点的子节点必须是黑色)。此时需要进一步检查叔叔节点的颜色。
    • 叔叔节点是红色: 这种情况下,父节点和叔叔节点都是红色。我们将父节点和叔叔节点都变为黑色,祖父节点变为红色。然后将祖父节点视为新的当前节点,继续向上检查,直到根节点或父节点是黑色。
    • 叔叔节点是黑色(或NIL): 此时需要进行旋转。根据当前节点、父节点和祖父节点的相对位置,可能需要进行一次或两次旋转(左旋、右旋、左右旋、右左旋)。旋转后,通过重新着色来恢复性质。
public void insert(K key, V value) {
    Node<K, V> z = new Node<>(key, value);
    Node<K, V> y = NIL;
    Node<K, V> x = this.root;

    // 标准BST插入
    while (x != NIL) {
        y = x;
        if (z.key.compareTo(x.key) < 0) {
            x = x.left;
        } else {
            x = x.right;
        }
    }
    z.parent = y;
    if (y == NIL) {
        root = z;
    } else if (z.key.compareTo(y.key) < 0) {
        y.left = z;
    } else {
        y.right = z;
    }
    z.left = NIL;
    z.right = NIL;
    z.color = Color.RED; // 新节点为红色
    insertFixup(z);
}

private void insertFixup(Node<K, V> z) {
    while (z.parent.color == Color.RED) { // 只要父节点是红色,就需要修复
        if (z.parent == z.parent.parent.left) { // 父节点是左孩子
            Node<K, V> y = z.parent.parent.right; // 叔叔节点
            if (y.color == Color.RED) { // Case 1: 叔叔是红色
                z.parent.color = Color.BLACK;
                y.color = Color.BLACK;
                z.parent.parent.color = Color.RED;
                z = z.parent.parent;
            } else { // 叔叔是黑色
                if (z == z.parent.right) { // Case 2: 当前节点是右孩子
                    z = z.parent;
                    leftRotate(z);
                }
                // Case 3: 当前节点是左孩子
                z.parent.color = Color.BLACK;
                z.parent.parent.color = Color.RED;
                rightRotate(z.parent.parent);
            }
        } else { // 父节点是右孩子 (对称处理)
            Node<K, V> y = z.parent.parent.left; // 叔叔节点
            if (y.color == Color.RED) { // Case 1: 叔叔是红色
                z.parent.color = Color.BLACK;
                y.color = Color.BLACK;
                z.parent.parent.color = Color.RED;
                z = z.parent.parent;
            } else { // 叔叔是黑色
                if (z == z.parent.left) { // Case 2: 当前节点是左孩子
                    z = z.parent;
                    rightRotate(z);
                }
                // Case 3: 当前节点是右孩子
                z.parent.color = Color.BLACK;
                z.parent.parent.color = Color.RED;
                leftRotate(z.parent.parent);
            }
        }
    }
    root.color = Color.BLACK; // 根节点永远是黑色
}

删除操作 (delete)

删除操作比插入复杂得多。首先,我们找到要删除的节点。如果它有两个子节点,我们通常会找到其右子树中的最小节点(或左子树中的最大节点)来替换它,然后删除这个替换节点。这个策略是为了将删除操作简化为删除一个最多只有一个子节点的节点。

jQuery+CSS3 3D立体图片排列布局代码
jQuery+CSS3 3D立体图片排列布局代码

jQuery+CSS3 3D立体图片排列布局代码

下载

删除一个节点后,如果被删除节点是黑色,那么树的平衡可能会被打破(路径上的黑色节点数量减少)。此时需要调用

deleteFixup
方法来恢复红黑树的性质。这个修复过程更为复杂,涉及四种主要情况,每种情况又可能根据兄弟节点的颜色和其子节点的颜色细分。

  1. 兄弟节点是红色: 通过旋转和重新着色,将情况转换为兄弟节点是黑色的情况。
  2. 兄弟节点是黑色,且兄弟的两个子节点都是黑色: 将兄弟节点变为红色,然后将“问题”向上移动到父节点。
  3. 兄弟节点是黑色,兄弟的左子节点是红色,右子节点是黑色: 对兄弟节点进行右旋,并重新着色,将情况转换为兄弟节点是黑色且右子节点是红色的情况。
  4. 兄弟节点是黑色,兄弟的右子节点是红色: 通过左旋和重新着色来解决问题。
public void delete(K key) {
    Node<K, V> z = search(key); // 假设search方法能找到节点
    if (z == NIL) return; // 节点不存在

    Node<K, V> y = z;
    Color yOriginalColor = y.color;
    Node<K, V> x; // x将是替代y的节点,或者是在y被删除后需要修复的起点

    if (z.left == NIL) {
        x = z.right;
        transplant(z, z.right); // 用右子节点替换z
    } else if (z.right == NIL) {
        x = z.left;
        transplant(z, z.left); // 用左子节点替换z
    } else { // z有两个子节点
        y = findMin(z.right); // 找到右子树中最小的节点
        yOriginalColor = y.color;
        x = y.right; // x是y的右子节点,因为y是最小的,所以左子节点必为NIL

        if (y.parent == z) { // y是z的直接右子节点
            x.parent = y; // x的父节点更新为y,这步很重要,如果x是NIL,它的parent也需要指向y
        } else {
            transplant(y, y.right); // 用y的右子节点替换y
            y.right = z.right;
            y.right.parent = y;
        }
        transplant(z, y); // 用y替换z
        y.left = z.left;
        y.left.parent = y;
        y.color = z.color; // y继承z的颜色
    }

    if (yOriginalColor == Color.BLACK) { // 如果删除的节点是黑色,才需要修复
        deleteFixup(x);
    }
}

// 辅助方法:将u替换为v,并更新父子关系
private void transplant(Node<K, V> u, Node<K, V> v) {
    if (u.parent == NIL) {
        root = v;
    } else if (u == u.parent.left) {
        u.parent.left = v;
    } else {
        u.parent.right = v;
    }
    v.parent = u.parent;
}

private Node<K, V> findMin(Node<K, V> node) {
    while (node.left != NIL) {
        node = node.left;
    }
    return node;
}

private void deleteFixup(Node<K, V> x) {
    while (x != root && x.color == Color.BLACK) {
        if (x == x.parent.left) { // x是左孩子
            Node<K, V> w = x.parent.right; // 兄弟节点
            if (w.color == Color.RED) { // Case 1: 兄弟是红色
                w.color = Color.BLACK;
                x.parent.color = Color.RED;
                leftRotate(x.parent);
                w = x.parent.right; // 更新兄弟节点
            }
            // 兄弟是黑色
            if (w.left.color == Color.BLACK && w.right.color == Color.BLACK) { // Case 2: 兄弟的两个子节点都是黑色
                w.color = Color.RED;
                x = x.parent; // 问题上移
            } else {
                if (w.right.color == Color.BLACK) { // Case 3: 兄弟左子是红色,右子是黑色
                    w.left.color = Color.BLACK;
                    w.color = Color.RED;
                    rightRotate(w);
                    w = x.parent.right; // 更新兄弟节点
                }
                // Case 4: 兄弟右子是红色
                w.color = x.parent.color;
                x.parent.color = Color.BLACK;
                w.right.color = Color.BLACK;
                leftRotate(x.parent);
                x = root; // 修复完成,跳出循环
            }
        } else { // x是右孩子 (对称处理)
            Node<K, V> w = x.parent.left; // 兄弟节点
            if (w.color == Color.RED) { // Case 1: 兄弟是红色
                w.color = Color.BLACK;
                x.parent.color = Color.RED;
                rightRotate(x.parent);
                w = x.parent.left; // 更新兄弟节点
            }
            // 兄弟是黑色
            if (w.right.color == Color.BLACK && w.left.color == Color.BLACK) { // Case 2: 兄弟的两个子节点都是黑色
                w.color = Color.RED;
                x = x.parent; // 问题上移
            } else {
                if (w.left.color == Color.BLACK) { // Case 3: 兄弟右子是红色,左子是黑色
                    w.right.color = Color.BLACK;
                    w.color = Color.RED;
                    leftRotate(w);
                    w = x.parent.left; // 更新兄弟节点
                }
                // Case 4: 兄弟左子是红色
                w.color = x.parent.color;
                x.parent.color = Color.BLACK;
                w.left.color = Color.BLACK;
                rightRotate(x.parent);
                x = root; // 修复完成,跳出循环
            }
        }
    }
    x.color = Color.BLACK; // 最终确保当前节点(通常是根)为黑色
}

可以看到,删除操作的修复逻辑确实复杂,尤其是

deleteFixup
部分,需要非常细致地考虑各种情况。实际编码时,一个常见的坑就是NIL节点的处理,以及旋转后父子关系的更新。调试时,画图和逐步跟踪是必不可少的。

红黑树在Java集合框架中的应用案例有哪些?

红黑树在Java标准库中有着举足轻重的地位,最典型的应用就是作为

java.util.TreeMap
和
java.util.TreeSet
的底层实现。

TreeMap
是一个基于红黑树实现的
Map
接口,它能保证键值对按照键的自然顺序或者自定义的比较器顺序存储。这意味着当你遍历
TreeMap
时,你会得到一个有序的键集合。这种有序性,加上红黑树带来的O(log N)的时间复杂度保证(对于
put
、
get
、
remove
等操作),使得
TreeMap
在需要有序键值对存储的场景下表现卓越。比如,你可能需要一个按时间戳排序的事件队列,或者一个按字母顺序排列的单词计数器,
TreeMap
就能派上大用场。它的优势在于,无论数据量多大,查询、插入、删除的性能都相对稳定,不会出现极端情况下的性能骤降。

TreeSet
则是一个基于
TreeMap
实现的
Set
接口。它存储的是不重复的元素,并且这些元素也是有序的。
TreeSet
内部实际上维护了一个
TreeMap
,其中
Set
中的元素作为
TreeMap
的键,而
TreeMap
的值则是一个虚拟的占位符对象。因此,
TreeSet
也继承了红黑树的所有优点:元素有序、操作效率高且稳定。例如,如果你想快速找出某个范围内的所有不重复的数字,或者需要一个能自动排序的黑名单列表,
TreeSet
都是一个非常合适的选择。

它们之所以选择红黑树而不是其他平衡二叉树(如AVL树),一个重要的考量是红黑树在插入和删除操作上通常比AVL树需要更少的旋转次数。虽然AVL树在查找性能上可能略优(因为它平衡性更严格),但在频繁更新(插入、删除)的场景下,红黑树的综合表现往往更佳,因为它在保持良好查找性能的同时,对修改操作的开销控制得更好。这在通用数据结构中是一个很重要的权衡。

实现红黑树时常见的挑战和调试技巧?

实现红黑树,说实话,是个不小的挑战。我个人在尝试实现时,也曾被那些复杂的旋转和着色逻辑搞得头晕眼花。它不像链表或简单的二叉树那样直观,每一步操作都牵一发而动全身。

常见的挑战:

  1. 理解和记忆修复逻辑: 插入和删除后的
    fixup
    函数是红黑树的核心,也是最容易出错的地方。特别是删除操作,它的修复情况多达四种主类型,每种又可能细分,并且还涉及对兄弟节点及其子节点的颜色判断。记住这些情况并正确实现它们的旋转和重新着色逻辑,是最大的难点。
  2. 边界条件处理: 树为空、只有一个根节点、删除根节点、NIL节点(哨兵节点)的正确使用和维护,这些都是容易被忽视但又极其关键的细节。比如,
    NIL
    节点的父节点、子节点应该指向谁,它的颜色始终是黑色,这些都需要在代码中明确体现。
  3. 指针操作的准确性: 旋转操作涉及到父子指针的修改,任何一个指针指向错误都可能导致树结构损坏,甚至出现无限循环。例如,
    leftRotate
    或
    rightRotate
    后,新的根节点需要正确更新,被旋转节点的父节点也需要正确指向新位置。
  4. 维护红黑树的五条性质: 在每一步操作后,都需要确保红黑树的五条性质(节点非红即黑;根节点是黑色;所有叶子节点(NIL)是黑色;红色节点的子节点都是黑色;从

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

花生AI
花生AI Hot

一款AI视频创作工具,主要用于B站推出的AI视频创作工具,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

相关专题

更多
C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

379

2026.04.23

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2681

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

376

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

417

2026.01.06

C++ 数据结构与算法实现教程合集
C++ 数据结构与算法实现教程合集

以 C++ 为实现语言,系统讲解核心数据结构与算法,涵盖链表(单链表/双链表/环检测)、栈与队列(单调栈/优先队列)、二叉树(遍历/BST/AVL/红黑树)、哈希表(开地址法/链地址法)、图(邻接表/BFS/DFS/Dijkstra/拓扑排序)、常见排序算法(快排/归并/堆排/计数排序)的实现与复杂度分析,同时分享 LeetCode 刷题技巧、竞赛编程常用模板(二分/前缀和/滑动窗口/动态规划),帮助开发者夯实算法基础。

472

2026.05.09

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

3188

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

5129

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3829

2025.12.29

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

60

2026.10.28

热门下载

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

精品课程

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

共23课时 | 9.5万人学习

vscode其实很简单
vscode其实很简单

共72课时 | 32.5万人学习

JavaScript OOP调试技巧视频教程
JavaScript OOP调试技巧视频教程

共5课时 | 1.1万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn