活动介绍

C++中重定向的概率

时间: 2025-06-18 08:31:24 浏览: 11
### 关于C++中重定向的概念及其实现 #### 1. C++ 中的重定向概念 在 C++ 编程中,“重定向”通常指的是改变标准输入/输出流的方向,即将程序的标准输入(`std::cin`)、标准输出(`std::cout`)或错误输出(`std::cerr`)重新指向其他文件或其他流对象。这种操作可以通过绑定新的流缓冲区来完成。 通过 `std::streambuf* rdbuf()` 方法获取当前流关联的缓冲区指针,并利用 `rdbuf(std::streambuf*)` 设置一个新的缓冲区,从而实现流的重定向功能[^5]。 --- #### 2. 实现方式及其概率分析 以下是基于 C++ 的一种典型重定向实现: ```cpp #include <iostream> #include <fstream> int main() { // 创建一个文件流并打开目标文件 std::ofstream outFile("output.txt"); // 将标准输出流 (std::cout) 绑定到文件流上 std::streambuf* oldBuffer = std::cout.rdbuf(outFile.rdbuf()); // 输出内容会自动被重定向至 output.txt 文件 std::cout << "This message will be written to the file." << std::endl; // 还原标准输出流 std::cout.rdbuf(oldBuffer); outFile.close(); return 0; } ``` 上述代码展示了如何将 `std::cout` 的默认行为从控制台打印更改为向指定文件写入数据的过程。此技术广泛应用于调试、日志记录以及自动化测试等领域[^6]。 关于“实现的概率”,这一表述可能涉及两方面含义: - **成功执行的可能性**:只要遵循正确的语法结构与逻辑流程,在现代编译器环境下几乎不存在失败的情况。 - **应用场景下的适用性评估**:取决于具体项目需求和技术栈选择等因素影响下采取该方案的比例大小;例如单元测试框架内部常常用类似的机制捕获函数调用期间产生的所有屏幕输出以便验证预期结果是否匹配实际表现形式[^7]。 --- #### 3. 示例说明 下面提供了一个更加复杂的例子,展示同时对输入和输出进行双重重定向的操作过程: ```cpp #include <iostream> #include <sstream> void redirectStreams(){ // 使用字符串流代替真实的硬件设备交互模拟情景 std::ostringstream oss; std::istringstream iss("Sample input string."); auto originalOutBuf = std::cout.rdbuf(); // 存储原始 cout 缓冲区地址 auto originalInBuf = std::cin.rdbuf(); // 同样保存 cin 原始状态 try{ std::cout.rdbuf(oss.rdbuf()); // 修改 cout 指向新目的地 std::cin.rdbuf(iss.rdbuf()); // 更改 cin 来源位置 std::string userInput; std::cout << "Enter something: "; getline(std::cin, userInput); // 此处读取的是预定义好的 iss 数据而非键盘敲击动作 // 并且最终显示效果存储到了 oss 当中而不是屏幕上呈现出来 std::cout << "\nYou entered: \"" << userInput << "\"\n"; }catch(...){ throw ; // 如果发生异常则抛出给外部处理者决定后续措施 } std::cout.rdbuf(originalOutBuf); // 恢复正常工作模式之前的状态设置 std::cin.rdbuf(originalInBuf); } // 调用以上函数即可观察整个运行阶段的变化情况 ``` ---
阅读全文

相关推荐

根据我写的红黑树,写一个打印函数给我#ifndef _RBTREE_H_ #define _RBTREE_H_ /* 红黑树性质 验证方法 1. 节点为红或黑 无需显式检查(由颜色枚举保证) 2. 根节点为黑 在入口函数检查 root->_col == BLACK 3. 叶子节点(NIL)为黑 通过 nullptr 隐式处理(到达空节点时统计) 4. 红色节点不能有红子节点 递归检查父子节点颜色 5. 黑高一致性 对比每条路径的黑色节点数是否等于 refNum */ /* 性质: 1.每个节点要么是红色, 要么就是黑色. 2.根节点必为黑色。 3.叶子节点(NIL)都为黑色,数据key和value且为null。 4.红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点)(即父与子节点不可能都是红色) 5.从任意节点出发,到其每个叶子节点(NIL)的路径中包含相同数量的黑色节点 衍生特性: 1.新加入到红黑树的节点为红色节点,如果新加入节点是黑色,会影响性质4。所以新节点是红色影响最小 2.根据性质3判断: 叶子节点(NIL)虽然是null, 但也算是一个有效的颜色节点 3.根据性质4判断: 黑色节点的2个子节点可以为2个红色节点; 也可以是2个黑色节点; 也可以是1红1黑的节点 5.根据性质5判断: 确保没有一条路径会比其他路径长出俩倍 */ #include <type_traits> namespace RBTREE { //红黑树的颜色类型 typedef enum class tagCOLOR : unsigned char//给枚举类型限定字节数,C++11开始支持 sizeof(Color) = 1byte { RED, BLACK }COLOR; /****************************************************************************************************************************/ //红黑树左右孩子的位置类型,主要用作判断当前节点是属于父节点的左孩子还是右孩子 //这样就不用疯狂使用父节点寻址才能得到当前节点的分支方向(左右) typedef enum class tagBELONG : unsigned char//给枚举类型限定字节数,C++11开始支持 sizeof(BELONG) = 1byte { ROOT, LEFT, RIGHT, NIL }BELONG; /****************************************************************************************************************************/ //红黑树节点类 template<class KEY, class VALUE> class RBTreeNode { public: COLOR m_Color; //节点颜色,不是红就是黑. 默认色是红色,黑色会影响性质5 KEY m_Key; //关键值: 查找;比较... 都依靠它 VALUE m_Value; //目标元素 RBTreeNode* m_pParent; //父节点 RBTreeNode* m_pLeft; //左孩子 RBTreeNode* m_pRight; //右孩子 BELONG m_beLong; //节点是属于父节点的左孩子还是右孩子。 bool m_bIsDeleted; //标记节点是否被删除 static RBTreeNode<KEY, VALUE> NIL; // 静态NIL声明 //默认构造函数:仅供NIL叶子节点创建时使用 RBTreeNode() : m_Color(COLOR::BLACK), //NIL节点默认颜色是: 黑色 m_Key(), //KEY默认构造 m_Value(), //VALUE默认构造 m_pParent(nullptr), //父指针置空 m_pLeft(nullptr), //左指针置空 m_pRight(nullptr), //右指针置空 m_beLong(BELONG::NIL), //设置为叶子节点NIL m_bIsDeleted(false) //标记节点没有被删除 { } //初始化节点数据 RBTreeNode(const KEY& _Key, const VALUE& _Value) : m_Color(COLOR::RED), //新节点默认颜色是: 红色 m_Key(_Key), //启动KEY的复制构造函数 m_Value(_Value), //启动VALUE的复制构造函数 m_pParent(nullptr), //父指针置空 m_pLeft(nullptr), //左指针置空 m_pRight(nullptr), //右指针置空 m_beLong(BELONG::ROOT) //设置为根节点,插入/删除后会调整 { //强制定义叶子节点为的就是维护红黑树的性质: 2.根据性质3判断: 叶子节点(NIL)虽然是null, 但也算是一个有效的颜色节点 m_pRight = &NIL; //新节点的右边指向叶子节点 m_pLeft = &NIL; //新节点的左边指向叶子节点 NIL.m_pLeft = NIL.m_pRight = NIL.m_pParent = &NIL; //完全自引用 //其实可以直接用nullptr去模拟成NIL节点,下面有2个方案的解析说明: 推荐方案:优先使用实质NIL节点 /* 方案一:使用nullptr表示NIL节点 实现方式 优点 内存效率高 不需要为NIL节点分配额外内存,节省空间。 适合内存敏感场景(如嵌入式系统)。 代码简洁 直接使用nullptr判断,减少间接访问。 例如:if(node->m_pLeft == nullptr) 比 if (node->m_pLeft == m_NIL) 更直观。 性能略优 避免指针解引用(访问实质NIL节点需要一次内存访问)。 缺点 边界条件处理复杂 旋转、删除等操作需要频繁检查nullptr,容易遗漏边界条件。 例如:左旋时需确保node->m_pRight != nullptr。 代码重复 每个操作需重复nullptr检查,可能违反DRY原则。 调试困难 nullptr无法提供调试信息(如NIL节点的颜色、父节点等)。 方案二:使用实质NIL节点 实现方式 优点 统一边界条件处理 所有叶子节点均为m_NIL,简化旋转、删除等操作的逻辑。 例如:左旋时无需检查node->m_pRight是否为空。 代码可维护性强 通过isNIL()方法封装判断逻辑,减少重复代码。 调试时可打印NIL节点的信息(如颜色、父节点)。 符合红黑树理论定义 明确区分内部节点和叶子节点,便于验证红黑树性质。 支持复杂自定义类型 自定义类型的NIL节点可初始化默认值,避免未定义行为。 缺点 内存开销 每个红黑树实例需分配一个NIL节点(通常可忽略)。 初始化复杂度 需确保NIL节点的指针自洽(如m_pLeft = m_pRight = m_pParent = this)。 推荐方案:优先使用实质NIL节点 理由 安全性:避免因nullptr检查遗漏导致的未定义行为(如空指针解引用)。 可维护性:统一处理边界条件,减少代码错误。 调试友好:NIL节点可附加调试信息(如颜色、父节点)。 性能差异极小:现代CPU的缓存机制会抵消单次指针解引用的开销。 */ } //判断当前是否叶子节点 inline bool isNIL()const { return m_beLong == BELONG::NIL; } }; // 类外静态成员定义 template<class KEY, class VALUE> RBTreeNode<KEY, VALUE> RBTreeNode<KEY, VALUE>::NIL; /****************************************************************************************************************************/ //红黑树类 template<class KEY, class VALUE> class RBTree { private: RBTreeNode<KEY, VALUE>* m_pRoot;//红黑树根节点, 性质: 必须为黑色 int m_iSize; //红黑树大小 public: //默认构造函数 RBTree() : m_pRoot(nullptr) //新创建的树,根节点置为空 { } //默认析构函数 ~RBTree() { } //判断是否空树 inline bool IsEmpty()const { return m_pRoot == nullptr; } //向红黑树插入元素 bool Insert(const KEY& _Key, const VALUE& _Value) { RBTreeNode<KEY, VALUE>* pNewNode = new RBTreeNode<KEY, VALUE>(_Key, _Value);//创建一个新节点 printf("创建节点:%#x key<%d>\n", pNewNode, _Key); if (insert(m_pRoot, pNewNode)) //接入内部insert函数 { m_iSize++;//插入成功,树大小+1 return true;//返回true } printf("插入失败,删除节点:%#x key<%d>完成\n", pNewNode, pNewNode->m_Key); delete pNewNode;//插入失败,删除新节点 pNewNode = NULL; return false; } //精确查找 VALUE* Find(const KEY& _Key) { RBTreeNode<KEY, VALUE>* pResult = find(_Key); return pResult ? &pResult->m_Value : nullptr; } bool Erase(const KEY& _Key) { RBTreeNode<KEY, VALUE>* pTarget = find(_Key);//查找目标节点 RBTreeNode<KEY, VALUE>* pTmp = nullptr; if (pTarget) { //成功查找到目标节点 if (pTarget->m_pRight->isNIL() == false && pTarget->m_pLeft->isNIL() == false)//如果2个孩子都不是NIL { //我个人优先采用后继节点替换方案 //据说优先采用后继节点替换, 删除目标后能减少调整红黑树的机率,是不是真的不知道. //但对于正常用右脑的我来说,思维视图方面相对方便一点的:查找后继过程思维视图类似写了个大于号: > //当然,对于用左脑的大佬来说,使用前驱节点替换方案也就只写了个小于号: < //找出后继节点,2个节点交换 //pTmp = sucessor(pTarget->m_pRight);//最坏情况:后继节点就是右孩子,正常情况:后继节点就是右孩子的最左节点 pTmp = predecessor(pTarget->m_pLeft); //最坏情况:前驱节点就是左孩子,正常情况:前驱节点就是左孩子的最右节点 swapKeys(pTarget, pTmp);//交换2个节点的数据 //数据交换成功,此时pTmp节点才是我们的删除目标了,因为数据已经交替过来了,并且确保pTmp的俩孩子都是NIL pTmp->m_bIsDeleted = true; //标记节点为删除状态 return erase(pTmp);//接入内部函数: 删除操作过程修复树 } else { pTarget->m_bIsDeleted = true; //标记节点为删除状态 return erase(pTarget);//说明删除目标的2个孩子都是NIL空, 修复树 } } else { //要查找的key不存在 return false; } } private: //红黑树的内部函数 // 1. 将红黑树当作一颗二叉查找树,将新节点插入到红黑树中。 // 2. 插入完成后再调整树状态,使其能成为一棵合法的红黑树 bool insert(RBTreeNode<KEY, VALUE>* &_pRoot, RBTreeNode<KEY, VALUE>* _pNewNode) //&_pRoot能够使函数内部直接读写m_pRoot { if (_pNewNode == nullptr)return false; RBTreeNode<KEY, VALUE>* pCursor = _pRoot;//操作游标,一般指当前节点 RBTreeNode<KEY, VALUE>* pPreNode = nullptr;//用作 备份上一级节点.当游标指向NIL叶子节点时,此指针就能直接向上回溯出父节点 BELONG blNIL = BELONG::NIL; //用来判断找到的叶子节点是左孩子或者是右孩子 while (pCursor != nullptr && !pCursor->isNIL())//如果当前节点不为空, 并且不是叶子节点 { pPreNode = pCursor;//备份上一个节点,游标将指向下个节点 if (_pNewNode->m_Key < pCursor->m_Key)//如果新节点的key小于当前节点的key { pCursor = pCursor->m_pLeft;//把左孩子赋值给游标 blNIL = BELONG::LEFT;//最后比较是: 向左操作 continue;//重新循环比较 } else if (_pNewNode->m_Key > pCursor->m_Key)//如果新节点的key大于当前节点的key { pCursor = pCursor->m_pRight;//把右孩子赋值给游标 blNIL = BELONG::RIGHT;//最后比较是: 向右操作 continue;//重新循环比较 } else //那么说明插入的节点已经存在 { return false;//插入失败 } } /*代码执行到此, 只有以下2种情况: 1. m_pRoot是空的 2. 说明游标节点已经指向到: 叶子节点*/ if (pCursor == nullptr)//m_pRoot是空的 { _pRoot = _pNewNode; //当前节点为根节点 _pRoot->m_Color = COLOR::BLACK; //根节点要求绝对黑色 _pRoot->m_beLong = BELONG::ROOT; //设置为根节点 return true; } else if(pCursor->isNIL() && pPreNode)//2.游标指向NIL, 说明已经找到适合插入的节点, 并且父节点不为空 { //判断最后的比较操作 if (blNIL == BELONG::LEFT)//叶子节点是位于左边 { //开始与左节点进行联结 pPreNode->m_pLeft = _pNewNode;//父节点的左孩子设置成这个新节点 _pNewNode->m_pParent = pPreNode;//设置这个新节点的父亲 _pNewNode->m_beLong = BELONG::LEFT;//这新节点是父亲的左孩子 //调整颜色不在此处调整 //联结完成 } else if (blNIL == BELONG::RIGHT)//叶子节点是位于右边 { //开始与右节点进行联结 pPreNode->m_pRight = _pNewNode;//父节点的右孩子设置成这个新节点 _pNewNode->m_pParent = pPreNode;//设置这个新节点的父亲 _pNewNode->m_beLong = BELONG::RIGHT;//这新节点是父亲的右孩子 //调整颜色不在此处调整 //联结完成 } else { //不应该也不可能来到此处,属于异常: 不是左操作,也不是右操作,等于操作已经返回false了 return false; } } else { //不应该来到此处,属于异常: 游标指向是叶子节点, 但pPreNode是空,这并不可能发生的事 return false; } //代码执行到此: 需要调整颜色调整树结构了, 审查当前结构的合法性 //设置插入目标节点的颜色为: 红色. 插入新节点为红色对红黑树的性质5影响最小 //_pNewNode->m_Color = Color::RED;//节点初始化已经是红色,无须重复设置 //插入新节点后, 重新修正为一颗平衡二叉树 insert_fix(_pNewNode); ////////// //修正完成后就是一棵标准的: 红黑树 // // return true; // } // // /******************************************开始解析说明 insert_fix() 函数修复红黑树过程******************************** * 红黑树插入修正函数 * * 在向红黑树中插入节点之后(失去平衡),再调用该函数; * 目的是将它根据颜色调整重新塑造成一颗红黑树。 * * 参数说明: * _pRoot 红黑树的根节点 * _pNewNode 插入的新节点 */ /* 修正原理 回忆下红黑树的性质: 性质: 1.每个节点要么是红色, 要么就是黑色。 2.根节点必为黑色。 3.叶子节点(NIL)都为黑色,数据key和value且为null。 4.红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点)(即父与子节点不可能都是红色) 5.从任意节点出发,到其每个叶子节点(NIL)的路径中包含相同数量的黑色节点 衍生特性: 1.新加入到红黑树的节点为红色节点,如果新加入节点是黑色,会影响性质4。所以新节点是红色影响最小 2.根据性质3判断: 叶子节点(NIL)虽然key和value是null, 但也算是一个有效的颜色节点 3.根据性质4判断: 黑色节点的2个子节点可以为2个红色节点。也可以是2个黑色节点,但必须是叶子节点(NIL)(左右树不可能存在有效的黑色节点) 4.根据性质4判断: 黑色节点的2个子点也可以是1个红色节点,一个黑色节点(叶子节点NIL)(左右树不可能存在有效的黑色节点) 5.根据性质5判断: 确保没有一条路径会比其他路径长出俩倍 ************************************************************************** 总结插入新节点可能出现的情景,并给出处理方案: 情景1: 插入的节点的父节点是黑色 情景1处理方案:无需任何处理 情景2:插入的节点的父节点是红色(父子节点均为红色,需修正) 根据叔叔节点去拆分3种分支情景: 情景2.1: 叔叔节点是存在,并且是红色 情景2.1处理方案: 将父节点设置成黑色; 将叔叔节点设置成黑色; 将祖父节点设置成红色; 将祖父节点设置成当前节点,继续while循环向上修正 情景2.2: 叔叔节点不存在或者叔叔节点是黑色, 并且插入的节点的父节点是属于其祖父节点的左孩子(叔叔是右孩子) 根据情景2.2去拆分2种分支情景: 情景2.2.1: 插入节点是其父节点的右孩子(内侧插入) 情景2.2.1处理方案: 将当前节点的父节点左旋操作后得到情景2.2.2 处理情景2.2.2 情景2.2.2: 插入节点是其父节点的左孩子(外侧插入) 情景2.2.2处理方案: 将父亲节点设置为黑色 将祖父节点设置成红色 将祖父节点进行右旋操作 情景2.3: 叔叔节点不存在或者叔叔节点是黑色, 并且插入节点的父节点是属于其祖父节点的右孩子(叔叔是左孩子) 根据情景2.3去拆分2种分支情景: 情景2.3.1: 插入的节点是其父节点的左孩子(外侧插入) 情景2.3.1处理方案: 将当前节点的父节点右旋操作后得到情景2.3.2 处理情景2.3.2 情景2.3.2: 插入的节点是其父节点的右孩子(内侧插入) 情景2.3.2处理方案: 将父新节点设置成黑色 将祖父节点设置成红色 将祖父节点进行左旋操作 */ bool insert_fix(RBTreeNode<KEY, VALUE>* _pNewNode) { if (!_pNewNode)return false; RBTreeNode<KEY, VALUE>* pParent = _pNewNode->m_pParent; //取得父节点 RBTreeNode<KEY, VALUE>* pGrandfather = nullptr;//取出祖父节点 RBTreeNode<KEY, VALUE>* pUncle = nullptr; //用作储存叔叔节点 // 情景1: 插入的节点的父节点是黑色 // 情景1处理方案:无需任何处理 if (pParent && pParent->m_Color == COLOR::BLACK)return true;//情景1处理完成 if (!pParent)//说明已经到达根节点了 { if (_pNewNode->m_beLong == BELONG::ROOT) { _pNewNode->m_Color = COLOR::BLACK;//根节点强制黑色 return true; } return false; } do { //这是情景2: 插入的新节点的父亲是红色的,违反红黑树性质4: //4. 红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点)(即父与子节点不可能都是红色) pGrandfather = pParent->m_pParent;//取出祖父节点 if (pGrandfather)//祖父节点是有效的 { //如果父亲是左孩子,那么叔叔肯定是祖父的右孩子。否则父亲是右孩子,那么叔叔就是左孩子 pUncle = pParent->m_beLong == BELONG::LEFT ? pGrandfather->m_pRight : pParent->m_beLong == BELONG::RIGHT ? pGrandfather->m_pLeft : nullptr;//取出叔叔节点 if (pUncle == nullptr) { //叔叔节点居然提取不到,不可能的,说明结构有异常 throw("红黑树结构异常: 颜色持衡过程发现->取得叔叔节点失败, 父亲节点居然是NIL"); } //开始检查叔叔节点颜色 if (pUncle->m_Color == COLOR::RED)//叔叔节点是红色: 这是情景2.1 { //情景2.1修正方案: 颜色持衡 //将P设置为黑色 //将U设置为黑色 //将G设置为红色 //将G设置成当前节点继续while循环向上判断 pParent->m_Color = COLOR::BLACK;//将P设置为黑色 pUncle->m_Color = COLOR::BLACK; //将U设置为黑色 pGrandfather->m_Color = COLOR::RED;//将G设置为红色 return insert_fix(pGrandfather);//将G设置成当前节点继续递归循环向上判断 } else if(pUncle->m_Color == COLOR::BLACK)//叔叔节点是黑色: 这是情景2.2或者情景2.3 { if (pParent->m_beLong == BELONG::LEFT)//父亲是个左孩子,触发情景2.2 { if (!_pNewNode) { throw("触发情景2.2: 插入节点为空"); } if (_pNewNode->m_beLong == BELONG::RIGHT)//如果插入节点是其父亲的右孩子: 触发情景 2.2.1 { pParent = left_rotate(pParent);//对父亲进行左旋操作得到情景2.2.2 if (!pParent)throw("红黑树结构异常: 触发情景2.2.1时左旋失败"); _pNewNode = pParent->m_pLeft; //左旋后, 插入节点和父亲交换成功了。 return insert_fix(_pNewNode);//重新递归进入情景: 2.2.2 } else if (_pNewNode->m_beLong == BELONG::LEFT)//如果插入节点是其父亲的左孩子: 触发情景2.2.2 { pParent->m_Color = COLOR::BLACK;//将P设置成黑色 pGrandfather->m_Color = COLOR::RED;//将G设置成红色 pParent = right_rotate(pGrandfather);//对G进行右旋操作 return true;//情景2.2的所有场景解析完成 } //进入到此处代码说明 新插入节点是ROOT,NIL, else已经不可能了,因为insert_fix的上层是insert对pNewNode封装好了 } else if (pParent->m_beLong == BELONG::RIGHT)//父亲是个右孩子,触发情景2.3 { if (!_pNewNode) { throw("触发情景2.3: 插入节点为空"); } if (_pNewNode->m_beLong == BELONG::LEFT)//如果插入的节点是其父亲的左孩子: 触发情景2.3.1 { pParent = right_rotate(pParent);//对父亲进行右旋操作得到情景2.3.2 if(!pParent)throw("红黑树结构异常: 触发情景2.3.1时右旋失败"); _pNewNode = pParent->m_pRight; //右旋后,插入节点和父亲交换成功了 return insert_fix(_pNewNode);//递归 进入情景: 2.3.2 } else if (_pNewNode->m_beLong == BELONG::RIGHT)//如果插入节点是其父亲的右孩子: 触发情景2.3.2 { pParent->m_Color = COLOR::BLACK;//将P设置成黑色 pGrandfather->m_Color = COLOR::RED;//将G设置成红色 pParent = left_rotate(pGrandfather);//对G进行左旋操作 return true;//情景2.3的所有场景解析完成 } //进入到此处代码说明 新插入节点是ROOT,NIL, else已经不可能了,因为insert_fix的上层是insert对pNewNode封装好了 } else if (pParent->m_beLong == BELONG::ROOT) { //插入节点红色,父节点红色才会进入情景2.2-2.3,但父亲如果是根节点Root,那么之前这个红黑树就是不合法了 //因为根节点永远是黑色,所以其实能进入到此处也是异常了。 //pParent->m_Color = COLOR::BLACK;//无条件把根节点设置为黑色 //return true;//已经while到根节点Root了 throw("红黑树结构异常: 插入红色新节点,父节点也是红色,但父节点居然是个根节点,红色的根节点是异常的"); } else { //基本不可能进入到此处,因为叶子节点永远是黑色的 throw("红黑树结构异常: 插入红色新节点,父节点也是红色,但父节点居然是个叶子节点"); } } //else 基本不可能的,因为红黑树性质: 节点不是红就是黑, 不可能进入else了 } else//祖父节点是无效的: 说明只有一种情况:当前的_pParent节点是根节点Root { if (pParent->m_beLong == BELONG::ROOT)//当前父节点是根节点Root { pParent->m_Color = COLOR::BLACK;//无条件把根节点设置为黑色 return true;//已经while到根节点Root了 } else { //祖父节点不存在, 父节点又不是根节点,说明结构异常了 throw("红黑树结构异常: 颜色持衡过程发现->祖父节点为空, 但父节点又不是根节点Root,这情况不应该发生."); } } } while (pParent); return false; } //进行左旋操作 RBTreeNode<KEY, VALUE>* left_rotate(RBTreeNode<KEY, VALUE>* _pTarget) { if (!_pTarget)return nullptr; if (_pTarget->isNIL()) { throw("左旋异常: 目标是个NIL, 叶子节点不能左旋操作"); } RBTreeNode<KEY, VALUE>* pParent = _pTarget->m_pParent; //取出目标的父节点 RBTreeNode<KEY, VALUE>* pRight = _pTarget->m_pRight;//备份目标的右孩子 RBTreeNode<KEY, VALUE>* pTmp = nullptr;//临时操作使用 BELONG beLong = _pTarget->m_beLong;//备份目标的 左/右孩子属性 if (pRight->isNIL()) { throw("左旋异常: 目标的右孩子是NIL, NIL不可能作为父节点的"); } pTmp = _pTarget->m_pRight = pRight->m_pLeft;//重定向右孩子到备份pRight的左孩子 pRight->m_pLeft = nullptr;//把备份的pRight的左孩子断开,因为左孩子已经被目标节点重定向占用了 if (pTmp->isNIL() == false)//如果新定向的右孩子不是NIL叶子节点 { pTmp->m_beLong = BELONG::RIGHT;//这个新孩子是从备份的pRight左边取过来的变成右孩子 pTmp->m_pParent = _pTarget;//把这个新右孩子的父亲设置是目标节点 } //else //新的右孩子是个NIL叶子节点,不用设置NIL属性 pTmp = nullptr;//置空,避免后面误操作 pRight->m_pLeft = _pTarget;//重新设置备份pRight的左孩子为目标节点 _pTarget->m_pParent = pRight;//重设置目标的父亲为pRight _pTarget->m_beLong = BELONG::LEFT;//此时目标就是pRight的左孩子 if (pParent == nullptr)//左旋的目标没有父节点,说明左旋的目标是一个根节点 { if (beLong != BELONG::ROOT)//如果成立,说明之前红黑树结构异常 { //左旋目标没有父节点并且不是根节点,异常了 throw("左旋异常: 目标没有父节点并且也没有根节点的标记"); } pRight->m_beLong = beLong;//设置Root pRight->m_Color = COLOR::BLACK;//根节点必然为黑 pRight->m_pParent = nullptr;//根节点没有父亲 m_pRoot = pRight;//根节点已经发生变化 } else { pRight->m_beLong = beLong;//设置备份pRight的孩子属性 pRight->m_pParent = pParent;//设置备份pRight的父亲 if (beLong == BELONG::LEFT)//左旋目标原来是一个左孩子 { pParent->m_pLeft = pRight; } else//左旋目标原来是一个右孩子 { pParent->m_pRight = pRight; } } return pRight; } //进行左旋操作 RBTreeNode<KEY, VALUE>* right_rotate(RBTreeNode<KEY, VALUE>* _pTarget) { if (!_pTarget)return nullptr; if (_pTarget->isNIL()) { throw("右旋异常: 目标是个NIL, 叶子节点不能右旋操作"); } RBTreeNode<KEY, VALUE>* pParent = _pTarget->m_pParent; //取出目标的父节点 RBTreeNode<KEY, VALUE>* pLeft = _pTarget->m_pLeft;//备份目标的左孩子 RBTreeNode<KEY, VALUE>* pTmp = nullptr;//临时操作使用 BELONG beLong = _pTarget->m_beLong;//备份目标的 左/右孩子属性 if (pLeft->isNIL()) { throw("右旋异常: 目标的左孩子是NIL, NIL不可能作为父节点的"); } pTmp = _pTarget->m_pLeft = pLeft->m_pRight;//重定向左孩子到备份pLeft的右孩子 pLeft->m_pRight = nullptr;//把备份的pLeft的右孩子断开,因为该右孩子已经被目标节点重定向占用了 if (pTmp->isNIL() == false)//如果新定向的左孩子不是NIL叶子节点 { pTmp->m_beLong = BELONG::LEFT;//这个新孩子是从备份的pLeft右边取过来的变成左孩子的 pTmp->m_pParent = _pTarget;//把这个新左孩子的父亲设置是目标节点 } //else //新的左孩子是个NIL叶子节点,不用设置NIL属性 pTmp = nullptr;//置空,避免后面误操作 pLeft->m_pRight = _pTarget;//重新设置备份pLeft的右孩子为目标节点 _pTarget->m_pParent = pLeft;//重设置目标的父亲为pLeft _pTarget->m_beLong = BELONG::RIGHT;//此时目标就是pLeft的右孩子 if (pParent == nullptr)//右旋的目标没有父节点,说明右旋的目标是一个根节点 { if (beLong != BELONG::ROOT)//如果成立,说明之前红黑树结构异常 { //右旋目标没有父节点并且不是根节点,异常了 throw("右旋异常: 目标没有父节点并且也没有根节点的标记"); } pLeft->m_beLong = beLong;//设置Root pLeft->m_Color = COLOR::BLACK;//根节点必然为黑 pLeft->m_pParent = nullptr;//根节点没有父亲 m_pRoot = pLeft;//根节点已经发生变化 } else { pLeft->m_beLong = beLong;//设置备份pLeft的孩子属性 pLeft->m_pParent = pParent;//设置备份pLeft的父亲 if (beLong == BELONG::LEFT)//右旋目标原来是一个左孩子 { pParent->m_pLeft = pLeft; } else//左旋目标原来是一个右孩子 { pParent->m_pRight = pLeft; } } return pLeft; } // 精确查找(返回节点指针,未找到返回nullptr) RBTreeNode<KEY, VALUE>* find(const KEY& key) const { RBTreeNode<KEY, VALUE>* pCurrent = m_pRoot; while (pCurrent && !pCurrent->isNIL()) { if (key == pCurrent->m_Key) { return pCurrent; } else if (key < pCurrent->m_Key) { pCurrent = pCurrent->m_pLeft; } else { pCurrent = pCurrent->m_pRight; } } return nullptr; } //删除操作 bool erase(RBTreeNode<KEY, VALUE>* _pTarget) { //printf("删除节点:%#x key<%d>\n", _pTarget, _pTarget->m_Key); RBTreeNode<KEY, VALUE>* pTarget = _pTarget; //目标节点 RBTreeNode<KEY, VALUE>* pParent = nullptr; //父亲节点 RBTreeNode<KEY, VALUE>* pBrother = nullptr; //兄弟节点 RBTreeNode<KEY, VALUE>* pChild = nullptr; //孩子节点 do { pParent = pTarget->m_pParent; //找出兄弟节点 pBrother = (pTarget->m_beLong == BELONG::LEFT) ? pParent->m_pRight : (pTarget->m_beLong ==BELONG::RIGHT) ? pTarget->m_pLeft : nullptr; //该如何判断肯定pBrother节点的存在性? 这已经给红黑树性质约束了: // 除根节点外,每个黑色节点都必须有非NIL的兄弟节点。这是由红黑树的性质5(黑色高度一致) //所以:每个黑色节点,除了根节点外,这个黑色节点必定会存在一个非NIL的兄弟 //这个兄弟节点是绝对存在的 if (pTarget->m_Color == COLOR::RED) { //场景1:被删除节点是红色********************************************* pTarget = remove_node_red(pTarget);//接入场景1的函数 //if(pTarget == nullptr) 如果返回的目标是空,则说明红黑树不用向上调整修复 } else//被删除的节点是黑色 { pChild = pTarget->m_pLeft->isNIL() == false ? pTarget->m_pLeft : pTarget->m_pRight;//取出孩子 if (pChild->isNIL() == false && pTarget->m_bIsDeleted)//如果有子节点, { pTarget = remove_node_black_3_1_1(pTarget); } else { if (pBrother && pBrother->m_Color == COLOR::RED) { //场景2:被删除节点是黑色,并且兄弟是红色********************************************* pTarget = remove_node_black_2(pTarget); //if(pTarget == nullptr) 如果返回的目标是空,则说明红黑树不用向上调整修复 if (pTarget)continue;//迭代进入场景3 } else if(pBrother && pBrother->m_Color == COLOR::BLACK) { //场景3:被删除节点是黑色,并且兄弟是黑色********************************************* pTarget = remove_node_black_3(pTarget); //if(pTarget == nullptr) 如果返回的目标是空,则说明红黑树不用向上调整修复 if (pTarget)continue;//迭代进入其它场景 } else { //被删除节点是根节点,并且2个子节点都是NIL m_pRoot = nullptr; break; } } } } while (pTarget);//采用迭代方案 printf("删除节点:%#x key<%d>完成\n", _pTarget, _pTarget->m_Key); delete _pTarget;//删除目标节点 m_iSize--;//删除节点计数器减1 return true; } //场景1单独处理函数 //场景1:被删除节点是红色 //场景1.1:被删除节点是叶子节点(2个孩子都是NIL) //场景1.2:被删除节点有两个黑色子节点(非NIL) RBTreeNode<KEY, VALUE>* remove_node_red(RBTreeNode<KEY, VALUE>* _pTarget) { RBTreeNode<KEY, VALUE>* pParent = _pTarget->m_pParent;//删除目标红色必然拥有父节点的,没有父亲说明是根节点,不可能存在红色根节点 if (_pTarget->m_pLeft->isNIL() && _pTarget->m_pRight->isNIL())//如果2个孩子都是NIL: 进入场景1.1 { //场景1.1, 可以直接删除,但删除操作不在此,我们只管把删除目标从红黑树中剥离出来 _pTarget->m_pParent = nullptr;//断开目标节点与父亲的联结 if (_pTarget->m_beLong == BELONG::LEFT)//如果删除目标是左孩子 { if (_pTarget->m_bIsDeleted)//如果已经被标记删除, 那么左孩子已经是NIL了 { pParent->m_pLeft = _pTarget->m_pLeft;//把父亲的左孩子重定向到NIL } } else//删除目标是右孩子 { if (_pTarget->m_bIsDeleted)//如果已经被标记删除, 那么右孩子已经是NIL了 { pParent->m_pRight = _pTarget->m_pRight;//把父亲的右孩子重定向到NIL } } } //else//2个孩子都是黑色, 为什么不能只有一个节点?删除目标是红色,如果只有一个孩子,无论这个孩子是黑色还是红色都违反红黑树性质 //{ //这个场景1.2不可能进入到此了,因为Erase内已经经过前驱/后继调整过 //直接以1个NIL孩子条件跳转到了场景2或场景3 //} return nullptr;//告诉上级函数, 目标已经从红黑树剥离,不用向上修复树结构 } //场景2单独处理函数 //场景2:被删除节点是黑色, 且其兄弟是红色 RBTreeNode<KEY, VALUE>* remove_node_black_2(RBTreeNode<KEY, VALUE>* _pTarget) { //兄弟设置为黑, 父亲设置为红, 然后 左左 或者 右右, 递归给场景3 RBTreeNode<KEY, VALUE>* pParent = _pTarget->m_pParent;//取出父亲 RBTreeNode<KEY, VALUE>* pBrother = _pTarget->m_beLong == BELONG::LEFT ? pParent->m_pRight : pParent->m_pLeft;//取出兄弟 //该如何判断肯定pBrother节点的存在性? 这已经给红黑树性质约束了: // 除根节点外,每个黑色节点都必须有非NIL的兄弟节点。这是由红黑树的性质5(黑色高度一致) //所以:每个黑色节点,除了根节点外,这个黑色节点必定会存在一个非NIL的兄弟 //这个兄弟节点是绝对存在的 //如果拥有一个红色孩子,不可能只拥有一个黑色孩子(非NIL) //if (_pTarget->m_pLeft->isNIL() == false || _pTarget->m_pRight->isNIL() == false) //{ //return remove_node_black_3_1_1(_pTarget); //} //否则删除目标2个孩子都是NIL //父兄颜色交换 pBrother->m_Color = COLOR::BLACK; pParent->m_Color = COLOR::RED; if (_pTarget->m_beLong == BELONG::LEFT)//如果目标是左孩子 { if (left_rotate(pParent))//父亲左旋 { return _pTarget;//迭代给场景3 } //else左旋失败 } else//不然就是右孩子,一个有父亲的,不可能是根节点ROOT { if (right_rotate(pParent))//父亲右旋 { return _pTarget;//迭代给场景3 } //else 右旋失败 } return nullptr; } //交换2个节点的key和value,不交换颜色,采用std::swap, 触发std::move, 不执行对象的复制构造函数 void swapKeys(RBTreeNode<KEY, VALUE>* node1, RBTreeNode<KEY, VALUE>* node2) { if (!node1 || !node2 || node1->isNIL() || node2->isNIL()) return; // 交换键值 std::swap(node1->m_Key, node2->m_Key); // 交换值 std::swap(node1->m_Value, node2->m_Value); } //查找前驱 //左的最右 RBTreeNode<KEY, VALUE>* predecessor(RBTreeNode<KEY, VALUE>* _pLeft) { RBTreeNode<KEY, VALUE>* pCurrent = _pLeft; RBTreeNode<KEY, VALUE>* pPre = nullptr; while (pCurrent->isNIL() == false) { pPre = pCurrent; pCurrent = pCurrent->m_pRight; } return pPre; } //查找后继节点 //右的最左 RBTreeNode<KEY, VALUE>* sucessor(RBTreeNode<KEY, VALUE>* _pRight) { RBTreeNode<KEY, VALUE>* pCurrent = _pRight; RBTreeNode<KEY, VALUE>* pPre = nullptr; while (pCurrent->isNIL() == false) { pPre = pCurrent; pCurrent = pCurrent->m_pLeft; } return pPre; } //场景3单独处理函数 //被删除节点是黑色,并且兄弟是黑色 RBTreeNode<KEY, VALUE>* remove_node_black_3(RBTreeNode<KEY, VALUE>* _pTarget) { RBTreeNode<KEY, VALUE>* pParent = _pTarget->m_pParent; RBTreeNode<KEY, VALUE>* pBrother = _pTarget->m_beLong == BELONG::LEFT ? pParent->m_pRight : pParent->m_pLeft;//取出兄弟 //该如何判断肯定pBrother节点的存在性? 这已经给红黑树性质约束了: 除根节点外,每个黑色节点都必须有非NIL的兄弟节点。这是由红黑树的性质5(黑色高度一致) //所以:每个黑色节点,除了根节点外,这个黑色节点必定会存在一个非NIL的兄弟 //当前目标是黑色, 所以这个兄弟节点是绝对存在的 //取得侄子节点,侄子是近侄还是远侄取决于删除目标在左边还是右边了 //快速判断: 近远侄子。如果删除目标是右孩子,那么右侄子必然就是近侄子了 //如果删除目标是左孩子,那么左侄子必然就是近侄子了 //取得近侄子 RBTreeNode<KEY, VALUE>* pNear = _pTarget->m_beLong==BELONG::RIGHT ? pBrother->m_pRight : pBrother->m_pLeft; //取得远侄子 RBTreeNode<KEY, VALUE>* pFar = _pTarget->m_beLong==BELONG::RIGHT ? pBrother->m_pLeft : pBrother->m_pRight; //场景3.1 - 兄弟2个子节点都是黑色(可以是NIL颜色占位判断) if (pNear->m_Color == COLOR::BLACK && pFar->m_Color == COLOR::BLACK) { return remove_node_black_3_1(_pTarget);//场景3.1单独分支处理 } //场景3.2 - 近侄子是红色,远侄子是黑色(黑色可以是NIL) else if (pNear->m_Color == COLOR::RED && pFar->m_Color == COLOR::BLACK) { return remove_node_black_3_2(_pTarget);//场景3.2单独分支处理 } //场景3.3 - 远侄子是红色 //场景3.4 - 兄弟节点的两个子节点都是红色 -- 如果2个节点都是红,说明远侄必然也是红了,所以合并场景 else { return remove_node_black_3_3(_pTarget);//进入场景3.3 } return nullptr; } //场景3:被删除节点是黑色, 且其兄弟是黑色 //场景3.1:兄弟节点的两个子节点都是黑色 //场景3.1.1:如果删除目标有1个孩子,那么这孩子必然是红色, 不可能是黑(非NIL) RBTreeNode<KEY, VALUE>* remove_node_black_3_1(RBTreeNode<KEY, VALUE>* _pTarget) { //场景3.1.1:如果删除目标有1个孩子,那么这孩子必然是红色, 不可能是黑(非NIL) //if (_pTarget->m_pLeft->isNIL() == false || _pTarget->m_pRight->isNIL() == false)//删除目标拥有一个红色的孩子 //{ //return remove_node_black_3_1_1(_pTarget);//迭代给上层 //} //场景3.1.2:如果删除目标有2个孩子,已经不可能,前期已经进行前驱/后继调整 //场景3.1.3:如果删除目标没有孩子,则去审查父亲节点 //else //{ return remove_node_black_3_1_3(_pTarget);//迭代给上层 //} return nullptr; } //场景3:被删除节点是黑色, 且其兄弟是黑色 //场景3.1:兄弟节点的两个子节点都是黑色 //场景3.1.1:如果删除目标有1个孩子,那么这孩子必然是红色, 不可能是黑(非NIL) RBTreeNode<KEY, VALUE>* remove_node_black_3_1_1(RBTreeNode<KEY, VALUE>* _pTarget) { /* * 处理过程: 1.把目标正常删除 2.把唯一的红色子节点和父节点连接 3.然后把红色染黑,完成节点删除 */ RBTreeNode<KEY, VALUE>* pParent = _pTarget->m_pParent;//取出父节点 RBTreeNode<KEY, VALUE>* pChild = _pTarget->m_pLeft->isNIL() == false ? _pTarget->m_pLeft : _pTarget->m_pRight;//取出孩子 //把删除目标剥离出来 _pTarget->m_pParent = nullptr;//断开与父亲的联结 _pTarget->m_pLeft = nullptr;//断开左孩子的联结 _pTarget->m_pRight = nullptr;//断开右孩子的联结 pChild->m_pParent = pParent;//唯一的红孩子重定向父亲 if (!pParent)//如果父节点不存在,说明删除目标就是根节点 { m_pRoot = pChild;//重定向根节点 pChild->m_beLong = BELONG::ROOT;//设置成根节点 pChild->m_Color = COLOR::BLACK;//设置成黑色 return nullptr;//不用迭代修复, 上层退出迭代 } if (_pTarget->m_beLong == BELONG::LEFT)//如果删除目标是左孩子 { pParent->m_pLeft = pChild;//重定向删除目标的左孩子 pChild->m_beLong = BELONG::LEFT;//设置成父亲的左孩子 } else//否则删除目标是右孩子 { pParent->m_pRight = pChild;//重定向删除目标的右孩子 pChild->m_beLong = BELONG::RIGHT;//设置成父亲的右孩子 } pChild->m_Color = COLOR::BLACK;//把唯一的红孩子设置成黑色 return nullptr;//不用迭代修复, 告诉上层正常删除目标 } //场景3:被删除节点是黑色, 且其兄弟是黑色 //场景3.1:兄弟节点的两个子节点都是黑色 //场景3.1.3:如果删除目标没有孩子,则去审查父亲节点 RBTreeNode<KEY, VALUE>* remove_node_black_3_1_3(RBTreeNode<KEY, VALUE>* _pTarget) { RBTreeNode<KEY, VALUE>* pParent = _pTarget->m_pParent;//取出父节点 RBTreeNode<KEY, VALUE>* pBrother = _pTarget->m_beLong == BELONG::LEFT ? pParent->m_pRight : pParent->m_pLeft;//取出兄弟 /* 分支1: 如果父亲节点是红色 处理步骤: 1.正常删除节点 2.把兄弟节点染红,父亲染黑(父兄交换颜色) 3.完成删除 */ if (pParent->m_Color == COLOR::RED) { _pTarget->m_pParent = nullptr;//断开与父亲的联结 if (_pTarget->m_beLong == BELONG::LEFT)//如果删除目标是左孩子 { if (_pTarget->m_bIsDeleted)//如果已经被标记删除, 那么左孩子已经是NIL了 { pParent->m_pLeft = _pTarget->m_pLeft;//重定向左路孩子到NIL } } else//否则删除目标是右孩子 { if (_pTarget->m_bIsDeleted)//如果已经被标记删除, 那么左孩子已经是NIL了 { pParent->m_pRight = _pTarget->m_pRight;//重定向右孩子到NIL } } pParent->m_Color = COLOR::BLACK; //父亲设置为黑色 pBrother->m_Color = COLOR::RED; //兄弟设置为红色 return nullptr;//不用迭代修复, 上层退出迭代 } /* 分支2: 如果父亲节点是黑色 处理步骤: 1.正常删除节点 2.将兄弟节点设为红色 3.将当前节点上移到父节点 4.把父亲当成删除目标, 迭代继续修复过程 */ else { _pTarget->m_pParent = nullptr;//断开与父亲的联结 if (_pTarget->m_beLong == BELONG::LEFT)//如果删除目标是左孩子 { if (_pTarget->m_bIsDeleted)//如果已经被标记删除, 那么左孩子已经是NIL了 { pParent->m_pLeft = _pTarget->m_pLeft;//重定向左路孩子到NIL } } else//否则删除目标是右孩子 { if (_pTarget->m_bIsDeleted)//如果已经被标记删除, 那么右孩子已经是NIL了 { pParent->m_pRight = _pTarget->m_pRight;//重定向右孩子到NIL } } pBrother->m_Color = COLOR::RED; //兄弟设置为红色 return pParent->m_beLong!=BELONG::ROOT ? pParent : nullptr;//把父亲当删除目标进行迭代修复树,如果是根节点,则上层退出迭代 } } // 场景3.2:近侄子是红色,远侄子是黑色(黑色可以是NIL) // 场景3.2.1:如果删除目标有1个孩子, 那么这孩子必然是红色,不可能是黑(非NIL) // 场景3.2.2:如果删除目标有2个孩子, 已经不可能,前期已经进行前驱/后继调整 // 场景3.2.3:如果删除目标没有孩子,则去审查父亲节点-父亲节点必定是红色 RBTreeNode<KEY, VALUE>* remove_node_black_3_2(RBTreeNode<KEY, VALUE>* _pTarget) { RBTreeNode<KEY, VALUE>* pParent = _pTarget->m_pParent;//取出父节点 RBTreeNode<KEY, VALUE>* pBrother = _pTarget->m_beLong == BELONG::LEFT ? pParent->m_pRight : pParent->m_pLeft;//取出兄弟 RBTreeNode<KEY, VALUE>* pNear = _pTarget->m_beLong == BELONG::RIGHT ? pBrother->m_pRight : pBrother->m_pLeft;//取得近侄子 //场景3.2.1:如果删除目标有1个孩子,那么这孩子必然是红色, 不可能是黑(非NIL) //if (_pTarget->m_pLeft->isNIL() == false || _pTarget->m_pRight->isNIL() == false)//删除目标拥有一个红色的孩子 //{ //return remove_node_black_3_1_1(_pTarget);//迭代给上层,因为场景3.1.1和场景3.2.1处理过程是一样的 //} //else //{ //场景3.2.3 //父亲必定是红色,所以不用重复审查,直接进行处理过程 //执行过程: //1.不要剥离目标, 只把兄弟进入旋转操作 //2.把兄弟设置红色, 近侄子设置黑色 //3.如果删除目标是左孩子 - 兄右旋, 如果删除目标是右孩子 - 兄左旋。最终得到场景3.3 //4.迭代执行删除目标节点 - 进入场景3.3 pBrother->m_Color = COLOR::RED; //兄弟设置为红色 pNear->m_Color = COLOR::BLACK; //近侄子设置为黑色 if (_pTarget->m_beLong == BELONG::LEFT)//如果删除目标是左孩子 { if (right_rotate(pBrother))//兄弟右旋,得到场景3.3 { return _pTarget;//迭代执行删除目标节点 - 进入场景3.3 } } else//否则删除目标是右孩子 { if (left_rotate(pBrother))//兄弟左旋,得到场景3.3 { return _pTarget;//迭代执行删除目标节点 - 进入场景3.3 } } //} return nullptr; } //场景3:被删除节点是黑色, 且其兄弟是黑色 //场景3.3:远侄子是红色,近侄子是黑色(黑色可以是NIL)或者红色都可以 //场景3.3没有其它分支 RBTreeNode<KEY, VALUE>* remove_node_black_3_3(RBTreeNode<KEY, VALUE>* _pTarget) { RBTreeNode<KEY, VALUE>* pParent = _pTarget->m_pParent;//取出父节点 RBTreeNode<KEY, VALUE>* pBrother = _pTarget->m_beLong == BELONG::LEFT ? pParent->m_pRight : pParent->m_pLeft;//取出兄弟 RBTreeNode<KEY, VALUE>* pFar = _pTarget->m_beLong == BELONG::RIGHT ? pBrother->m_pLeft : pBrother->m_pRight;//取得远侄子 //剥离删除目标 _pTarget->m_pParent = nullptr;//断开与父亲的联结 //处理步骤: //1.将兄弟设为红色 //2.将父亲设为黑色 //3.将远侄子设为黑色 //4.如果删除目标是左孩子,对父节点执行左旋 //如果删除目标是右孩子,对父节点执行右旋 //5.修复完成,退出循环 pBrother->m_Color = COLOR::RED;//兄弟设置红色 pParent->m_Color = COLOR::BLACK;//父亲设置黑色 pFar->m_Color = COLOR::BLACK;//远侄子设置黑色 if (_pTarget->m_beLong == BELONG::LEFT)//如果删除目标是左孩子 { if (_pTarget->m_bIsDeleted)//如果删除目标已经被标记为删除 { pParent->m_pLeft = _pTarget->m_pLeft;//重定向父亲的左孩子到NIL } if (left_rotate(pParent))//父节点左旋, 完成修复 { return nullptr; } } else//否则删除目标是右孩子 { if (_pTarget->m_bIsDeleted)//如果删除目标已经被标记为删除 { pParent->m_pRight = _pTarget->m_pRight;//重定向父亲的右孩子到NIL } if (right_rotate(pParent))//父亲右旋,完成修复 { return nullptr; } } return nullptr;//修复完成 } }; } #endif // !_RBTREE_H_

大家在看

recommend-type

ELEC5208 Group project submissions.zip_furniturer4m_smart grid_悉

悉尼大学ELEC5208智能电网project的很多组的报告和code都在里面,供学习和参考
recommend-type

基于python单通道脑电信号的自动睡眠分期研究

【作品名称】:基于python单通道脑电信号的自动睡眠分期研究 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【项目介绍】:网络结构(具体可查看network.py文件): 网络整体结构类似于TinySleepNet,对RNN部分进行了修改,增加了双向RNN、GRU、Attention等网络结构,可根据参数进行调整选择。 定义了seq_len参数,可以更灵活地调整batch_size与seq_len。 数据集加载(具体可查看dataset.py文件) 直接继承自torch的Dataset,并定义了seq_len和shuffle_seed,方便调整输入,并复现实验。 训练(具体可查看train.py文件): 定义并使用了focal loss损失函数 在实验中有使用wandb,感觉用起来还挺方便的,非常便于实验记录追溯 测试(具体可查看test.py文件): 可以输出accuracy、mf1、recall_confusion_matrics、precision_confusion_matrics、f1
recommend-type

bid格式文件电子标书阅读器.zip

软件介绍: bid格式招投标文件阅读器,可以打开浏览、管理电子招标文件,如果打不开标书文件,请按下面步骤检查:1、请查看招标文件(.bid文件)是否下载完全,请用IE下载工具下载;2、查看IE浏览器版本,如果版本低于IE8,低于IE8版本的请升级为IE8浏览器。
recommend-type

机器翻译WMT14数据集

机器翻译WMT14数据集,ACL2014公布的share task,很多模型都在这上benchmark
recommend-type

高通QXDM使用手册.pdf

高通QXDM使用手册,介绍高通QXDM工具软件的使用,中文版的哦。

最新推荐

recommend-type

C#类库封装:简化SDK调用实现多功能集成,构建地磅无人值守系统

内容概要:本文介绍了利用C#类库封装多个硬件设备的SDK接口,实现一系列复杂功能的一键式调用。具体功能包括身份证信息读取、人证识别、车牌识别(支持臻识和海康摄像头)、LED显示屏文字输出、称重数据读取、二维码扫描以及语音播报。所有功能均被封装为简单的API,极大降低了开发者的工作量和技术门槛。文中详细展示了各个功能的具体实现方式及其应用场景,如身份证读取、人证核验、车牌识别等,并最终将这些功能整合到一起,形成了一套完整的地磅称重无人值守系统解决方案。 适合人群:具有一定C#编程经验的技术人员,尤其是需要快速集成多种硬件设备SDK的应用开发者。 使用场景及目标:适用于需要高效集成多种硬件设备SDK的项目,特别是那些涉及身份验证、车辆管理、物流仓储等领域的企业级应用。通过使用这些封装好的API,可以大大缩短开发周期,降低维护成本,提高系统的稳定性和易用性。 其他说明:虽然封装后的API极大地简化了开发流程,但对于一些特殊的业务需求,仍然可能需要深入研究底层SDK。此外,在实际部署过程中,还需考虑网络环境、硬件兼容性等因素的影响。
recommend-type

基于STM32F1的BLDC无刷直流电机与PMSM永磁同步电机源码解析:传感器与无传感器驱动详解

基于STM32F1的BLDC无刷直流电机和PMSM永磁同步电机的驱动实现方法,涵盖了有传感器和无传感两种驱动方式。对于BLDC电机,有传感器部分采用霍尔传感器进行六步换相,无传感部分则利用反电动势过零点检测实现换相。对于PMSM电机,有传感器部分包括霍尔传感器和编码器的方式,无传感部分则采用了滑模观测器进行矢量控制(FOC)。文中不仅提供了详细的代码片段,还分享了许多调试经验和技巧。 适合人群:具有一定嵌入式系统和电机控制基础知识的研发人员和技术爱好者。 使用场景及目标:适用于需要深入了解和实现BLDC和PMSM电机驱动的开发者,帮助他们掌握不同传感器条件下的电机控制技术和优化方法。 其他说明:文章强调了实际调试过程中可能遇到的问题及其解决方案,如霍尔传感器的中断触发换相、反电动势过零点检测的采样时机、滑模观测器的参数调整以及编码器的ABZ解码等。
recommend-type

基于Java的跨平台图像处理软件ImageJ:多功能图像编辑与分析工具

内容概要:本文介绍了基于Java的图像处理软件ImageJ,详细阐述了它的跨平台特性、多线程处理能力及其丰富的图像处理功能。ImageJ由美国国立卫生研究院开发,能够在多种操作系统上运行,包括Windows、Mac OS、Linux等。它支持多种图像格式,如TIFF、PNG、GIF、JPEG、BMP、DICOM、FITS等,并提供图像栈功能,允许多个图像在同一窗口中进行并行处理。此外,ImageJ还提供了诸如缩放、旋转、扭曲、平滑处理等基本操作,以及区域和像素统计、间距、角度计算等高级功能。这些特性使ImageJ成为科研、医学、生物等多个领域的理想选择。 适合人群:需要进行图像处理的专业人士,如科研人员、医生、生物学家,以及对图像处理感兴趣的普通用户。 使用场景及目标:适用于需要高效处理大量图像数据的场合,特别是在科研、医学、生物学等领域。用户可以通过ImageJ进行图像的编辑、分析、处理和保存,提高工作效率。 其他说明:ImageJ不仅功能强大,而且操作简单,用户无需安装额外的运行环境即可直接使用。其基于Java的开发方式确保了不同操作系统之间的兼容性和一致性。
recommend-type

MATLAB语音识别系统:基于GUI的数字0-9识别及深度学习模型应用 · GUI v1.2

内容概要:本文介绍了一款基于MATLAB的语音识别系统,主要功能是识别数字0到9。该系统采用图形用户界面(GUI),方便用户操作,并配有详尽的代码注释和开发报告。文中详细描述了系统的各个组成部分,包括音频采集、信号处理、特征提取、模型训练和预测等关键环节。此外,还讨论了MATLAB在此项目中的优势及其面临的挑战,如提高识别率和处理背景噪音等问题。最后,通过对各模块的工作原理和技术细节的总结,为未来的研究和发展提供了宝贵的参考资料。 适合人群:对语音识别技术和MATLAB感兴趣的初学者、学生或研究人员。 使用场景及目标:适用于希望深入了解语音识别技术原理的人群,特别是希望通过实际案例掌握MATLAB编程技巧的学习者。目标是在实践中学习如何构建简单的语音识别应用程序。 其他说明:该程序需要MATLAB 2019b及以上版本才能正常运行,建议使用者确保软件环境符合要求。
recommend-type

c语言通讯录管理系统源码.zip

C语言项目源码
recommend-type

Teleport Pro教程:轻松复制网站内容

标题中提到的“复制别人网站的软件”指向的是一种能够下载整个网站或者网站的特定部分,然后在本地或者另一个服务器上重建该网站的技术或工具。这类软件通常被称作网站克隆工具或者网站镜像工具。 描述中提到了一个具体的教程网址,并提到了“天天给力信誉店”,这可能意味着有相关的教程或资源可以在这个网店中获取。但是这里并没有提供实际的教程内容,仅给出了网店的链接。需要注意的是,根据互联网法律法规,复制他人网站内容并用于自己的商业目的可能构成侵权,因此在此类工具的使用中需要谨慎,并确保遵守相关法律法规。 标签“复制 别人 网站 软件”明确指出了这个工具的主要功能,即复制他人网站的软件。 文件名称列表中列出了“Teleport Pro”,这是一款具体的网站下载工具。Teleport Pro是由Tennyson Maxwell公司开发的网站镜像工具,允许用户下载一个网站的本地副本,包括HTML页面、图片和其他资源文件。用户可以通过指定开始的URL,并设置各种选项来决定下载网站的哪些部分。该工具能够帮助开发者、设计师或内容分析人员在没有互联网连接的情况下对网站进行离线浏览和分析。 从知识点的角度来看,Teleport Pro作为一个网站克隆工具,具备以下功能和知识点: 1. 网站下载:Teleport Pro可以下载整个网站或特定网页。用户可以设定下载的深度,例如仅下载首页及其链接的页面,或者下载所有可访问的页面。 2. 断点续传:如果在下载过程中发生中断,Teleport Pro可以从中断的地方继续下载,无需重新开始。 3. 过滤器设置:用户可以根据特定的规则过滤下载内容,如排除某些文件类型或域名。 4. 网站结构分析:Teleport Pro可以分析网站的链接结构,并允许用户查看网站的结构图。 5. 自定义下载:用户可以自定义下载任务,例如仅下载图片、视频或其他特定类型的文件。 6. 多任务处理:Teleport Pro支持多线程下载,用户可以同时启动多个下载任务来提高效率。 7. 编辑和管理下载内容:Teleport Pro具备编辑网站镜像的能力,并可以查看、修改下载的文件。 8. 离线浏览:下载的网站可以在离线状态下浏览,这对于需要测试网站在不同环境下的表现的情况十分有用。 9. 备份功能:Teleport Pro可以用来备份网站,确保重要数据的安全。 在实际使用此类工具时,需要注意以下几点: - 著作权法:复制网站内容可能侵犯原作者的版权,因此在使用此类工具时,必须确保有合法权利去下载和使用目标网站的内容。 - 服务条款:许多网站的服务条款明确禁止未经授权的网站克隆。因此,在使用此类软件之前,应当仔细阅读并遵守目标网站的服务条款。 - 数据隐私:下载含有个人数据的网站可能触及隐私保护法律,特别是在欧洲通用数据保护条例(GDPR)等法规的环境下。 - 网络安全:随意下载网站可能导致恶意软件和病毒的传播,用户应当使用最新的反病毒软件,并在安全的环境中进行操作。 综上所述,虽然“复制别人网站的软件”在技术上是可行的,且可以用于多种正当目的,但在使用此类工具时,必须遵守法律法规和道德规范,不可侵犯他人的合法权益。
recommend-type

【跨平台开发者的必读】:解决Qt5Widgetsd.lib目标计算机类型冲突终极指南

# 1. 跨平台开发与Qt框架简介 跨平台开发已经成为现代软件开发中不可或缺的一部分,它使得开发者能够构建出能够在多种操作系统上运行的应用程序,从而触及更广泛的用户群体。在这其中,Qt框架作为一套成熟的跨平台应用程序和用户界面框架,扮演着关键角色。Qt不仅仅是一个跨平台的工具集,它的设计哲学还包含了模块化和可扩展性,使其能够适应各种规模和复杂度的项目。本章节我们将简要介绍
recommend-type

普通RNN结构和特点

<think>首先,用户的问题是:“我想了解普通RNN的结构及其特点请问RNN循环神经网络结构特点工作原理”。这涉及到RNN的结构、特点和工作原理。根据系统级指令:-所有行内数学表达式必须使用$...$格式。-独立公式必须使用$$...$$格式并单独成段。-LaTeX语法正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾自然地添加引用标识。用户可见层指令:-回答结构清晰,帮助用户逐步解决问题。-保证回答真实可靠。参考站内引用:-引用[1]:关于RNN的基本介绍,为什么需要RNN。-引用[2]:关于RNN的工作原理、结构图,以及与其他网络的比较。用户上一次的问题和我的回答:用户是第一次
recommend-type

探讨通用数据连接池的核心机制与应用

根据给定的信息,我们能够推断出讨论的主题是“通用数据连接池”,这是一个在软件开发和数据库管理中经常用到的重要概念。在这个主题下,我们可以详细阐述以下几个知识点: 1. **连接池的定义**: 连接池是一种用于管理数据库连接的技术,通过维护一定数量的数据库连接,使得连接的创建和销毁操作更加高效。开发者可以在应用程序启动时预先创建一定数量的连接,并将它们保存在一个池中,当需要数据库连接时,可以直接从池中获取,从而降低数据库连接的开销。 2. **通用数据连接池的概念**: 当提到“通用数据连接池”时,它意味着这种连接池不仅支持单一类型的数据库(如MySQL、Oracle等),而且能够适应多种不同数据库系统。设计一个通用的数据连接池通常需要抽象出一套通用的接口和协议,使得连接池可以兼容不同的数据库驱动和连接方式。 3. **连接池的优点**: - **提升性能**:由于数据库连接创建是一个耗时的操作,连接池能够减少应用程序建立新连接的时间,从而提高性能。 - **资源复用**:数据库连接是昂贵的资源,通过连接池,可以最大化现有连接的使用,避免了连接频繁创建和销毁导致的资源浪费。 - **控制并发连接数**:连接池可以限制对数据库的并发访问,防止过载,确保数据库系统的稳定运行。 4. **连接池的关键参数**: - **最大连接数**:池中能够创建的最大连接数。 - **最小空闲连接数**:池中保持的最小空闲连接数,以应对突发的连接请求。 - **连接超时时间**:连接在池中保持空闲的最大时间。 - **事务处理**:连接池需要能够管理不同事务的上下文,保证事务的正确执行。 5. **实现通用数据连接池的挑战**: 实现一个通用的连接池需要考虑到不同数据库的连接协议和操作差异。例如,不同的数据库可能有不同的SQL方言、认证机制、连接属性设置等。因此,通用连接池需要能够提供足够的灵活性,允许用户配置特定数据库的参数。 6. **数据连接池的应用场景**: - **Web应用**:在Web应用中,为了处理大量的用户请求,数据库连接池可以保证数据库连接的快速复用。 - **批处理应用**:在需要大量读写数据库的批处理作业中,连接池有助于提高整体作业的效率。 - **微服务架构**:在微服务架构中,每个服务可能都需要与数据库进行交互,通用连接池能够帮助简化服务的数据库连接管理。 7. **常见的通用数据连接池技术**: - **Apache DBCP**:Apache的一个Java数据库连接池库。 - **C3P0**:一个提供数据库连接池和控制工具的开源Java框架。 - **HikariCP**:目前性能最好的开源Java数据库连接池之一。 - **BoneCP**:一个高性能的开源Java数据库连接池。 - **Druid**:阿里巴巴开源的一个数据库连接池,提供了对性能监控的高级特性。 8. **连接池的管理与监控**: 为了保证连接池的稳定运行,开发者需要对连接池的状态进行监控,并对其进行适当的管理。监控指标可能包括当前活动的连接数、空闲的连接数、等待获取连接的请求队列长度等。一些连接池提供了监控工具或与监控系统集成的能力。 9. **连接池的配置和优化**: 连接池的性能与连接池的配置密切相关。需要根据实际的应用负载和数据库性能来调整连接池的参数。例如,在高并发的场景下,可能需要增加连接池中连接的数量。另外,适当的线程池策略也可以帮助连接池更好地服务于多线程环境。 10. **连接池的应用案例**: 一个典型的案例是电商平台在大型促销活动期间,用户访问量激增,此时通用数据连接池能够保证数据库操作的快速响应,减少因数据库连接问题导致的系统瓶颈。 总结来说,通用数据连接池是现代软件架构中的重要组件,它通过提供高效的数据库连接管理,增强了软件系统的性能和稳定性。了解和掌握连接池的原理及实践,对于任何涉及数据库交互的应用开发都至关重要。在实现和应用连接池时,需要关注其设计的通用性、配置的合理性以及管理的有效性,确保在不同的应用场景下都能发挥出最大的效能。
recommend-type

【LabVIEW网络通讯终极指南】:7个技巧提升UDP性能和安全性

# 摘要 本文系统介绍了LabVIEW在网络通讯中的应用,尤其是针对UDP协议的研究与优化。首先,阐述了UDP的原理、特点及其在LabVIEW中的基础应用。随后,本文深入探讨了通过调整数据包大小、实现并发通信及优化缓冲区管理等技巧来优化UDP性能的LabVIEW方法。接着,文章聚焦于提升UDP通信安全性,介绍了加密技术和认证授权机制在LabVIEW中的实现,以及防御网络攻击的策略。最后,通过具体案例展示了LabVIEW在实时数据采集和远程控制系统中的高级应用,并展望了LabVIEW与UDP通讯技术的未来发展趋势及新兴技术的影响。 # 关键字 LabVIEW;UDP网络通讯;性能优化;安全性;