当前位置: 首页 > news >正文

红黑树-带源码

目录

一 红黑树概述

二 红黑树插入原理介绍

三 红黑树删除的原理介绍

四 红黑树 Java 实现

五 代码解释

1. RedBlackNode 节点类

2. insert() 方法

3. handleReorient() 与 rotate()

六 红黑树总结


一 红黑树概述

红黑树(Red-Black Tree,简称 RBT)是一种自平衡二叉查找树(Self-Balancing Binary Search Tree)。它在普通二叉查找树(BST)的基础上,通过“红黑规则”与旋转、变色操作保证了树的近似平衡,从而使得插入、删除、查询等操作的时间复杂度稳定在 O(log n)

红黑树最早由 Rudolf Bayer 于 1972 年提出,原名为 对称二叉 B 树(Symmetric Binary B-tree)。后来由 Leo J. GuibasRobert Sedgewick 于 1978 年改进并命名为红黑树。如今,红黑树已成为计算机科学领域中使用最广泛的平衡树之一。

  • 红黑树的五条性质

    1. 每个结点要么是红色,要么是黑色。

    2. 根结点是黑色。

    3. 所有叶子结点(NIL或NULL节点)都是黑色。

    4. 如果一个节点是红色,则它的两个子节点必须是黑色(红节点不能相邻)。

    5. 从任意一个节点到其所有叶子节点的路径上,黑色节点数相同

第五条性质称为 黑高平衡,它是红黑树平衡性的核心。虽然红黑树不是严格意义上的高度平衡树(如AVL树),但其最大高度不会超过 2*log2(n+1)(最长路径(红黑节点交替)不会超过最短路径(全黑节点)的两倍),在实际应用中性能非常稳定。

  • 红黑树的基本操作

    为了在插入和删除后仍能维持红黑树的五大性质,我们需要进行两种基本操作:变色旋转

    1. 旋转

      旋转是保持BST性质并调整树结构的局部操作。

      • 左旋:以某个节点为支点,其右子节点成为新的父节点,支点自身成为新父节点的左子节点。

      • 右旋:以某个节点为支点,其左子节点成为新的父节点,支点自身成为新父节点的右子节点。

    1. 变色

      简单地改变节点的颜色,从红变黑或从黑变红。这是最直接的调整方式。

二 红黑树插入原理介绍

插入新节点的步骤可以概括为两步:

  1. 标准BST插入:首先,像在普通二叉查找树中一样插入新节点。新插入的节点我们总是将其着色为红色

    • 为什么是红色?因为如果插入黑色节点,会立即违反性质5(黑高不一致),修复起来非常困难。插入红色节点可能违反性质2(根节点为黑)或性质4(不能有连续红节点),但修复这些情况相对容易。

  2. 重新平衡与修复:如果插入后违反了红黑树的性质,我们需要通过变色和旋转来修复。修复的情况主要取决于新节点的父节点叔叔节点(父节点的兄弟节点)的颜色。

我们定义:

  • P:父节点

  • U:叔叔节点

  • G:祖父节点

  • N:新插入的节点

情况1:N是根节点

  • 操作:直接将N变为黑色。(违反性质2)

情况2:P是黑色

  • 操作:什么都不用做。树仍然是有效的红黑树。(没有违反任何性质)

情况3:P是红色,U也是红色

  • 操作

    1. 将P和U变为黑色。

    2. 将G变为红色。

    3. 将G视为新的当前节点,从情况1开始递归检查。

情况4:P是红色,U是黑色(或NIL),且N是P的右子节点,P是G的左子节点

  • 操作

    1. 以P为支点进行左旋。

    2. 将P作为新的当前节点,此时情况转变为情况5。

情况5:P是红色,U是黑色(或NIL),且N是P的左子节点,P是G的左子节点

  • 操作

    1. 将P变为黑色。

    2. 将G变为红色。

    3. 以G为支点进行右旋。

(如果P是G的右子节点,则情况4和5是镜像对称的,操作中的左右旋相反)

三 红黑树删除的原理介绍

删除操作比插入更复杂,但核心思想相似:先执行标准BST删除,然后修复可能被破坏的红黑性质。

  1. 标准BST删除

    • 如果被删除的节点有两个非NIL子节点,我们通常找到它的后继节点(右子树中的最小节点),用后继节点的值替换被删除节点的值,然后转而删除这个后继节点。这样问题就转化为删除一个至多只有一个子节点的节点

    • 最终,我们实际删除的节点(记为 D)最多只有一个子节点(记为 C)。

  2. 重新平衡与修复

    • 如果 D 是红色,直接删除它,用 C 替换它,不会破坏任何性质。

    • 如果 D 是黑色,而 C 是红色,那么直接用红色的 C 替换 D,并将 C 变为黑色。

    • 最复杂的情况:如果 DC 都是黑色(C 可能是NIL节点)。删除 D 后,经过 D 的路径会少一个黑色节点,破坏了性质5。修复过程需要根据兄弟节点 S 的颜色和其子节点的颜色来分多种情况处理,通过变色和旋转将“双重黑色”向上传递或消除。这个过程比插入更繁琐,但核心目标始终是恢复黑高平衡。

四 红黑树 Java 实现

下面的代码实现基于 自顶向下插入法(Top-Down Insertion),该方法在插入时提前进行调整,避免自底向上回溯,提高了效率。

package org.algds.tree.ds;
​
/*** 红黑树实现 - 自顶向下** @param <T>*/
public class RedBlackTree<T extends Comparable<? super T>> {
​// 1 内部结点定义 ****************************************************************************************************private static final int BLACK = 1;private static final int RED = 0;
​private static class RedBlackNode<T> {
​RedBlackNode(T theElement) {this(theElement, null, null);}
​RedBlackNode(T theElement, RedBlackNode<T> lt, RedBlackNode<T> rt) {element = theElement;left = lt;right = rt;color = RedBlackTree.BLACK;}
​T element;RedBlackNode<T> left;RedBlackNode<T> right;int color;}
​
​// 2 核心结构定义 ****************************************************************************************************
​private RedBlackNode<T> header; // 头结点,header.right 引用红黑树根结点private RedBlackNode<T> nullNode; // 空结点,空对象设计模式
​// 自顶向下红黑树不需要叔叔结点,只需要当前结点上游结点引用即可private RedBlackNode<T> current; // 当前结点private RedBlackNode<T> parent; // 父节点private RedBlackNode<T> grand; // 祖父结点private RedBlackNode<T> great; // 曾祖父结点
​
​public RedBlackTree() {nullNode = new RedBlackNode<>(null);nullNode.left = nullNode;nullNode.right = nullNode;
​header = new RedBlackNode<>(null);header.left = nullNode;header.right = nullNode;}
​private int compare(T item, RedBlackNode<T> t) {if (t == header)return 1;elsereturn item.compareTo(t.element);}
​
​// 3 核心方法区 *****************************************************************************************************
​/*** 向红黑树插入结点** @param item*/public void insert(T item) {current = parent = grand = header; // 自顶向下插入nullNode.element = item; // 保存临时数据,完成下面退出条件
​while (compare(item, current) != 0) { // 退出条件?// 向下前进一个深度great = grand;grand = parent;parent = current;current = compare(item, current) < 0 ? current.left : current.right;
​// 当遇到两个儿子都是红色结点时 执行旋转+变色动作if (current.left.color == RED && current.right.color == RED) { // 当前结点的两个儿子是红色handleReorient(item); // 执行调整}}
​if (current != nullNode) // 这就说明遍历到了最后也没有发现item结点,所以下面可以插入数据了return;
​current = new RedBlackNode<>(item, nullNode, nullNode);
​if (compare(item, parent) < 0) {parent.left = current;} else {parent.right = current;}
​/*** 插入叶子结点是红色,父节点也是红色将引发调整*/handleReorient(item);}
​public void remove(T x) {throw new UnsupportedOperationException();}
​public T findMin() {if (isEmpty())throw new UnderflowException();
​RedBlackNode<T> itr = header.right;
​while (itr.left != nullNode)itr = itr.left;
​return itr.element;}
​public T findMax() {if (isEmpty())throw new UnderflowException();
​RedBlackNode<T> itr = header.right;
​while (itr.right != nullNode)itr = itr.right;
​return itr.element;}
​public boolean contains(T x) {nullNode.element = x;current = header.right;
​for (; ; ) {if (x.compareTo(current.element) < 0)current = current.left;else if (x.compareTo(current.element) > 0)current = current.right;else if (current != nullNode)return true;elsereturn false;}}
​public boolean isEmpty() {return header.right == nullNode;}
​public void makeEmpty() {header.right = nullNode;}
​public void printTree() {if (isEmpty())System.out.println("Empty tree");elseSystem.out.print("Red Black tree: ");printTree(header.right);}
​private void printTree(RedBlackNode<T> t) {if (t != nullNode) {printTree(t.left);System.out.print(t.element + " ");printTree(t.right);}}
​
​// 4 重要方法区(变色 + 旋转) ******************************************************************************************private void handleReorient(T item) {// 执行变色动作current.color = RED;current.left.color = BLACK;current.right.color = BLACK;
​/*** 当前结点和父节点都是红色将会引发调整,调整原理如下所示:** 一字型(zig-zig) 带.表示是红色结点*            G               P*           / \             / \*         .P   S           .x .G*         / \  |            | / \*       .x   B C   -->      A B  S*        |                       |*        A                       C** 之字形(zig-zag)*           G                      x*          / \                   /   \*         .P   S     -->       .P    .G*         | \  |               / \   / \*         A .x C              A  B1  B2 S*           / \                         |*          B1  B2                       C*/if (parent.color == RED) { // 当前结点时红色 并且 父节点也是红色(祖父一定是黑色),违反红黑树规则,需要执行调整grand.color = RED; // 调整祖父为红色,因为不管是一字型还是之字形旋转后都是变为红色结点/*** 满足下面两种情况*      G       G*     /         \*    P    或     P*     \         /*      X       X*/if ((compare(item, grand) < 0) != (compare(item, parent) < 0)) { // 之字型旋转parent = rotate(item, grand); // zig-zag 格式需要完成两次旋转,这里执行第一次,传入祖父为了后边建立链/** 之字形第一次旋转*             G*            /*           x*          /*         P*/}current = rotate(item, great); // zig-zag 的第二次旋转 || 或者 zig-zig格式的一次旋转current.color = BLACK;}
​header.right.color = BLACK; // 根节点始终是黑色}
​
​private RedBlackNode<T> rotate(T item, RedBlackNode<T> parent) {if (compare(item, parent) < 0)return parent.left = compare(item, parent.left) < 0 ?rotateWithLeftChild(parent.left) :  // LLrotateWithRightChild(parent.left);  // LR (parent.left = P)elsereturn parent.right = compare(item, parent.right) < 0 ?rotateWithLeftChild(parent.right) :  // RLrotateWithRightChild(parent.right);  // RR}
​/*** 右旋转(处理LL情况)*     k2          k1*    /           / \*   k1    -->   o1  k2*  /* o1*/private RedBlackNode<T> rotateWithLeftChild(RedBlackNode<T> k2) {RedBlackNode<T> k1 = k2.left;k2.left = k1.right;k1.right = k2;return k1;}
​/*** 左旋转(处理RR情况)* k1                k2*  \               / \*   k2   -->      k1  o1*    \*     o1*/private RedBlackNode<T> rotateWithRightChild(RedBlackNode<T> k1) {RedBlackNode<T> k2 = k1.right;k1.right = k2.left;k2.left = k1;return k2;}
​
​// 5 单元测试 *******************************************************************************************************public static void main(String[] args) {RedBlackTree<Integer> t = new RedBlackTree<>();final int NUMS = 50;final int GAP = 3;
​System.out.println("Checking... (no more output means success)");
​t.printTree();
​for (int i = GAP; i != 0; i = (i + GAP) % NUMS)t.insert(i);
​t.printTree();
​if (t.findMin() != 1 || t.findMax() != NUMS - 1)System.out.println("FindMin or FindMax error!");
​for (int i = 1; i < NUMS; i++)if (!t.contains(i))System.out.println("Find error1!");}
}
​

五 代码解释

1. RedBlackNode 节点类

private static final int BLACK = 1;
private static final int RED = 0;
​
private static class RedBlackNode<T> {
​RedBlackNode(T theElement) {this(theElement, null, null);}
​RedBlackNode(T theElement, RedBlackNode<T> lt, RedBlackNode<T> rt) {element = theElement;left = lt;right = rt;color = RedBlackTree.BLACK;}
​T element;RedBlackNode<T> left;RedBlackNode<T> right;int color;
}

每个节点包含:

  • element:存储的数据;

  • leftright:左右子节点;

  • color:颜色属性(0=RED, 1=BLACK)。

该实现使用 nullNode 作为所有空指针的替代物(空对象模式),避免空指针判断。

2. insert() 方法

public void insert(T item) {current = parent = grand = header; // 自顶向下插入nullNode.element = item; // 保存临时数据,完成下面退出条件
​while (compare(item, current) != 0) { // 退出条件?// 向下前进一个深度great = grand;grand = parent;parent = current;current = compare(item, current) < 0 ? current.left : current.right;
​// 当遇到两个儿子都是红色结点时 执行旋转+变色动作if (current.left.color == RED && current.right.color == RED) { // 当前结点的两个儿子是红色handleReorient(item); // 执行调整}}
​if (current != nullNode) // 这就说明遍历到了最后也没有发现item结点,所以下面可以插入数据了return;
​current = new RedBlackNode<>(item, nullNode, nullNode);
​if (compare(item, parent) < 0) {parent.left = current;} else {parent.right = current;}
​/*** 插入叶子结点是红色,父节点也是红色将引发调整*/handleReorient(item);
}

采用 自顶向下(Top-Down) 插入思想:

  • 每向下走一步,都提前检查是否有连续红节点;

  • 若遇到“父红+两个红儿子”,立即调用 handleReorient() 修复;

  • 插入完成后再调用一次 handleReorient(),保证平衡。

这种策略无需递归回溯,逻辑更清晰。

3. handleReorient()rotate()

private void handleReorient(T item) {// 执行变色动作current.color = RED;current.left.color = BLACK;current.right.color = BLACK;
​/*** 当前结点和父节点都是红色将会引发调整,调整原理如下所示:** 一字型(zig-zig) 带.表示是红色结点*            G               P*           / \             / \*         .P   S           .x .G*         / \  |            | / \*       .x   B C   -->      A B  S*        |                       |*        A                       C** 之字形(zig-zag)*           G                      x*          / \                   /   \*         .P   S     -->       .P    .G*         | \  |               / \   / \*         A .x C              A  B1  B2 S*           / \                         |*          B1  B2                       C*/if (parent.color == RED) { // 当前结点时红色 并且 父节点也是红色(祖父一定是黑色),违反红黑树规则,需要执行调整grand.color = RED; // 调整祖父为红色,因为不管是一字型还是之字形旋转后都是变为红色结点/*** 满足下面两种情况*      G       G*     /         \*    P    或     P*     \         /*      X       X*/if ((compare(item, grand) < 0) != (compare(item, parent) < 0)) { // 之字型旋转parent = rotate(item, grand); // zig-zag 格式需要完成两次旋转,这里执行第一次,传入祖父为了后边建立链/** 之字形第一次旋转*             G*            /*           x*          /*         P*/}current = rotate(item, great); // zig-zag 的第二次旋转 || 或者 zig-zig格式的一次旋转current.color = BLACK;}
​header.right.color = BLACK; // 根节点始终是黑色
}
​
​
private RedBlackNode<T> rotate(T item, RedBlackNode<T> parent) {if (compare(item, parent) < 0)return parent.left = compare(item, parent.left) < 0 ?rotateWithLeftChild(parent.left) :  // LLrotateWithRightChild(parent.left);  // LR (parent.left = P)elsereturn parent.right = compare(item, parent.right) < 0 ?rotateWithLeftChild(parent.right) :  // RLrotateWithRightChild(parent.right);  // RR
}
​
/*** 右旋转(处理LL情况)*     k2          k1*    /           / \*   k1    -->   o1  k2*  /* o1*/
private RedBlackNode<T> rotateWithLeftChild(RedBlackNode<T> k2) {RedBlackNode<T> k1 = k2.left;k2.left = k1.right;k1.right = k2;return k1;
}
​
/*** 左旋转(处理RR情况)* k1                k2*  \               / \*   k2   -->      k1  o1*    \*     o1*/
private RedBlackNode<T> rotateWithRightChild(RedBlackNode<T> k1) {RedBlackNode<T> k2 = k1.right;k1.right = k2.left;k2.left = k1;return k2;
}

该方法是红黑树插入的核心:

  • 先变色:当前节点红、左右儿子黑;

  • 若父节点红 → 触发旋转调整;

  • 根据插入位置判断是 “之字型(Zig-Zag)” 还是 “一字型(Zig-Zig)”;

  • rotate() 根据方向选择合适旋转(LL/LR/RL/RR),并返回新子树根;

  • 最后根节点染黑。

通过这套机制,红黑树始终保持红黑性质。

六 红黑树总结

红黑树作为一种高效的自平衡搜索树,在理论与工程中都极具重要性。与 AVL 树相比,红黑树牺牲了一部分“严格平衡性”,但换来了 更少的旋转次数和更高的插入删除性能

对比项红黑树AVL树
平衡性较弱(近似平衡)严格平衡
查找性能略逊一筹最优
插入性能更高(少旋转)较低
删除性能更高较复杂
应用场景通用集合结构、语言标准库实时搜索或频繁查找场景

红黑树是 算法工程化的典范:它用极小的实现代价,保证了平衡查找树的性能稳定性。通过颜色和局部旋转的协同,使得树在动态操作下依旧保持高效结构。

http://www.dtcms.com/a/484340.html

相关文章:

  • 如何知道一个网站是用什么做的网站稳定期的推广
  • 用二级域名做网站推介网app
  • 【Linux内核】Linux 内核开发模式演变:从硬编码到设备树驱动配置
  • 【Linux】Linux进程概念(一)
  • 怎么给网站做广告专做运动品牌的网站
  • 宝塔建设的网站火车头发布失败湖南建筑行业
  • 做网站哪个好湖南省建设工程施工合同示范文本
  • 网站做成响应式的有什么弊端简单安卓app开发
  • 网站开发和移动开发购买帝国cms做网站代理
  • 做個app网站价格网站平台建设公司经营范围
  • linux网站建设技术指南 pdf注册岩土工程师
  • 网站建设所需人力网站制作公司 云南
  • 计算机中浮点数的存储
  • 电子电气架构 --- 车载操作系统Android
  • 网站设计苏州dedecms后台程序已经安装完了怎么把自己的网站加进去?
  • asp网站后台源码企业办公软件排名
  • 上海网站 备案网上学编程的有哪些比较好的网站
  • 推荐几个做网站比较好的公司网站开发与管理实验五
  • 青海省建设厅网站怎么搭建一个博客网站
  • 庆元建设局网站网站建设预计资金投入
  • 网站开发前的准备巢湖网站开发
  • 北京网站定制建设logo素材大图
  • 哪个基层司法所网站做的比较好求个网站你懂我意思是
  • 重庆建设教育协会网站app下载平台哪个好
  • 高校思政网站建设意义wordpress可以接广告吗
  • TikTok推荐算法快速解析
  • 开源网站程序怎样下一本wordpress
  • 北京 手机网站建设程序员为什么不敢创业做网站
  • 新余网站建设找谁做小程序推广app
  • RoboTwin 2.0 部署DexVLA模型记录