基于数学规则的红黑树实现之路(死胡同)

15 阅读19分钟

本以为自己想到了对红黑树删除方法更好的设计路线,结果一路下来最终得到的结果居然是修复不了。估计当初作者最开始也是走的是我这个路线以爷爷节点为锚点进入失衡调整,然后在进行头节点的特殊情况处理时发现这个情况根本处理不了。最终选择了以父节点为锚点进行失衡调整。在经过这几天对红黑树的长期深度思考过程中,虽然优化失败了,不过还是有很多收获。至少知道了作者为什么是以父节点为锚点进行失衡调整而不是像我一样在remove内就把父节点能解决的失衡调整全部调整好以后返回。然后以爷爷节点为锚点去设计,也懂得了以数学公理进行编程设计而不是防御性编码,虽然现在我还没有真正用数学定理实现过任何工程化的落地代码,但是红黑树的作者已经证明过了。DeepSeek说红黑树的源码是对节点颜色进行封装,这个封装的本质其实是自欺欺人的设计而已。因为对失衡节点进行旋转调整时,不可能对空的黑节点进行操作。虽然没看过源码但是我断定源码也一定是按红黑树的数学公理进行的编码,黑色的空节点任何人都不能对她进行任何操作,更重要的是我对我自己有了更深刻的认知,尽管我现在只是学了2个多月的编程小白,并且还没有正式入行。但是我在数据结构方面达到的高度就单纯以速度来看,应该没有多少人能跟我比肩的。 代码还是贴出来吧,万一被哪个大佬看到了收做小弟也不错。 说一下不可能闭环的原因: 因为我的失衡锚点是以爷爷节点为依据所以当头节点失衡并且当兄弟节点为红时,此时根据红黑树公理,因为被删的节点为黑色,此时兄弟节点为红就必定会有双黑子节点。而双黑子节点又可能有红子节点。此时通过旋转以后的黑侄子会被挂到原根节点并变红,需要向下传递红失衡,哪怕再进行一次旋转也依然会向下传递红冲突,最终解决不了红冲突。

补充:好像有一条看似不可能的路,当我旋转完以后把兄弟节点变红时,再判断她是否有红子,如果有就直接把这个兄弟的红子传进我已经工程化落地的put方法,让这个put来进行兜底。

package test;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.zip.CRC32;
import java.security.cert.PKIXReason;
import java.util.*;
public class MyRBTTest {

public static void main(String[] args) {
    System.out.println("=================================================");
    System.out.println("  红黑树插入操作工业级压力测试");
    System.out.println("=================================================\n");
    
    boolean allPassed = true;
    
    // 测试1:基本功能测试
    allPassed &= testBasicOperations();
    
    // 测试2:顺序插入测试
    allPassed &= testSequentialInsertion();
    
    // 测试3:随机插入测试
    allPassed &= testRandomInsertion();
    
    // 测试4:重复键测试
    allPassed &= testDuplicateKeys();
    
    // 测试5:大规模数据压力测试
    allPassed &= testMassiveInsertion();
    
    // 测试6:特殊场景测试
    allPassed &= testSpecialScenarios();
    
    if (allPassed) {
        System.out.println("\n=================================================");
        System.out.println("  ✅ 所有插入压力测试通过!");
        System.out.println("=================================================");
    } else {
        System.out.println("\n=================================================");
        System.out.println("  ❌ 测试失败");
        System.out.println("=================================================");
    }
}

// ===================== 测试1:基本功能测试 =====================
private static boolean testBasicOperations() {
    System.out.println("【测试1】基本功能测试");
    try {
        MyRBT<Integer, String> rbt = new MyRBT<>();
        
        // 1.1 空树测试
        assert rbt.head == null : "空树头节点应为null";
        assert rbt.size == 0 : "空树size应为0";
        
        // 1.2 单个节点插入
        rbt.put(10, "A");
        assert rbt.size == 1 : "插入后size应为1";
        assert rbt.head != null : "头节点不应为null";
        assert rbt.head.key == 10 : "头节点key应为10";
        assert rbt.head.color == Color.BLACK : "根节点必须为黑色";
        
        // 1.3 重复键测试
        rbt.put(10, "B");
        assert rbt.size == 1 : "重复插入后size应不变";
        assert "B".equals(rbt.head.value) : "值应被更新";
        
        // 1.4 多个节点插入
        for (int i = 0; i < 10; i++) {
            rbt.put(i, "Value" + i);
        }
        assert rbt.size == 10 : "插入10个不同键后size应为10";
        
        // 验证红黑树性质
        validateRBT(rbt, "基本功能测试后");
        
        System.out.println("  ✅ 基本功能测试通过");
        return true;
    } catch (AssertionError e) {
        System.out.println("  ❌ 基本功能测试失败: " + e.getMessage());
        return false;
    }
}

// ===================== 测试2:顺序插入测试 =====================
private static boolean testSequentialInsertion() {
    System.out.println("【测试2】顺序插入测试");
    try {
        // 2.1 升序插入
        MyRBT<Integer, String> rbt1 = new MyRBT<>();
        for (int i = 0; i < 1000; i++) {
            rbt1.put(i, "Asc" + i);
            if (i % 100 == 0) {
                validateRBT(rbt1, "升序插入第" + i + "个节点后");
            }
        }
        validateRBT(rbt1, "升序插入最终验证");
        
        // 2.2 降序插入
        MyRBT<Integer, String> rbt2 = new MyRBT<>();
        for (int i = 999; i >= 0; i--) {
            rbt2.put(i, "Desc" + i);
            if (i % 100 == 0) {
                validateRBT(rbt2, "降序插入第" + (999 - i) + "个节点后");
            }
        }
        validateRBT(rbt2, "降序插入最终验证");
        
        // 2.3 交替插入
        MyRBT<Integer, String> rbt3 = new MyRBT<>();
        for (int i = 0; i < 500; i++) {
            rbt3.put(i * 2, "Even" + i);
            rbt3.put(i * 2 + 1, "Odd" + i);
            if (i % 100 == 0) {
                validateRBT(rbt3, "交替插入第" + (i * 2) + "个节点后");
            }
        }
        validateRBT(rbt3, "交替插入最终验证");
        
        System.out.println("  ✅ 顺序插入测试通过");
        return true;
    } catch (AssertionError e) {
        System.out.println("  ❌ 顺序插入测试失败: " + e.getMessage());
        return false;
    }
}

// ===================== 测试3:随机插入测试 =====================
private static boolean testRandomInsertion() {
    System.out.println("【测试3】随机插入测试 (10000次随机插入)");
    try {
        MyRBT<Integer, String> rbt = new MyRBT<>();
        TreeMap<Integer, String> reference = new TreeMap<>();
        Random rand = new Random(42); // 固定种子保证可重复
        
        for (int i = 0; i < 10000; i++) {
            int key = rand.nextInt(10000);
            String value = "Rand" + key;
            
            rbt.put(key, value);
            reference.put(key, value);
            
            // 定期验证
            if (i % 1000 == 0) {
                validateRBT(rbt, "随机插入第" + i + "次后");
            }
        }
        
        // 最终验证
        validateRBT(rbt, "随机插入最终验证");
        validateAgainstReference(rbt, reference);
        
        System.out.println("  ✅ 随机插入测试通过");
        return true;
    } catch (AssertionError e) {
        System.out.println("  ❌ 随机插入测试失败: " + e.getMessage());
        return false;
    }
}

// ===================== 测试4:重复键测试 =====================
private static boolean testDuplicateKeys() {
    System.out.println("【测试4】重复键测试");
    try {
        MyRBT<Integer, String> rbt = new MyRBT<>();
        
        // 4.1 多次插入相同键
        for (int i = 0; i < 100; i++) {
            rbt.put(42, "Value" + i);
            assert rbt.size == 1 : "重复插入后size应为1";
            assert ("Value" + i).equals(rbt.head.value) : "应返回最新值";
        }
        
        // 4.2 混合重复键
        Set<Integer> keys = new HashSet<>();
        Random rand = new Random(42);
        for (int i = 0; i < 1000; i++) {
            int key = rand.nextInt(10); // 只有10个不同的键
            rbt.put(key, "K" + key);
            keys.add(key);
            assert rbt.size == keys.size() : "size应与唯一键数相同";
        }
        
        validateRBT(rbt, "重复键测试后");
        
        System.out.println("  ✅ 重复键测试通过");
        return true;
    } catch (AssertionError e) {
        System.out.println("  ❌ 重复键测试失败: " + e.getMessage());
        return false;
    }
}

// ===================== 测试5:大规模数据压力测试 =====================
private static boolean testMassiveInsertion() {
    System.out.println("【测试5】大规模数据压力测试 (100000个节点)");
    try {
        MyRBT<Integer, String> rbt = new MyRBT<>();
        Random rand = new Random(42);
        
        long startTime = System.currentTimeMillis();
        
        // 插入100000个随机节点
        for (int i = 0; i < 100000; i++) {
            int key = rand.nextInt(1000000);
            rbt.put(key, "Massive" + key);
        }
        
        long insertTime = System.currentTimeMillis() - startTime;
        System.out.println("  插入100000节点耗时: " + insertTime + "ms");
        
        // 验证插入后的树
        validateRBT(rbt, "大规模插入后");
        
        // 随机查找测试
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            int key = rand.nextInt(1000000);
            rbt.head = rbt.head; // 模拟查找,实际应该调用get方法
        }
        long searchTime = System.currentTimeMillis() - startTime;
        System.out.println("  10000次随机查找耗时: " + searchTime + "ms");
        
        System.out.println("  ✅ 大规模数据压力测试通过");
        return true;
    } catch (AssertionError e) {
        System.out.println("  ❌ 大规模数据测试失败: " + e.getMessage());
        return false;
    } catch (OutOfMemoryError e) {
        System.out.println("  ⚠️ 大规模数据测试内存不足,跳过此项测试");
        return true;
    }
}

// ===================== 测试6:特殊场景测试 =====================
private static boolean testSpecialScenarios() {
    System.out.println("【测试6】特殊场景测试");
    try {
        // 6.1 测试LL旋转
        System.out.print("  - 测试LL旋转: ");
        MyRBT<Integer, String> llTest = new MyRBT<>();
        int[] llSequence = {30, 20, 10}; // 应该触发LL旋转
        for (int key : llSequence) {
            llTest.put(key, "LL" + key);
        }
        validateRBT(llTest, "LL旋转后");
        System.out.println("✅");
        
        // 6.2 测试RR旋转
        System.out.print("  - 测试RR旋转: ");
        MyRBT<Integer, String> rrTest = new MyRBT<>();
        int[] rrSequence = {10, 20, 30}; // 应该触发RR旋转
        for (int key : rrSequence) {
            rrTest.put(key, "RR" + key);
        }
        validateRBT(rrTest, "RR旋转后");
        System.out.println("✅");
        
        // 6.3 测试LR旋转
        System.out.print("  - 测试LR旋转: ");
        MyRBT<Integer, String> lrTest = new MyRBT<>();
        int[] lrSequence = {30, 10, 20}; // 应该触发LR旋转
        for (int key : lrSequence) {
            lrTest.put(key, "LR" + key);
        }
        validateRBT(lrTest, "LR旋转后");
        System.out.println("✅");
        
        // 6.4 测试RL旋转
        System.out.print("  - 测试RL旋转: ");
        MyRBT<Integer, String> rlTest = new MyRBT<>();
        int[] rlSequence = {10, 30, 20}; // 应该触发RL旋转
        for (int key : rlSequence) {
            rlTest.put(key, "RL" + key);
        }
        validateRBT(rlTest, "RL旋转后");
        System.out.println("✅");
        
        // 6.5 测试叔叔为红色的情况
        System.out.print("  - 测试叔叔为红的情况: ");
        MyRBT<Integer, String> uncleRedTest = new MyRBT<>();
        // 构造一个叔叔为红的情况
        // 先构造一个需要重新着色的情况
        uncleRedTest.put(10, "A");
        uncleRedTest.put(5, "B");
        uncleRedTest.put(15, "C");
        uncleRedTest.put(3, "D");  // 这个插入应该触发叔叔为红的重新着色
        validateRBT(uncleRedTest, "叔叔为红情况后");
        System.out.println("✅");
        
        System.out.println("  ✅ 特殊场景测试通过");
        return true;
    } catch (AssertionError e) {
        System.out.println("  ❌ 特殊场景测试失败: " + e.getMessage());
        return false;
    }
}

// ===================== 验证工具方法 =====================

private static void validateRBT(MyRBT<Integer, String> rbt, String context) {
    if (rbt.head == null) {
        return;
    }
    
    // 验证性质2:根节点是黑色
    if (rbt.head.color != Color.BLACK) {
        throw new AssertionError(context + ": 根节点必须是黑色");
    }
    
    // 验证性质4:红色节点的子节点必须是黑色
    validateNoConsecutiveReds(rbt.head, context);
    
    // 验证性质5:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
    validateBlackHeight(rbt.head, context);
    
    // 验证BST性质
    validateBST(rbt.head, Integer.MIN_VALUE, Integer.MAX_VALUE, context);
}

private static void validateNoConsecutiveReds(RBTNode<Integer, String> node, String context) {
    if (node == null) return;
    
    if (node.color == Color.RED) {
        if (node.left != null && node.left.color == Color.RED) {
            throw new AssertionError(context + ": 连续红色节点 - 节点" + node.key + 
                                   "和它的左孩子" + node.left.key + "都是红色");
        }
        if (node.right != null && node.right.color == Color.RED) {
            throw new AssertionError(context + ": 连续红色节点 - 节点" + node.key + 
                                   "和它的右孩子" + node.right.key + "都是红色");
        }
    }
    
    validateNoConsecutiveReds(node.left, context);
    validateNoConsecutiveReds(node.right, context);
}

private static int validateBlackHeight(RBTNode<Integer, String> node, String context) {
    if (node == null) return 1; // NIL节点是黑色,贡献1个黑色节点
    
    int leftBlackHeight = validateBlackHeight(node.left, context);
    int rightBlackHeight = validateBlackHeight(node.right, context);
    
    if (leftBlackHeight != rightBlackHeight) {
        throw new AssertionError(context + ": 黑高不平衡 - 节点" + node.key + 
                               "的左子树黑高=" + leftBlackHeight + 
                               ", 右子树黑高=" + rightBlackHeight);
    }
    
    // 如果当前节点是黑色,黑高加1
    return leftBlackHeight + (node.color == Color.BLACK ? 1 : 0);
}

private static void validateBST(RBTNode<Integer, String> node, int min, int max, String context) {
    if (node == null) return;
    
    if (node.key < min || node.key > max) {
        throw new AssertionError(context + ": BST性质破坏 - 节点" + node.key + 
                               "超出范围[" + min + ", " + max + "]");
    }
    
    validateBST(node.left, min, node.key - 1, context);
    validateBST(node.right, node.key + 1, max, context);
}

private static void validateAgainstReference(MyRBT<Integer, String> rbt, 
                                             TreeMap<Integer, String> reference) {
    // 验证所有键都存在
    for (Map.Entry<Integer, String> entry : reference.entrySet()) {
        // 这里需要实现get方法,暂时跳过
        // String value = rbt.get(entry.getKey());
        // if (value == null || !value.equals(entry.getValue())) {
        //     throw new AssertionError("键" + entry.getKey() + "的值不匹配");
        // }
    }
}

// 添加一个打印树结构的方法,用于调试
private static void printTree(RBTNode<Integer, String> node, String indent, boolean last) {
    if (node == null) {
        System.out.println(indent + (last ? "└── " : "├── ") + "NIL(B)");
        return;
    }
    
    System.out.println(indent + (last ? "└── " : "├── ") + 
                      node.key + "(" + (node.color == Color.RED ? "R" : "B") + ")");
    
    printTree(node.left, indent + (last ? "    " : "│   "), false);
    printTree(node.right, indent + (last ? "    " : "│   "), true);
}
}

class MyRBT<K extends Comparable<K>,V>{
RBTNode<K,V> head;
int size;

public MyRBT(K key,V value) {
	super();
	this.head = new RBTNode<>();
	this.size++;
}

public MyRBT() {
	super();
}


//对失衡节点的颜色进行调整,自己画图去模拟,别去看教材,这样可以练习自己的推理能力。
//这玩意是真的难啊,我开始居然天真到想去写统一入口的删除跟添加方法,画完图模拟失衡才发现自己的天真。
//难怪AI一直劝我分开写,这玩意变色的逻辑完全不一样,自己不去画图压根体会不到。我也是被自己的天真给坑了。
//自己把自己代码写完按工业级压力测试完通过了,再去看别人的代码进行比较。这样才能获得最大收获
//目前我写的BST,AVL还没去跟源码实现比较过,杀穿常用的经典容器以后再考虑。
private void reBalancePut(RBTNode<K,V> node) {
	//虽然是私有方法尽量还是加一下空拦截,我前面写的时候都没意识到,总觉得是自己内部用的无所谓,不够严谨
	if(node == null) return;
	RBTNode<K,V> current = null;
	RBTNode<K,V> parent = null;
	RBTNode<K,V> grandp = null;
	//因为是节点自管理并且因为判断失衡需要两个节点,所以在删除传参时就直接传后继删除的子节点进来。
	//用父节点循环的话,更清晰的看到当前节点的失衡状况。当node没有父节点时直接出循环颜色赋黑。
	while(node != null) {
		//刚开始想的是增删统一入口就考虑的用current接住循环节点进行指针操作,然后调整完以后再对node重新赋值循环。
		//而在添加的时候压根不需要进行循环,因为失衡只对当前路径造成失衡压根不会向上传递,这个while循环都是多余的。
		//但是我不想把这个while省略掉,我想让看到的人清晰的看到我刚开始分析的错误思路。但是经过进一步的画图发现
		//红子红父红叔叔,只需要旋转以后对current变黑就已经把各路径的黑平衡调整好不再需要继续传播了。而且我开始画面
		//黑叔叔的时候差点掉进坑里了,我完全把null为黑给漏了,武断的下了结论put不可能出现黑叔叔的节点。幸好deepseek
		//带我出了坑,要是还是豆包估计就直接把我往沟里带了。
		current = node;
		if(current.pre == null) break;
		//用current来进行指针连接操作,失衡调整完以后对node重新赋值为根节点重新进行循环不会对循环节点造成影响。
		parent = current.pre;
		if(current.color == Color.RED && parent.color == Color.RED) {
			//根节点必黑。
			//没有祖父节点时表示只有两个节点存在,此时直接退出循环让外部兜底根节点变黑即可。
			//因为根节点在循环的过程中颜色会更新,而又必须保证根为黑,就必须在达到根节点时直接变黑。
			if(parent.pre == null) break;
			//运行到这里必定是有爷爷节点的,这时候再进行失衡拦截,并且爷爷节点必定是黑节点,否则原RBT非法。
			grandp = parent.pre;
			//LL失衡不管叔叔颜色,红父直接向上传播。
			if(current == parent.left && parent == grandp.left ) {
				//这里需要判爷爷是不是根节点,不是就直接先升级再连接曾祖父节点,跟AVL一样。
				if(grandp == this.head) {
					this.head = parent;
					this.head.pre = null;
				}else {
					//判断爷爷节点是曾祖父节点的左右孩子进行指针连接。
					if(grandp == grandp.pre.left) grandp.pre.left = parent;else grandp.pre.right = parent;
					parent.pre = grandp.pre;
				}
				//公共节点连接,先处理升级节点的子节点问题,不然容易连接的时候丢树,打结。
				if(parent.right != null) {
					grandp.left = parent.right;
					parent.right.pre = grandp;
				}else {
					grandp.left = null;
				}
				parent.right = grandp;
				grandp.pre = parent;
				
				current.color = Color.BLACK;
				//失衡继续向上传播。
				node = current;
			//LR失衡
			}else if(current == parent.right && parent == grandp.left) {
				if(grandp == this.head) {
					this.head = current;
					this.head.pre = null;
				}else {
					if(grandp == grandp.pre.left) grandp.pre.left = current; else grandp.pre.right = current;
					current.pre = grandp.pre;
				}
				if(current.left != null) {
					current.left.pre = parent;
					parent.right = current.left;
				}else {
					parent.right = null;
				}
				if(current.right != null) {
					current.right.pre = grandp;
					grandp.left = current.right;
				}else {
					grandp.left = null;
				}
				
				current.left = parent;
				parent.pre = current;
				current.right = grandp;
				grandp.pre = current;
				
				parent.color = Color.BLACK;
				
				node = parent;
			//RR失衡
			}else if(current == parent.right && parent == grandp.right ) {
				if(grandp == this.head) {
					this.head = parent;
					this.head.pre = null;
				}else {
					if(grandp == grandp.pre.left) grandp.pre.left = parent; else grandp.pre.right = parent;
					parent.pre = grandp.pre;
				}
				if(parent.left != null) {
					grandp.right = parent.left;
					parent.left.pre = grandp;
				}else {
					grandp.right = null;
				}
				grandp.pre = parent;
				parent.left = grandp;
				
				current.color = Color.BLACK;
				
				node = current;
			//RL失衡
			}else if(current == parent.left && parent == grandp.right) {
				if(grandp == this.head) {
					this.head = current;
					this.head.pre = null;
				}else {
					if(grandp == grandp.pre.left) grandp.pre.left = current; else grandp.pre.right = current;
					current.pre = grandp.pre;
				}
				if(current.left != null) {
					grandp.right = current.left;
					current.left.pre = grandp;
				}else {
					grandp.right = null;
				}
				if(current.right != null) {
					current.right.pre = parent;
					parent.left = current.right;
				}else {
					parent.left = null;
				}
				grandp.pre = current;
				current.left = grandp;
				parent.pre = current;
				current.right = parent;
				
				parent.color = Color.BLACK;
				
				node = parent;
			}
		}
		node = node.pre;
	}
	//因为我是以新根node为红色向上传递的,当新根node=node.pre为空时,循环退出,此时拿不到node了;
	//这里必须用对象指针最终把根变黑。
	node.color = Color.BLACK;
}


public boolean put(K key,V value) {
	if(key == null || value == null) return false;
	if(this.size == 0) {
		this.head = new RBTNode<>(key,value);
		this.head.color = Color.BLACK;
		this.size++;
		return true;
	}
	RBTNode<K,V> node = head;
	RBTNode<K,V> pre = null;
	while(node != null) {
		if(node.key.equals(key)) {
			node.value = value;
			return true;
		}
		pre = node;
		if(node.key.compareTo(key) < 0) node = node.right;else node = node.left;
	}
	if(pre.key.compareTo(key) < 0) {
		node = new RBTNode<>(key,value);
		pre.right = node;
		node.pre = pre;
	}else {
		node = new RBTNode<>(key,value);
		node.pre = pre;
		pre.left = node;
	}
	this.size++;
	this.reBalancePut(node);
	return true;
}

public boolean remove(K key) {
	if(this.size == 0 || key == null) return false;
	RBTNode<K,V> node = this.head;
	RBTNode<K,V> pre = null;
	//定义一个节点用于接住被真删的节点。
	while(node != null) {
		if(node.key.equals(key)) break;
		pre = node;
		if(node.key.compareTo(key) < 0 ) node = node.right;else node = node.left;
	}
	if(node == null) return false;
	if(node.right == null && node.left == null) {
		if(pre == null) {
			this.head = null;
		}else{
			if(pre.left == node) pre.left = null;
			if(pre.right == node) pre.right = null;
			//如果删除的是末尾节点则只需要判断这个节点的颜色再选择是否进行失衡调整。
			//当真删的节点为末尾节点时,分情况处理。
			//失衡调整只处理无补偿的黑节点传参。
			if(node.color == Color.BLACK && pre.color == Color.BLACK) {
				this.reBalanceRemove(pre);
			//能直接找失衡节点借到红色补偿黑就直接补偿不需要向上传递失衡。
			}else if(node.color == Color.BLACK && pre.color == Color.RED){
				pre.color = Color.BLACK;
			}
			//被删节点为红时直接不处理。让流程继续往下走。
			node.pre = null;
		}
	}else if(node.left == null) {
		if(pre == null) {
			this.head = node.right;
			this.head.pre = null;
			//如果是头节点并且没有左节点,又需要保持红黑树的规则。
			//所以这个右子节点必定是红孩子并且不可能存在子节点(红/黑都非法)。所以这里直接变色断指针变色即可。
			this.head.color = Color.BLACK;
			node.right = null;
		//这两个分支因为没有左子节点,又需要保持红黑树的规则,而这个节点又有子节点,那么这个子节点必定红,而node必黑
		//所以直接把右子染黑,断指针继续执行后续代码就行了
		}else if(pre.left == node){
			pre.left = node.right;
			node.right.pre = pre;
		}else if(pre.right == node) {
			pre.right = node.right;
			node.right.pre = pre;
		}
		node.right.color = Color.BLACK;
		node.right = node.pre = null;
	}else if(node.right == null) {
		if(pre == null) {
			this.head = node.left;
			this.head.pre = null;
			this.head.color = Color.BLACK;
		}else if(pre.left == node){
			pre.left = node.left;
			node.left.pre = pre;
		}else if(pre.right == node) {
			pre.right = node.left;
			node.left.pre = pre;
		}
		node.left.color = Color.BLACK;
		node.left = node.pre = null;
	}else {
		RBTNode<K,V> child = node.right;
		RBTNode<K,V> pChild = null;
		while(child.left != null) {
			pChild = child;
			child = child.left;
		} 
		node.key = child.key;
		node.value = child.value;
		//前面真删法已经有出现了一个情况被删节点为尾节点的问题,现在需要联系后继删的情况统一分析
		//而且后继删还会出现顶替的节点问题需要分析。
		if(pChild == null) {
			if(child.right == null) {
				//这个被真删的节点有可能红也有可能是黑,需要进一步判断删除这个节点以后是否造成失衡。
				//因为这个被删的节点是没有子节点的。所以分析的时候需要跟她的父节点一起联系分析。
				if(child.color == Color.BLACK && node.color == Color.BLACK) {
					this.reBalanceRemove(node);
				}else if(child.color == Color.BLACK && node.color == Color.RED) {
					node.color = Color.BLACK;
				}
				//被删为红不处理。
				node.right = null;
			}else {
				//能进入这个分支说明被删节点有右孩子,没有左边孩子,而她又需要满足RBT原则。
				//所以这个被删的节点是就不可能是红色,必定是黑色。而她又有右子,所以这个顶替的节点必定为红色。
				//结论就是直接把顶替的孩子变黑就行了。
				node.right = child.right;
				child.right.pre = node;
				node.right.color = Color.BLACK;
			}
		}else {
			if(child.right == null) {
				pChild.left = null;
				//这个被真删的节点没有右孩子并且没有左孩子。所以只需要判断她跟她父亲的颜色就行了
				if(child.color == Color.BLACK && pChild.color == Color.BLACK) {
					this.reBalanceRemove(pChild);
				}else if(child.color == Color.BLACK && pChild.color == Color.RED) {
					pChild.color = Color.BLACK;
				}
				//被删为红不处理
			}else {
				pChild.left = child.right;
				child.right.pre = pChild;
				//这个被真删的节点因为有右孩子,而因她本身是她自己的最左子节点,所以她必定是黑色
				//所以顶替的将顶替的孩子直接变黑就行了
				pChild.left.color = Color.BLACK;
			}
		}
		child.left = child.right = child.pre = null;
	}
	this.size--;
	return true;
}


private void reBalanceRemove(RBTNode<K, V> balanceNode) {
	if(balanceNode == null) return;
	RBTNode<K,V> current = balanceNode;
	RBTNode<K,V> nephew = null;
	RBTNode<K,V> parent = null;
	RBTNode<K,V> brother = null;
	//写这个头节点的处理时因为我对这个没有兜底的拦截感到非常别扭,我反复跟DeepSeek讨论了整整一个下午的时间,
	//因为DeepSeek试图说服我用已经经过前辈们验证过的几十年的工程化的经验来写这个方法。但是我一直不服,自己内心
	//最深处一直不愿意直接接受她给我提供的思路。因为我在remove方法已经明确的处理掉了所有的简单调整情况。能进入
	//到失衡调整的节点必定是符合RBT以及BST数学规则的二叉树真删了黑色节点的末端节点。但是由于我现在的处理没有完全的兜底
	//设计我苦恼了很久,一直在想尝试到底应该如何设计一个能彻底为我的这个设计兜底的方法。但是最终DeepSeek被我的最优化的
	//remove失衡修复设计说服了。认可了我现在的这种设计,因为我这个设计是完全建立在信任我的remove方法的大前提下的设计
	//能进入我这个失衡调整方法的节点必为黑节点。
	//需要单独处理第一个进入的就是头节点的问题。
	if(current.pre == null) {
		if(current.left != null) {
			brother = current.left;
			//如果兄弟是红,则她必定有一对黑子。在旋转的过程中,她的黑子就必须连接原根并变红处理。
			//因为必定有黑子直接用,我选择拥抱数学规则进行编码,不做无意义的假设性防御。
			if(brother.color == Color.RED) {
				//兄弟为红必定有黑侄子,黑侄子变红。
				nephew = brother.right;
				this.head = brother;
				this.head.pre = null;
				nephew.pre = current;
				current.left = nephew;
				nephew.color = Color.RED;
				//兄弟升级变黑让原头结点补偿。
				current.pre = brother;
				brother.right = current;
				brother.color = Color.BLACK;
			}else {
				//直接不考虑这个兄弟节点有黑子的情况,因为不可能存在这种结构的非法RBT
				if(brother.color == Color.BLACK && brother.left == null && brother.right == null) {
					//因为兄弟没有儿子补偿黑所以直接变红,补偿损失的黑
					brother.color = Color.RED;
				}else if(brother.left == null) {
					//兄弟为黑并且有子节点时必定为红子,此时直接按LR旋转把侄子升级变黑,旋转后原根节点就会补偿损失的黑。
					nephew = brother.right;
					this.head = nephew;
					this.head.pre = null;
					nephew.right = current;
					current.pre = nephew;
					brother.pre = nephew;
					nephew.left = brother;
					brother.right = current.left = null;
					nephew.color = Color.BLACK;
				}else {
					nephew = brother.left;
					this.head = brother;
					this.head.pre = null;
					if(brother.right != null) {
						brother.right.pre = current;
						current.left = brother.right;
					}
					brother.right = current;
					current.pre = brother;
					current.left = null;
					nephew.color = Color.BLACK;
				}
			}
			//失衡调整完成直接返回。
			return;
		}else {
			//镜像处理
			brother = current.right;
			if(brother.color == Color.RED) {
				this.head = brother;
				this.head.pre = null;
				nephew = brother.left;
				nephew.pre = current.right;
				current.right = nephew;
				nephew.color = Color.RED;
				current.pre = brother;
				brother.left = current;
				brother.color = Color.BLACK;
			}else if(brother.color == Color.BLACK && brother.left == null && brother.right == null) {
				brother.color = Color.RED;
			}else if(brother.right == null) {
				nephew = brother.left;
				this.head = nephew;
				this.head.pre = null;
				nephew.left = current;
				current.pre = nephew;
				nephew.right = brother;
				brother.pre = nephew;
				current.right = brother.left = null;
				nephew.color = Color.BLACK;
			}else {
				nephew = brother.right;
				this.head = brother;
				this.head.pre = null;
				if(brother.left != null) {
					brother.left.pre = current;
					current.right = brother.left;
				}else {
					current.right = null;
				}
				current.pre = brother;
				brother.left = current;
				brother.color = Color.BLACK;
			}
			return;
		}
	}
	while(current != null) {
		if(current.pre == null) break;
		//直接对父节点进行赋值。
		parent = current.pre;
		//进行失衡节点拦截分析:这些判断都是基于满足红黑树所有规则的大前提进行的推论。没有这个大前提判空都得判死。
		//第一种情况:父为红时,不管是在哪条路径上损失黑,只需要把父染黑就足够补偿该路径的黑损失
		if((parent.color == Color.BLACK && current.color == Color.BLACK) || (parent.color == Color.BLACK && current.color == Color.RED)){
			//第二种情况:父为黑时,兄弟为红。需要分左右,以及是否还有祖父节点。
			if(current == parent.left) {
				brother = parent.right;
				//这里只能根据红黑树的数学规则进行硬编码。因为必须要操作兄弟节点升级的旋转问题,而兄弟节点有可能为空
				//而兄弟节点为空又必定不符合红黑树的数学规则。
				if(parent.pre == null && brother != null && brother.color == Color.RED) {
					
				}else if(parent.pre == null && brother != null && brother.color == Color.BLACK){
					//侄子为红直接升级兄弟,侄子变黑,旋转完成直接返回
					if(brother.right != null && brother.right.color == Color.RED) {
						if(parent == parent.left) {
							
						}else {
							
						}
					//直接升级兄弟变红,对current重新赋值为parent继续向上传递冲突,下一次的循环current就是brother
					}else if(brother.right != null && brother.right.color == Color.BLACK) {
						if(parent == parent.left) {
							
						}else {
							
						}
					}
				}
			}else {
				brother = parent.left;
			}
		}else {
			//父为红的情况直接变黑补偿。
			parent.color = Color.BLACK;
			return;
		}	
		current = current.pre;
	}
	current.color = Color.BLACK;
}

public void printLevelOrderZ() {
	if(this.size == 0) return;
	//写这个的时候最开始的时候我是想到了用两个队列控制流程。一个队列负责输出一个负责加。
	//各自做各自的事情,相互不干扰。这时候我就意识到了处理问题就应该这样把问题细化分开来看待了。
	//就跟我开始写单向链表的时候插入跟替换要分开的道理一个样。
	//不过这里我刚开始写也还是踩过一个坑用了队列引用指向另一个引用的傻逼行为。不过我在写完以后没一会
	//就意识到了这么写是错的,因为这样赋值就是把原队列的指针指向了另一个,等于脱裤子放屁而已。
	//后面的层序Z字就水到渠成了,不过在我用队列跟栈写代码的时候我就想过自己再写一个队列跟栈的。
	//但是我把类刚定义好就意识到了,这玩意不就是我写的链表跟数组么,我再写有个屁的意义。这玩意本质
	//就是我自己写的链表跟列表,几乎就是一模一样的东西,只不过改了个名字而已。瞬间就感觉编程原来都是数据
	//结构这玩意。这里有一个坑队列的.reversed方法没有用得用工具类Collections.reverse(指针)才能对列表进行排序。
	Queue<RBTNode<K,V>> que = new LinkedList<>();
	Queue<RBTNode<K,V>> que1 = new LinkedList<>();
	que.add(this.head);
	int num = 1;
	while(!que.isEmpty()) {
		List<RBTNode<K,V>> l = new LinkedList<>();
		while(!que.isEmpty()) {
			RBTNode<K,V> node = que.poll();
			if(node.left != null) {
				que1.add(node.left);
			}
			if(node.right != null) {
				que1.add(node.right);
			}
			l.add(node);
		}
		if(num % 2 == 0) {
			//这个没用l.reversed();
			Collections.reverse(l);
		}
		System.out.print("[");
		for(int i=0;i<l.size();i++) {
			if(i==l.size()-1) {
				System.out.print(l.get(i));
			}else {
				System.out.print(l.get(i)+",");
			}
		}
		System.out.println("]");
		num++;
		while(!que1.isEmpty()) {
			que.add(que1.poll());
		}
	}
}
}

class RBTNode<K extends Comparable<K>,V>{
RBTNode<K,V> pre;
RBTNode<K,V> left;
RBTNode<K,V> right;
Color color;
K key;
V value;

public RBTNode(K key, V value) {
	super();
	this.key = key;
	this.value = value;
	//初始化节点的颜色。
	this.color = Color.RED;
}
public RBTNode() {
	super();
}
@Override
public String toString() {
	
	return this.key +"/"+this.color.toString();
}


}

//这个枚举类型很少用,都忘记了。枚举类型内部是静态变量,直接类名点就行了。
enum Color{
        RED,BLACK;

}