数据结构:红黑树的插入
上一次我们介绍了红黑树的基本特性,和根据key查询节点的方法。这一次我们介绍如何插入一个新的节点。在我们对一颗红黑树进行插入或删除节点后可能会破坏这颗红黑树,使它变成一颗普通的二叉树。这个时候我们就需要通过一些操作使它重新变成一颗红黑树,这个操作就是旋转。
旋转分为左旋转和右旋转,下面我们分别介绍什么是左旋转,右旋转:
左旋转是一组操作的集合,对某个节点左旋转就是将该节点的右节点设置为它的父节点,该节点将变成左节点,同时将该节点的右节点的左节点设为该节点的右节点。说着比较拗口,我们结合下面的图形和代码实现过一遍就明白了:
private void leftRotation(Node h){
Node m = h.rightNode;
// 1. 将 k 节点设置为 h 的右节点
h.rightNode = m.leftNode;
if(null != m.leftNode){
m.leftNode.parentNode = h;
}
// 2. 将 h 的父节点 赋给 m 的父节点,之后分 3 种情况讨论
m.parentNode = h.parentNode;
if(null == m.parentNode){ // I: 说明 h 原来是根节点,现在将 m 设置为新的根节点
root = m;
}else{
if(h.key.compareTo(h.parentNode.key) < 0){ // II: 说明 h 原来是它父节点的左孩子,现在将 m 设置为新的左孩子
h.parentNode.leftNode = m;
}else {
h.parentNode.rightNode = m; // III: 说明 h 原来是它父节点的右孩子,现在将 m 设置为新的右孩子
}
}
// 3. 将 h 挂靠在 m 的左孩子上
m.leftNode = h;
h.parentNode = m;
}
右旋转就是左旋转的逆向操作,对某个节点进行右旋转就是将该节点的左节点设置为它的父节点,该节点将变成右节点,同时将左节点的右节点设置为该节点的左节点。同样看图和代码实现:
private void rightRotation(Node m){
Node h = m.leftNode;
// 1. 将 k 设置为 m 的左节点
m.leftNode = h.rightNode;
if(null != h.rightNode){
h.rightNode.parentNode = m;
}
// 2. 将 m 的父节点 赋给 h 的父节点,之后分 3 种情况讨论
h.parentNode = m.parentNode;
if(null == m.parentNode){ // I: 说明 m 原来是根节点,现在将 h 设置为新的根节点
root = h;
}else{
if(m.key.compareTo(m.parentNode.key) < 0){ // II: 说明 m 原来是它父节点的左孩子,现在将 h 设置为新的左孩子
m.parentNode.leftNode = h;
}else {
m.parentNode.rightNode = h; // III: 说明 m 原来是它父节点的右孩子,现在将 h 设置为新的右孩子
}
}
// 3. 将 m 挂靠在 h 的右孩子上
h.rightNode = m;
m.parentNode = h;
}
插入一个节点分为以下几个步骤:
- 新建一个红色节点(为什么是红色节点呢?看下面的操作我们就明白,用红色节点操作更简单)
- 和普通的二叉排序树一样,找到插入的位置插入
- 通过旋转,重新着色操作使它重新成为一颗红黑树
我们发现关键在于第三步,第三步比较复杂可以分为两种大的情况讨论,
一 : 父节点是左子树,对于这种情况又可以分为以下3中小的情况考虑
二 : 父节点是右子树,对于这种情况也可以分为以下3中小的情况考虑
由于知乎上文字不能有颜色的区分,所以为了清除的表示整个过程,上面都是截图。
接下来我们通过代码来实现前面讲解的流程:
/**
* 插入新的节点,如果指定的key已经存在,则更新原来的值
* @param key
* @param value
*/
public void put(K key, V value) {
Node newNode = new Node(key, value, null, null, RED, null);
if(null == root){
root = newNode;
root.color = BLACK;
}else{
upsert(null, root, newNode);
}
}
private void upsert(Node parent, Node current, Node newNode){
if(null == current){
if(newNode.key.compareTo(parent.key) > 0){
parent.rightNode = newNode;
}else{
parent.leftNode = newNode;
}
newNode.parentNode = parent;
upsertFix(newNode); // 插入新节点后 对红黑树进行修复
}else{
int result = newNode.key.compareTo(current.key);
if(result == 0){
current.value = newNode.value;
}
parent = current;
if(result > 0){
upsert(parent, parent.rightNode, newNode);
}
if(result < 0){
upsert(parent, parent.leftNode, newNode);
}
}
}
private void upsertFix(Node newNode){
Node parent = newNode.parentNode;
if(RED == parent.color){ // 父节点如果是黑节点 则不需要处理
Node grandfather = parent.parentNode;
if(parent == grandfather.leftNode){ // 1. 父节点原来是 左节点
Node uncle = grandfather.rightNode;
if((null != uncle) && (RED == uncle.color)){ // case 3: 叔叔节点是红色
uncleRedFix(newNode);
}else{ // 叔叔节点为 NULL 或者 是黑色节点
if(newNode.key.compareTo(parent.key) < 0){ // case 1: 叔叔节点是黑色,插入到左子树中
leftNodeFix(grandfather, parent);
}else{ // case 2: 叔叔节点是黑色,插入到右子树中
leftRotation(parent);
leftNodeFix(grandfather, newNode); //我们将 parent 节点作为“新插入的节点”,这样 真正新插入的节点 newNode 就是父节点
}
}
}else{ // 1. 父节点原来是 右节点
Node uncle = grandfather.leftNode;
if((null != uncle) && (RED == uncle.color)){ // case 3: 叔叔节点是红色
uncleRedFix(newNode);
}else{ // 叔叔节点为 NULL 或者 是黑色节点
if(newNode.key.compareTo(parent.key) > 0){ // case 1: 叔叔节点是黑色,插入到右子树中
rightNodeFix(grandfather, parent);
}else{ // case 2: 叔叔节点是黑色,插入到左子树中
rightRotation(parent);
rightNodeFix(grandfather, newNode); //我们将 parent 节点作为“新插入的节点”,这样 真正新插入的节点 newNode 就是父节点
}
}
}
}
}
/**
* 处理 父节点原来是 左节点 的 case 1 的情况: 叔叔节点是黑色,插入到左子树中
* @param grandfather
* @param parent
*/
private void leftNodeFix(Node grandfather, Node parent){
parent.color = BLACK;
grandfather.color = RED;
rightRotation(grandfather);
}
/**
* 处理 父节点原来是 右节点 的 case 1 的情况: 叔叔节点是黑色,插入到右子树中
* @param grandfather
* @param parent
*/
private void rightNodeFix(Node grandfather, Node parent){
parent.color = BLACK;
grandfather.color = RED;
leftRotation(grandfather);
}
/**
* 处理 case 3: 叔叔节点是红色
* @param newNode
*/
private void uncleRedFix(Node newNode){
Node parent = newNode.parentNode;
if((null != parent) && (RED == parent.color)){
Node grandfather = parent.parentNode;
Node uncle = grandfather.leftNode;
if(parent == grandfather.leftNode){
uncle = grandfather.rightNode;
}
parent.color = BLACK;
uncle.color = BLACK;
if(root != grandfather){
grandfather.color = RED;
upsertFix(grandfather);
}
}
}
我们可以结合前面的图形,参照代码的注释来阅读代码。下面咱插入一些测试数据
public static void main(String[] args) {
RedBlackTree<Integer, String> bst = new RedBlackTree<Integer, String>();
bst.put(100, "v100");
bst.put(50, "v50");
bst.put(150, "v150");
bst.put(20, "v20");
bst.put(85, "v85");
bst.put(10, "v10");
bst.put(15, "a15");
bst.put(75, "v75");
bst.put(95, "v95");
bst.put(65, "v65");
bst.put(76, "v76");
bst.put(60, "v60");
bst.put(66, "v66");
bst.put(61, "v61");
System.out.println(bst.getRoot());
}
我们可以先自己在纸上画一遍,看看画出来的结果是否和打印的结果一样。
好红黑树的插入我们就先介绍到这,下一次我们将介绍红黑树的删除,删除的过程和普通的二叉排序树一样,只不过删除后也需要对红黑树进行修复,好吧赶火车回家喽!
编辑于 2016-11-02 22:24