【红黑树】在控制台打印红黑树的插入删除操作

  • 获取红黑树节点在二维数组中的(理论)纵坐标,(即不考虑排版用到的”/“,""所占用的纵向坐标)

  • 求某节点的纵坐标,即求该节点有多少个直接或间接的父节点

  • @param node 红黑树节点

  • @return 纵坐标

*/

private int getNodeY(Node<K, V> node) {

int y = 0;

if (node != null) {

Node<K, V> p = node.parent;

if (p == null) {

return y;

}

y++;

while ((p = p.parent) != null) {

y++;

}

}

return y;

}

/**

  • 将红黑树节点加入二维数组对应坐标中

  • @param node 红黑树节点

  • @param rbTree 二维数组

*/

private void setNodeIntoArr(Node<K, V> node, Node[][] rbTree) {

int x = getNodeX(node);

int y = getNodeY(node);

rbTree[y * 2][x] = node;//实际纵坐标不是y,而是y*2,因为纵向加入了"/“,”"

if (node.parent != null) {

if (node == node.parent.left) {

rbTree[y * 2 - 1][x] = new Node<String, String>(“/”, null, null);

} else {

rbTree[y * 2 - 1][x] = new Node<String, String>(“\”, null, null);

}

}

}

/**

  • 打印红黑树

  • 将红黑树放进二维数组中,这个二维数组的行数是红黑树的高度*2-1(*2-1的原因是给子节点加入了”/“,”\“指向),列数是红黑树的节点个数。

  • 红黑树节点在二维数组中的行(纵坐标)列(横坐标)计算:

*/

public void showRBTree() {

if(root==null){

System.out.println(“当前红黑树为空树”);

return;

}

int origHeight = getHeight(root);

int height = origHeight * 2 - 1;

int width = size;

Node<K, V>[][] rbTree = new Node[height][width];

// 将根节点加入二维数组

setNodeIntoArr(root, rbTree);

// 将根节点的左子树加入二维数组

Node<K, V> prev = prevNode(root);

while (prev != null) {

setNodeIntoArr(prev, rbTree);

prev = prevNode(prev);

}

// 将根节点的右子树加入二维数组

Node<K, V> next = nextNode(root);

while (next != null) {

setNodeIntoArr(next, rbTree);

next = nextNode(next);

}

/**

  • 遍历包含红黑树节点的二维数组,此时红黑树节点已经按照对应的坐标加入二维数组

*/

for (int i = 0; i < rbTree.length; i++) {

for (int j = 0; j < rbTree[i].length; j++) {

Node<K, V> node = rbTree[i][j];

if (node == null) {

System.out.print(“\t”);

} else {

if (node.color == RED) {

if (node.key.equals(“/”) || node.key.equals(“\”)) {

System.out.print(“\t” + ConsoleColors.BLUE + node.key + ConsoleColors.RESET);

} else {

System.out.print(“\t” + ConsoleColors.RED + node.key + ConsoleColors.RESET);

}

} else {

System.out.print(“\t” + ConsoleColors.BLACK + node.key + ConsoleColors.RESET);

}

}

}

System.out.println();

}

}

static class Node<K, V> {

K key;

V value;

Node<K, V> parent;

Node<K, V> left;

Node<K, V> right;

boolean color;

public Node(K key, V value, Node<K, V> parent) {

this.key = key;

this.value = value;

this.parent = parent;

}

}

/**

  • 左旋

  • @param node 旋转点

*/

public void rotateLeft(Node<K, V> node) {

if (node == null)

return;

Node<K, V> r = node.right;

Node<K, V> rl = r.left;

Node<K, V> p = node.parent;

if (p != null) {

if (p.left == node) {

p.left = r;

} else {

p.right = r;

}

} else {

root = r;

}

r.parent = p;

r.left = node;

node.parent = r;

node.right = rl;

if (rl != null) {

rl.parent = node;

}

}

/**

  • 右旋

  • @param node 旋转点

*/

public void rotateRight(Node<K, V> node) {

if (node == null)

return;

Node<K, V> p = node.parent;

Node<K, V> l = node.left;

Node<K, V> lr = l.right;

if (p != null) {

if (p.left == node) {

p.left = l;

} else {

p.right = l;

}

} else {

root = l;

}

l.parent = p;

l.right = node;

node.parent = l;

node.left = lr;

if (lr != null) {

lr.parent = node;

}

}

/**

  • 红黑树插入节点

  • 如果是空树的话,则插入节点作为根节点

  • 如果是非空树,则从根节点开始比较,

  • 如果比较结果小于0,则说明插入节点在比较节点的左子树中,继续比较

  • 如果比较结果大于0,则说明插入节点在比较节点的右子树中,继续比较

  • 如果比较结果等于0,则说明插入节点对应的key已存在,只需要替换value即可

  • 比较直至节点的左子树或右子树为null

  • @param key

  • @param value

  • @return 覆盖的value

*/

public V put(K key, V value) {

Node<K, V> node = root;

Node<K, V> newNode;

/**

  • 空树

*/

if (node == null) {

// 插入节点作为根节点

root = new Node<K, V>(key, value, null);

newNode = root;

size++;

}

/**

  • 非空树

*/

else {

int cmp;

Node<K, V> parent;

//从根节点开始比较

do {

parent = node;

// key的类型是K extends Comparable,即key的类型必须实现Compareable接口,且自然排序的元素是K

cmp = key.compareTo(node.key);

if (cmp < 0) {

// 如果插入的key小于当前比较节点的key,则继续和当前节点的左子节点比较

node = node.left;

} else if (cmp > 0) {

// 如果插入的key大于当前比较节点的key,则继续和当前节点的右子节点比较

node = node.right;

} else {

// 如果插入的key和当前比较节点的key相同,则覆盖对应value

V oldValue = node.value;

node.value = value;

return oldValue;

}

} while (node != null);

// 如果上面逻辑比较到叶子节点还没有发现相同key的节点,则新增节点,新增节点的父节点是当前比较节点

newNode = new Node<K, V>(key, value, parent);

// 建立当前比较节点和新增节点的联系

if (cmp > 0) {

parent.right = newNode;

} else {

parent.left = newNode;

}

}

fixAfterInsertion(newNode);

size++;

return null;

}

private boolean colorOf(Node<K, V> node) {

return node == null ? BLACK : node.color;

}

private void setColor(Node<K, V> node, boolean color) {

if (node != null) {

node.color = color;

}

}

private Node<K, V> parentOf(Node<K, V> node) {

return node == null ? null : node.parent;

}

private Node<K, V> grandOf(Node<K, V> node) {

return node == null ? null : parentOf(parentOf(node));

}

private Node<K, V> leftOf(Node<K, V> node) {

return node == null ? null : node.left;

}

private Node<K, V> rightOf(Node<K, V> node) {

return node == null ? null : node.right;

}

private void fixAfterInsertion(Node<K, V> newNode) {

newNode.color = RED;

// 只有新增节点的父节点是红色时,才需要调整

while (newNode != root && colorOf(parentOf(newNode)) == RED) {

// 判断新增节点是否有叔叔节点,右叔叔,左爸爸

if (leftOf(grandOf(newNode)) == parentOf(newNode)) {

Node<K, V> uncleNode = rightOf(grandOf(newNode));

// 没有叔叔节点,则为234树3节点添加元素

if (colorOf(uncleNode) == BLACK) {

// 非左倾结构

if (newNode != leftOf(parentOf(newNode))) {

// 转成左倾结构

rotateLeft(parentOf(newNode));

newNode = leftOf(newNode);

}

// 左倾结构

setColor(grandOf(newNode), RED);

setColor(parentOf(newNode), BLACK);

rotateRight(grandOf(newNode));

}

// 有叔叔节点,则为234树4节点添加元素

else {

setColor(parentOf(newNode), BLACK);

setColor(uncleNode, BLACK);

setColor(grandOf(newNode), RED);

newNode = grandOf(newNode);

}

}

// 判断新增节点是否有叔叔节点,左叔叔,右爸爸

else {

Node<K, V> uncleNode = leftOf(grandOf(newNode));

// 没有叔叔节点,则为234树3节点添加元素

if (colorOf(uncleNode) == BLACK) {

// 非右倾结构

if (newNode != rightOf(parentOf(newNode))) {

// 转成右倾结构

rotateRight(parentOf(newNode));

newNode = rightOf(newNode);

}

// 右倾结构

setColor(grandOf(newNode), RED);

setColor(parentOf(newNode), BLACK);

rotateLeft(grandOf(newNode));

}

// 有叔叔节点,则为234树4节点添加元素

else {

setColor(parentOf(newNode), BLACK);

setColor(uncleNode, BLACK);

setColor(grandOf(newNode), RED);

newNode = grandOf(newNode);

}

}

}

setColor(root, BLACK);

}

/**

  • 获取当前节点的前驱节点

  • @param node 当前节点

  • @return 前驱节点

*/

private Node<K, V> prevNode(Node<K, V> node) {

// 如果当前节点不存在,则不存在前驱节点

if (node == null)

return null;

// 如果当前节点有左子树,则其前驱节点是其左子树中的最大值节点

Node prev;

if ((prev = node.left) != null) {

while (prev.right != null) {

prev = prev.right;

}

return prev;

}

// 如果当前节点没有左子树,则其前驱节点是其向上递归第一次左拐时的父节点

else {

Node p = node.parent;

Node c = node;

while (p != null && p.left == c) {

c = p;

p = p.parent;

}

return p;

}

}

/**

  • 查找当前节点的后继节点

  • @param node 当前节点

  • @return 后继节点

*/

private Node<K, V> nextNode(Node<K, V> node) {

// 当前节点不存在,则不存在后继节点

if (node == null)

return null;

// 如果当前节点存在右子树,则其后继节点是其右子树中最小值节点

Node next;

if ((next = node.right) != null) {

while (next.left != null) {

next = next.left;

}

return next;

}

// 如果当前节点不存在右子树,则其后继节点是其向上递归第一次右拐时的父节点

else {

Node p = node.parent;

Node c = node;

while (p != null && p.right == c) {

c = p;

p = p.parent;

}

return p;

}

}

/**

  • 根据key删除红黑树中对应的节点

  • @param key 要删除节点对应的key

  • @return 被删除节点的value

*/

public V remove(K key) {

// 先根据key找到要删除的节点

Node<K, V> node = getNode(key);

V oldValue = null;

if (node != null) {

oldValue = node.value;

// 删除节点

deleteNode(node);

}

return oldValue;

}

/**

  • 根据key找对应节点

  • @param key 要找节点的key

  • @return 要找的节点

*/

private Node<K, V> getNode(K key) {

// 由于这里只有自然排序,所以key==null时无法使用compareTo方法

if (key != null) {

// 从红黑树根节点开始比较节点值大小

Node<K, V> x = root;

int cmp;

while (x != null) {

cmp = key.compareTo(x.key);

if (cmp < 0) {

x = x.left;

} else if (cmp > 0) {

x = x.right;

} else {

// 如果找到key相同的节点,则返回该节点

return x;

}

}

//走到此步,则说明没有找到key相同的节点,则返回null

}

return null;

}

/**

  • 删除节点

  • @param node 删除的节点

*/

private void deleteNode(Node<K, V> node) {

if (node == null) {

return;

}

// 删除节点是双子节点,则使用它的前驱或后继节点来代替它,这里模仿TreeMap,使用后继节点来代替他

if (node.left != null && node.right != null) {

Node<K, V> replace = prevNode(node);

node.key = replace.key;

node.value = replace.value;

node = replace;//实际删除后继节点,且后继节点只可能是叶子节点或单子节点,不可能是双子节点

}

// 删除节点是单子节点

if ((node.left != null && node.right == null) || (node.left == null && node.right != null)) {

Node<K, V> replace;

if (node.left != null) {

replace = node.left;

} else {

replace = node.right;

}

node.key = replace.key;

node.value = replace.value;

node = replace;//实际删除单子节点的唯一子节点,且唯一子节点只能是叶子节点

}

// 删除节点是叶子节点

if (node.left == null && node.right == null) {

Node<K, V> p = node.parent;

if (p == null) {//如果叶子节点没有父节点,则说明该节点就是根节点,删除根节点后,不需要任何调整,因为已经是空树了

root = null;

size–;

return;

}

if (colorOf(node) == BLACK) {

fixAfterDeletion(node);

}

//解除叶子节点和其父节点的双向联系,方便叶子节点被垃圾回收

if (p.left == node) {

p.left = null;

} else {

p.right = null;

}

node.parent = null;

}

}

/**

  • 删除节点后,调整红黑树

  • 如果被删除的节点是红色的,则直接删除,无需调整

  • 如果被删除的节点是黑色,

  • 被删除节点是其父节点的左孩子
    
  •     判断其兄弟节点是否为红色
    
  •        若为红色,则为假兄弟节点,需要将假兄弟节点变黑,父节点变红,绕父节点左旋,而后得到真兄弟节点
    
  •        若为黑色,则为真兄弟节点
    
  •    判断其兄弟节点有几个孩子
    
  •        若没有孩子
    
  •           则兄弟节点没的借,兄弟节点自损变红
    
  •               判断其父节点是否为红色,
    
  •                    若为红色,则变黑,结束调整
    
  •                    若为黑色,则继续将父节点当成被删除节点,从头开始循环,直到父节点为root或者父节点为黑色,结束循环
    
  •        若有孩子
    
  •          则判断兄弟节点右孩子是否为黑色
    
  •             若为黑色,则将其兄弟节点变红,其兄弟右孩子变黑,绕兄弟节点右旋,重新获得兄弟节点
    
  •             若为红色,则无需调整
    
  •          其兄弟节点变为父节点颜色
    
  •          其父节点变为黑色
    
  •          其兄弟右孩子变为黑色
    
  •          绕其父节点左旋
    
  • @param node 将要被节点

*/

private void fixAfterDeletion(Node<K, V> node) {

while(node!=root&&colorOf(node)==BLACK){

if (node==leftOf(parentOf(node))){

Node<K,V> brother = rightOf(parentOf(node));

if (colorOf(brother)==RED){

setColor(brother,BLACK);

setColor(parentOf(node),RED);

rotateLeft(parentOf(node));

brother = rightOf(parentOf(node));

}

if (colorOf(leftOf(brother))==BLACK&&colorOf(rightOf(brother))==BLACK){

setColor(brother,RED);

node = parentOf(node);

} else {

if (colorOf(rightOf(brother))==BLACK){

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
de)==BLACK){

if (node==leftOf(parentOf(node))){

Node<K,V> brother = rightOf(parentOf(node));

if (colorOf(brother)==RED){

setColor(brother,BLACK);

setColor(parentOf(node),RED);

rotateLeft(parentOf(node));

brother = rightOf(parentOf(node));

}

if (colorOf(leftOf(brother))==BLACK&&colorOf(rightOf(brother))==BLACK){

setColor(brother,RED);

node = parentOf(node);

} else {

if (colorOf(rightOf(brother))==BLACK){

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-oAtUOAR1-1715795266137)]

[外链图片转存中…(img-qEWt73KE-1715795266137)]

[外链图片转存中…(img-YCsE32O3-1715795266137)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值