数据结构系列(5)之 红黑树

本文将主要讲述平衡二叉树中的红黑树,红黑树是一种我们经常使用的树,相较于 AVL 树他无论是增加还是删除节点,其结构的变化都能控制在常树次;在 JDK 中的 TreeMap 同样也是使用红黑树实现的;

一、结构概述

红黑树是在AVL 树平衡条件的基础上,进一步放宽条件,从而使得红黑树在动态变化的时候,其结构的变化在常数次;其标准大致可以表示为; 任一节点左、右子树的高度,相差不得超过两倍。

同他的名字,红黑树的节点是有颜色的,如图所示:

其性质如下:

  • 树根始终为黑色
  • 外部节点均为黑色(图中的 leaf 节点,通常在表述的时候会省略)
  • 红色节点的孩子节点必为黑色(通常插入的节点为红色)
  • 从任一外部节点到根节点的沿途,黑节点的数目相等

(2,4)B 树,如果将红黑树的红色节点和其父节点合并为一个超级节点,则其结构和(2,4)B 树 的结构完全一样,所以在学习红黑树的时候,可以对照 B 的转换方法,帮助理解;

public class RBTree<T extends Comparable<T>> {
  private static final boolean RED = false;
  private static final boolean BLACK = true;
  private RBTNode<T> root;  // 根结点

  public class RBTNode<T extends Comparable<T>> {
    boolean color;      // 颜色
    T key;              // 关键字(键值)
    RBTNode<T> left;    // 左孩子
    RBTNode<T> right;   // 右孩子
    RBTNode<T> parent;  // 父结点
  }
}

二、红重平衡

因为通常情况下插入的节点会标记为红色,那么就有可能导致两个红色的节点练成父子,所以需要通过一下方法修复;

1. RR-1

如图所示,如果插入的红色节点和父节点一起组成了3个关键码的超级节点,在 B 树的角度上则只需要重新标记颜色,使黑色节点位于中间即可;表现在红黑树中就需要进行旋转操作,如图:

双红节点同边:

  • 如图中,两个红色节点都是左孩子或者都是右孩子时
  • 只需要旋转其祖父节点
  • 然后祖父节点和其父节点反转颜色即可

双红节点异边:

  • 如图中, 两个红色节点是异边的时候
  • 首先需要旋转父节点,转为上面同边的情况,在旋转其祖父节点;
  • 然后祖父节点和其父节点反转颜色即可

其实在这里如果忽略颜色,其旋转操作就可 AVL 树是一样的;那么在实现的时候同样可以使用之前讲过的 3+4 重构

1. RR-2

如图所示,如果红色节点上移后,同其父节点组成的超级节点是4个关键码,则发生了上溢,需要将其分裂为两个节点;但此时表现在红黑树上其结构并未发生变化,所以只需要重新染色即可;

  • 如图所示,如果父亲是红色节点,同时叔父也是红色节点,此时就构成了4个关键码的超级节点
  • 这个时候只需要将父亲节点和叔父节点变成黑色,祖父节点变红即可;

如图所示:

三、黑重平衡

当删除黑节点的时候,会使得该分支的黑高度降低,从而不满足每个分支的黑高度相等,所以下面将删除黑节点分成几种情况进行修复;

1. BB-1

当删除的节点是黑色节点,且其兄弟节点是黑色,同时有红孩子的时候;如果转化为 (2,4)B 树

如图所示:

  • 图中的绿色节点,表示颜色任意;
  • 如果将以情况看作是 B 树,则相当于删除 x 节点后,使得该节点关键码不足,发生下溢;于是通过旋转父节点向其兄弟节点借一个关键码;
  • 对于红黑树则是,旋转父节点,同时相同位置的颜色保持不变;

2. BB-2-R

如果父节点是红色,有黑色兄弟节点,并且没有红色孩子:

转化为 (2,4)B 树

如图所示,

  • 此时相当于删除黑色节点,使得该节点的关键码不足,发生下溢
  • 同时兄弟节点没有红色孩子,没办法借出,所以只能从父节点以一个关键码合并两个孩子节点;
  • 同时父节点为红色,借出一个关键码后,其黑高度不变;
  • 在红黑树中则为删除的位置由父节点代替,并且兄弟姐弟节点变红;整体结构不变;

3. BB-2-B

如果父节点是黑色,有黑色兄弟节点,并且没有红色孩子:

转化为 (2,4)B 树

如图所示:

  • 整体情况和 bb-2-r 一样,但是其父节点为黑色;
  • 也就是在父节点借出一个节点后,父节点会继续发生下溢;并根据情况再次判断调整;但是下溢整体不会超过O(logn) 次;

4. BB-3

如果父节点是黑色,有红色兄弟节点:

转化为 (2,4)B 树

如图所示:

  • 如果有黑色父节点,且兄弟节点为红色;
  • 则相当于可以从兄弟节点借一个节点,同时结构不会改变;
  • 对于红黑树而言,相当于旋转父节点,同时父节点和兄弟节点变色;

三、实现

1. 查找

private RBTNode<T> search(RBTNode<T> x, T key) {
  if (x == null) return x;
  int cmp = key.compareTo(x.key);
  if (cmp < 0)
    return search(x.left, key);
  else if (cmp > 0)
    return search(x.right, key);
  else
    return x;
}

2. 插入

public void insert(T key) {
  insert(new RBTNode<T>(key, BLACK, null, null, null));
}

private void insert(RBTNode<T> node) {
  int cmp;
  RBTNode<T> y = null;
  RBTNode<T> x = this.root;

  // 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。
  while (x != null) {
    y = x;
    cmp = node.key.compareTo(x.key);
    if (cmp < 0)
      x = x.left;
    else
      x = x.right;
  }

  node.parent = y;
  if (y != null) {
    cmp = node.key.compareTo(y.key);
    if (cmp < 0)
      y.left = node;
    else
      y.right = node;
  } else {
    this.root = node;
  }

  // 2. 设置节点的颜色为红色
  node.color = RED;

  // 3. 将它重新修正为一颗二叉查找树
  insertFixUp(node);
}

private void insertFixUp(RBTNode<T> node) {
  RBTNode<T> parent, gparent;

  // 若“父节点存在,并且父节点的颜色是红色”
  while (((parent = parentOf(node)) != null) && isRed(parent)) {
    gparent = parentOf(parent);

    //若“父节点”是“祖父节点的左孩子”
    if (parent == gparent.left) {
      // Case 1条件:叔叔节点是红色
      RBTNode<T> uncle = gparent.right;
      if ((uncle != null) && isRed(uncle)) {
        setBlack(uncle);
        setBlack(parent);
        setRed(gparent);
        node = gparent;
        continue;
      }

      // Case 2条件:叔叔是黑色,且当前节点是右孩子
      if (parent.right == node) {
        RBTNode<T> tmp;
        leftRotate(parent);
        tmp = parent;
        parent = node;
        node = tmp;
      }

      // Case 3条件:叔叔是黑色,且当前节点是左孩子。
      setBlack(parent);
      setRed(gparent);
      rightRotate(gparent);
    } else {  //若“z的父节点”是“z的祖父节点的右孩子”
      // Case 1条件:叔叔节点是红色
      RBTNode<T> uncle = gparent.left;
      if ((uncle != null) && isRed(uncle)) {
        setBlack(uncle);
        setBlack(parent);
        setRed(gparent);
        node = gparent;
        continue;
      }

      // Case 2条件:叔叔是黑色,且当前节点是左孩子
      if (parent.left == node) {
        RBTNode<T> tmp;
        rightRotate(parent);
        tmp = parent;
        parent = node;
        node = tmp;
      }

      // Case 3条件:叔叔是黑色,且当前节点是右孩子。
      setBlack(parent);
      setRed(gparent);
      leftRotate(gparent);
    }
  }
}

/*
 * 对红黑树的节点(x)进行左旋转
 *
 * 左旋示意图(对节点x进行左旋):
 *      px                  px
 *     /                   /
 *    x                   y
 *   /  \    --(左旋)-.   / \        #
 *  lx   y              x  ry
 *   /   \             /   *  ly   ry           lx  ly
 *
 *
 */
private void leftRotate(RBTNode<T> x) {
  // 设置x的右孩子为y
  RBTNode<T> y = x.right;

  // 将 “y的左孩子” 设为 “x的右孩子”;
  // 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
  x.right = y.left;
  if (y.left != null)
    y.left.parent = x;

  // 将 “x的父亲” 设为 “y的父亲”
  y.parent = x.parent;

  if (x.parent == null) {
    this.root = y;      // 如果 “x的父亲” 是空节点,则将y设为根节点
  } else {
    if (x.parent.left == x)
      x.parent.left = y;  // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
    else
      x.parent.right = y;  // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
  }

  // 将 “x” 设为 “y的左孩子”
  y.left = x;
  // 将 “x的父节点” 设为 “y”
  x.parent = y;
}

/*
 * 对红黑树的节点(y)进行右旋转
 *
 * 右旋示意图(对节点y进行左旋):
 *         py                      py
 *         /                       /
 *        y                       x
 *       /  \    --(右旋)-.      /  \           #
 *      x   ry                 lx   y
 *     / \                    / \           #
 *    lx  rx                 rx  ry
 *
 */
private void rightRotate(RBTNode<T> y) {
  // 设置x是当前节点的左孩子。
  RBTNode<T> x = y.left;

  // 将 “x的右孩子” 设为 “y的左孩子”;
  // 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
  y.left = x.right;
  if (x.right != null)
    x.right.parent = y;

  // 将 “y的父亲” 设为 “x的父亲”
  x.parent = y.parent;

  if (y.parent == null) {

    this.root = x;      // 如果 “y的父亲” 是空节点,则将x设为根节点
  } else {
    if (y == y.parent.right)
      y.parent.right = x;  // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
    else
      y.parent.left = x;  // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
  }

  // 将 “y” 设为 “x的右孩子”
  x.right = y;

  // 将 “y的父节点” 设为 “x”
  y.parent = x;
}

3. 删除

public void remove(T key) {
  RBTNode<T> node;
  if ((node = search(root, key)) != null)
    remove(node);
}

private void remove(RBTNode<T> node) {
  RBTNode<T> child, parent;
  boolean color;

  // 被删除节点的"左右孩子都不为空"的情况。
  if ((node.left != null) && (node.right != null)) {
    // 被删节点的后继节点。(称为"取代节点")
    // 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。
    RBTNode<T> replace = node;

    // 获取后继节点
    replace = replace.right;
    while (replace.left != null)
      replace = replace.left;

    // "node节点"不是根节点(只有根节点不存在父节点)
    if (parentOf(node) != null) {
      if (parentOf(node).left == node)
        parentOf(node).left = replace;
      else
        parentOf(node).right = replace;
    } else {
      // "node节点"是根节点,更新根节点。
      this.root = replace;
    }

    // child是"取代节点"的右孩子,也是需要"调整的节点"。
    // "取代节点"肯定不存在左孩子!因为它是一个后继节点。
    child = replace.right;
    parent = parentOf(replace);
    // 保存"取代节点"的颜色
    color = colorOf(replace);

    // "被删除节点"是"它的后继节点的父节点"
    if (parent == node) {
      parent = replace;
    } else {
      // child不为空
      if (child != null)
        setParent(child, parent);
      parent.left = child;

      replace.right = node.right;
      setParent(node.right, replace);
    }

    replace.parent = node.parent;
    replace.color = node.color;
    replace.left = node.left;
    node.left.parent = replace;

    if (color == BLACK)
      removeFixUp(child, parent);

    node = null;
    return;
  }

  if (node.left != null) {
    child = node.left;
  } else {
    child = node.right;
  }

  parent = node.parent;
  // 保存"取代节点"的颜色
  color = node.color;

  if (child != null)
    child.parent = parent;

  // "node节点"不是根节点
  if (parent != null) {
    if (parent.left == node)
      parent.left = child;
    else
      parent.right = child;
  } else {
    this.root = child;
  }

  if (color == BLACK)
    removeFixUp(child, parent);
  node = null;
}

private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) {
  RBTNode<T> other;

  while ((node == null || isBlack(node)) && (node != this.root)) {
    if (parent.left == node) {
      other = parent.right;
      if (isRed(other)) {
        // Case 1: x的兄弟w是红色的
        setBlack(other);
        setRed(parent);
        leftRotate(parent);
        other = parent.right;
      }

      if ((other.left == null || isBlack(other.left)) &&
          (other.right == null || isBlack(other.right))) {
        // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的
        setRed(other);
        node = parent;
        parent = parentOf(node);
      } else {

        if (other.right == null || isBlack(other.right)) {
          // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。
          setBlack(other.left);
          setRed(other);
          rightRotate(other);
          other = parent.right;
        }
        // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
        setColor(other, colorOf(parent));
        setBlack(parent);
        setBlack(other.right);
        leftRotate(parent);
        node = this.root;
        break;
      }
    } else {

      other = parent.left;
      if (isRed(other)) {
        // Case 1: x的兄弟w是红色的
        setBlack(other);
        setRed(parent);
        rightRotate(parent);
        other = parent.left;
      }

      if ((other.left == null || isBlack(other.left)) &&
          (other.right == null || isBlack(other.right))) {
        // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的
        setRed(other);
        node = parent;
        parent = parentOf(node);
      } else {

        if (other.left == null || isBlack(other.left)) {
          // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。
          setBlack(other.right);
          setRed(other);
          leftRotate(other);
          other = parent.left;
        }

        // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
        setColor(other, colorOf(parent));
        setBlack(parent);
        setBlack(other.left);
        rightRotate(parent);
        node = this.root;
        break;
      }
    }
  }

  if (node != null) setBlack(node);
}

总结

  • 对于红黑树增加和删除的情况特别的多,不是特别好理解,所以这一部分最好对应 B 树,上溢和下溢的修复

原文地址:https://www.cnblogs.com/sanzao/p/10509626.html

时间: 2024-10-14 02:06:12

数据结构系列(5)之 红黑树的相关文章

【C/C++学院】0828-STL入门与简介/STL容器概念/容器迭代器仿函数算法STL概念例子/栈队列双端队列优先队列/数据结构堆的概念/红黑树容器

STL入门与简介 #include<iostream> #include <vector>//容器 #include<array>//数组 #include <algorithm>//算法 using namespace std; //实现一个类模板,专门实现打印的功能 template<class T> //类模板实现了方法 class myvectorprint { public: void operator ()(const T &

jdk1.8源码解析:HashMap底层数据结构之链表转红黑树的具体时机

前言 本文从三个部分去探究HashMap的链表转红黑树的具体时机: 一.从HashMap中有关“链表转红黑树”阈值的声明: 二.[重点]解析HashMap.put(K key, V value)的源码: 三.测试: 一.从HashMap中有关“链表转红黑树”阈值的声明,简单了解HashMap的链表转红黑树的时机 在 jdk1.8 HashMap底层数据结构:散列表+链表+红黑树(图解+源码)的 “四.问题探究”中,我有稍微提到过散列表后面跟什么数据结构是怎么确定的: HashMap中有关“链表转

算法系列笔记4(红黑树)

随机构建的二叉查找树的高度期望值为O(lgn),并不代表所有的二叉查找树的高度都为O(lgn).但是对于有些二叉查找树的变形来说,动态集合各基本操作的性能却总是很好的,如红黑树.B树.平衡二叉树(AVL树).跳跃表(确切的说不是树,或多或少有树的结构).treaps(树堆)等.这里我们讲解红黑树. 平衡的意思就是完成动态数据集的操作(minimum.maximum.search.predecessor(前驱).successor(后继).insert及delete)在O(lgn)时间内完成. 定

研磨数据结构与算法-14红黑树

红黑树: public class RBTree { private final Node NIL = new Node(null,null,null,Color.BLACK,-1); private Node root; public RBTree() { root = NIL; } public RBTree(Node  root) { this.root = root; } //插入节点 public void rbInsert(Node node) { Node previous = N

数据结构Java版之红黑树(八)

红黑树是一种自动平衡的二叉查找树,因为存在红黑规则,所以有效的防止了二叉树退化成了链表,且查找和删除的速度都很快,时间复杂度为log(n). 什么是红黑规则? 1.根节点必须是黑色的. 2.节点颜色要么是红要么是黑. 3.树的每一个分叉存在相同黑色节点. 4.不允许存在两个连续的红色节点. 为不断适应红黑规则,在写程序中如何调整? 1.旋转 ---单旋转 外侧节点单旋转.外侧节点指的是左子树的左孩子节点,右子树的右孩子节点. ---双旋转 内侧节点双旋转.内侧节点指的是左子树的右孩子节点,右子树

Java数据结构和算法(八)--红黑树与2-3树

红黑树规则: 1.根节点与叶节点都是黑色节点 2.每个红色节点的两个子节点都是黑色节点,反之,不做要求,换句话说就是不能有连续两个红色节点 3.从根节点到所有叶子节点上的黑色节点数量是相同的 一般对红黑树的讲述都是先给出这样的定义,这样想对不太容易理解的,而在算法4一书中,直接跳过这些规则,而讲述了红黑树与2-3树的等价性 如果我们先了解2-3树,理解了红黑树与2-3树之间的关系,回过头就会发现红黑树不难 2-3树: 2-3树满足二分搜索树的基本性质,但是不是二叉树 2-3树节点可以存放一个元素

HashMap底层数据结构之链表转红黑树的具体时机

前言 本文从三个部分去探究HashMap的链表转红黑树的具体时机: 1.从HashMap中有关"链表转红黑树"阈值的声明:2.[重点]解析HashMap.put(K key, V value)的源码:3.测试: 一.从HashMap中有关"链表转红黑树"阈值的声明,简单了解HashMap的链表转红黑树的时机 HashMap中有关"链表转红黑树"阈值的声明: /** * 使用红黑树(而不是链表)来存放元素.当向至少具有这么多节点的链表再添加元素时,

数据结构与算法简记--红黑树

红黑树 平衡二叉树 定义:二叉树中任意一个节点的左右子树的高度相差不能大于 1. 完全二叉树.满二叉树其实都是平衡二叉树,非完全二叉树也有可能是平衡二叉树. 平衡二叉查找树 任何节点的左右子树高度相差不超过 1,是一种高度平衡的二叉查找树. 符合二叉查找树的特点:左子节点小于父节点,右子节点大于父节点. 发明的初衷是:解决普通二叉查找树在频繁的插入.删除等动态更新的情况下,出现时间复杂度退化的问题. “平衡”的意思,其实就是让整棵树左右看起来比较“对称”.比较“平衡”,不要出现左子树很高.右子树

红黑树(一)之 原理和算法详细介绍---转帖

目录1 红黑树的介绍2 红黑树的应用3 红黑树的时间复杂度和相关证明4 红黑树的基本操作(一) 左旋和右旋5 红黑树的基本操作(二) 添加6 红黑树的基本操作(三) 删除 作者:Sky Wang    于 2013-08-08 概述:R-B Tree,又称为"红黑树".本文参考了<算法导论>中红黑树相关知识,加之自己的理解,然后以图文的形式对红黑树进行说明.本文的主要内容包括:红黑树的特性,红黑树的时间复杂度和它的证明,红黑树的左旋.右旋.插入.删除等操作. 请尊重版权,转

Java - HashTree源码解析 + 红黑树

Java提高篇(二七)-----TreeMap TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap的实现,但是为了与Java提高篇系列博文保持一致还是叫做TreeMap比较好.通过这篇博文你可以获得如下知识点: 1.红黑树的基本概念. 2.红黑树增加节点.删除节点的实现过程. 3.红黑树左旋转.右旋转的复杂过程. 4.Java 中TreeMap是如何通过put.deleteEntry两个来实现红