红黑树
Posted unknowcodemaker
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树相关的知识,希望对你有一定的参考价值。
性质
红黑树是满足下列性质的二叉树:
-
树中只有红色的节点和黑色的节点
-
根节点是黑色的
-
外部节点(NIL)都是黑色的
注意:这里的外部节点指的是这样的节点:
为了节省空间可以使他们指向同一个外部节点
对于C/C++语言来说可以不设外部节点,如果一个节点没有子节点,可以将其节点的指针域置为空,可以视空节点
为外部节点 -
如果一个节点是红色的,那么它的左右孩子节点必须是黑色的
-
对于每个节点,从该节点到其所有后代外部节点的简单路径上,所经过黑色节点的个数相同,也称为"黑高"
根据上述性质,使用C++语言定义树节点如下:
#define RED true #define BLACK false template<typename T> struct tagRBNode struct tagRBNode<T> * m_pLeft; //指向左孩子 struct tagRBNode<T> * m_pRight; //指向右孩子 struct tagRBNode<T> * m_pParent;//指向父节点 bool m_bRed; //节点颜色 T m_TDataElemet; //数据域 tagRBNode(T & data,bool bRed = RED): m_pParent(nullptr), m_pLeft(nullptr), m_pRight(nullptr), m_TDataElemet(data), m_bRed(bRed), ; /*定义模版节点类型别名*/ template<typename T> using RBNode = tagRBNode<T>; /*定义模版节点指针类型别名*/ template<typename T> using PRBNode = tagRBNode<T>*;
红黑树类模版定义如下:
template<typename T> class CRBTree PRBNode<T> m_Root; //指向根节点 int m_nNumOfNode; //记录节点个数
旋转节点
S1,S2,S3指代整个子树,X表示待旋转节点的父节点
- 左旋
旋转前:
旋转后:
左旋代码实现:
/************************************************************************ // 函数名称: CRBTree<T>::LRotate // 访问权限: private // 函数功能: 对一对父子节点进行左旋 // 返回值: bool:成功返回true,失败返回false // 参数: PRBNode<T> ParentOfPair:指向这对带旋转节点中的父节点 // 注意: ************************************************************************/ template<typename T> bool CRBTree<T>::LRotate(PRBNode<T> ParentOfPair) PRBNode<T> RChidOfPair = ParentOfPair->m_pRight; PRBNode<T> SubRoot = nullptr; if (ParentOfPair->m_pRight == nullptr) /*待旋转的父节点无右孩子,此时无法进行左旋*/ return false; SubRoot = ParentOfPair->m_pParent; /*将这对待旋转节点中的子节点的左孩子置为待旋转节点父节点的右孩子*/ ParentOfPair->m_pRight = RChidOfPair->m_pLeft; if (RChidOfPair->m_pLeft != nullptr) RChidOfPair->m_pLeft->m_pParent = ParentOfPair; /*将这对待旋转节点中的父节点置为待旋转子节点的左孩子*/ RChidOfPair->m_pLeft = ParentOfPair; ParentOfPair->m_pParent = RChidOfPair; /*重置这对待旋转节点中的子节点的父节点*/ RChidOfPair->m_pParent = SubRoot; if (SubRoot == nullptr) m_Root = RChidOfPair; else if(ParentOfPair == SubRoot->m_pLeft) SubRoot->m_pLeft = RChidOfPair; else SubRoot->m_pRight = RChidOfPair; return true;
- 右旋
旋转前:
旋转后:
右旋代码实现:
/************************************************************************ // 函数名称: CRBTree<T>::RRotate // 访问权限: private // 函数功能: 对一堆父子节点进行右旋 // 返回值: bool:成功返回true,失败返回false // 参数: PRBNode<T> ParentOfPair:指向这对带旋转节点中的父节点 // 注意: ************************************************************************/ template<typename T> bool CRBTree<T>::RRotate(PRBNode<T> ParentOfPair) PRBNode<T> LChildOfPair = ParentOfPair->m_pLeft; PRBNode<T> SubRoot = ParentOfPair->m_pParent; if (LChildOfPair == nullptr) return false; /*将这对待旋转节点的子节点的右孩子置为带旋转父节点的左孩子*/ ParentOfPair->m_pLeft = LChildOfPair->m_pRight; if (LChildOfPair->m_pRight != nullptr) LChildOfPair->m_pRight->m_pParent = ParentOfPair; /*颠倒这对待旋转节点的父子关系*/ LChildOfPair->m_pRight = ParentOfPair; ParentOfPair->m_pParent = LChildOfPair; /*重置旋转后父节点的父节点*/ LChildOfPair->m_pParent = SubRoot; if (SubRoot == nullptr) m_Root = LChildOfPair; else if (SubRoot->m_pLeft == ParentOfPair) SubRoot->m_pLeft = LChildOfPair; else SubRoot->m_pRight = LChildOfPair; return true;
插入节点
如果红黑树为空则直接插入即可,如果红黑树不为空,则需要使用类似"二分查找"的方式来找到合适的插入位置,
插入前节点需要着色,一般都是设置为红色,因为一个非空红黑树从根节点到任意叶节点的黑高是相同的,所以插入
一个红色的节点不会影响所在路径的黑高(性质5),可能会造成与其父节点都是红色(性质4);但是如果讲新节点着
黑色插入,那么一定会影响所在路径的黑高,则每次插入都必须做出调整
下面给出插入的实现代码:
/************************************************************************ // 函数名称: CRBTree<T>::Insert // 访问权限: public // 函数功能: 插入新节点 // 返回值: bool:成功返回true,失败返回false // 参数: T & & data:要插入的节点元素 // 注意: 不支持重复节点值插入 ************************************************************************/ template<typename T> bool CRBTree<T>::Insert(T && data) PRBNode<T> pParentOfNewNode = nullptr; PRBNode<T> pCurrentNode = m_Root; while (pCurrentNode != nullptr) pParentOfNewNode = pCurrentNode; if (pCurrentNode->m_TDataElemet > data) /*当前节点值大于待插入的节点值则当前节点的左侧寻找插入位置*/ pCurrentNode = pCurrentNode->m_pLeft; else if (pCurrentNode->m_TDataElemet < data) /*当前节点值小于待插入的节点值则当前节点的右侧寻找插入位置*/ pCurrentNode = pCurrentNode->m_pRight; else /*树中已经有此节点值则返回失败*/ return false; PRBNode<T> pNewNode = new RBNode<T>(data); if (pNewNode == nullptr) return false; if (pParentOfNewNode == nullptr) m_Root = pNewNode; m_nNumOfNode++; else if (pParentOfNewNode->m_TDataElemet > data) pParentOfNewNode->m_pLeft = pNewNode; else pParentOfNewNode->m_pRight = pNewNode; pNewNode->m_pParent = pParentOfNewNode; m_nNumOfNode++; return FixAfterInsert(pNewNode);
因为每次插入的新节点的颜色都是红色,如果是一颗空树插入根节点后,会破坏性质2;如果不是空树,插入后可能破
坏性质4;也有可能不破坏性质4,如果插入后新节点的父节点为黑色;所以插入新节点后要维持红黑树的性质,则从
维护这两个性质开始:
- 如果新节点的父节点是其祖父节点左孩子
Case0:向空树插入根节点,或者调整时根节点被置为红色,则直接将根节点置为黑色
Case1:插入后,待调整节点的父节点为红色,并且待调整节点的叔节点也为红色,此时待调整节点的祖父节点必定是
黑色的,那么将父节点和叔节点置为黑色,将祖父节点置为红色;但是祖父节点的父节点有可能红色的,所以
调整位置要移动到祖父节点,继续向上调整,可以看出维持红黑树性质的算法中肯定有一个循环
Case2:插入后,待调整节点的父节点为红色,并且待调整节点的叔节点为黑色(无叔节点也视为黑色),并且待调整节
点是其父节点的右孩子,那么此时需要对待调整节点和其父节点进行一次左旋
Case3:插入后(或者经过Case2调整后),待调整节点的父节点为红色,并且待调整节点的叔节点为黑色(无叔节点也
视为黑色),此时将待调整节点的父节点置为黑色,将待调整节点的祖父节点置为红色,并对父节点和祖父节
点进行一次右旋
- 如果新节点的父节点是其祖父节点右孩子
Case2:插入后,待调整节点的父节点为红色,并且待调整节点的叔节点为黑色(无叔节点也视为黑色),并且待调整节
点是其父节点的左孩子,那么此时需要对待调整节点和其父节点进行一次右旋
Case3:插入后(或者经过Case2调整后),待调整节点的父节点为红色,并且待调整节点的叔节点为黑色(无叔节点也
视为黑色),此时将待调整节点的父节点置为黑色,将待调整节点的祖父节点置为红色,并对父节点和祖父节
点进行一次左旋
根据上面的几种情形得出下面是插入后维持红黑树性质的代码:
template<typename T> bool CRBTree<T>::FixAfterInsert(PRBNode<T> pNewNode) if (nullptr == pNewNode) return false; PRBNode<T> pAdjustNode = pNewNode; PRBNode<T> pParent = nullptr; PRBNode<T> pGrandParent = nullptr; PRBNode<T> pUncle = nullptr; while ((pParent = pAdjustNode->m_pParent) && pParent->m_bRed) pGrandParent = pParent->m_pParent; if (pGrandParent->m_pLeft == pParent) /*父节点为祖父节点的左孩子*/ pUncle = pGrandParent->m_pRight; if (pUncle != nullptr && pUncle->m_bRed) /*Case1:待调整节点的叔节点为红色,则将待调整节点的父节点和叔节点置为黑色, 并将祖父节点置为红色,并将下次待调整节点置为祖父节点*/ pUncle->m_bRed = BLACK; pParent->m_bRed = BLACK; pGrandParent->m_bRed = RED; pAdjustNode = pGrandParent; else if (pAdjustNode == pParent->m_pRight) /*Case2:待调整节点的叔节点为黑色(无叔节点也视为黑色), 并且待调整节点为其父节点的右孩子,则进行左旋*/ pAdjustNode = pParent; LRotate(pAdjustNode); pParent = pAdjustNode->m_pParent; /*Case3:待调整节点的叔节点为黑色(无叔节点也视为黑色), 并且待调整节点为其父节点的左孩子,则先将待调整 节点的父节点置为黑色,将祖父节点置为红色,在对 祖父节点和父节点进行一次右旋*/ pParent->m_bRed = BLACK; pGrandParent->m_bRed = RED; RRotate(pGrandParent); else /*父节点为祖父节点的右孩子*/ pUncle = pGrandParent->m_pLeft; if (pUncle != nullptr && pUncle->m_bRed) /*Case1:待调整节点的叔节点为红色,则将待调整节点的父节点和叔节点置为黑色, 并将祖父节点置为红色,并将下次待调整节点置为祖父节点*/ pUncle->m_bRed = BLACK; pParent->m_bRed = BLACK; pGrandParent->m_bRed = RED; pAdjustNode = pGrandParent; else if (pAdjustNode == pParent->m_pLeft) /*Case2:待调整节点的叔节点为黑色(无叔节点也视为黑色), 并且待调整节点为其父节点的右孩子,则进行左旋*/ pAdjustNode = pParent; RRotate(pAdjustNode); pParent = pAdjustNode->m_pParent; /*Case3:待调整节点的叔节点为黑色(无叔节点也视为黑色), 并且待调整节点为其父节点的左孩子,则先将待调整 节点的父节点置为黑色,将祖父节点置为红色,在对 祖父节点和父节点进行一次右旋*/ pParent->m_bRed = BLACK; pGrandParent->m_bRed = RED; LRotate(pGrandParent); m_Root->m_bRed = BLACK; return true;
插入节点时间复杂度分析
插入前寻找插入位置的代码复杂度与红黑树的高度有关,所以需要找出节点总个数n与树高度h的关系;
设任意节点x的黑高为hb(x)(不小于0),先证明任意节点x的至少含有2^hb(x)-1个内部节点:
当hb(x)=0时,节点x的是外部节点或者叶节点;x为外部节点时,其内部节点的个数为0,x为叶节点时,x含有一个内
部节点就是它自己;则hb(x)=0时,至少含有2^hb(x)-1成立;
当节点x的hb(x)!=0时,也即hb(x)>0时,假设其左右孩子都存在,当孩子节点为红色时,孩子节点的黑高也为hb(x);
当孩子节点为黑色时,孩子节点的高度为hb(x)-1;不管节点x的孩子节点是红的还是黑的,那么孩子节点的黑高至少
是hb(x)-1,那么节点x含有外部节点的个数至少为(包括x自己):2^(hb(x)-1)-1+2^(hb(x)-1)-1+1,简化后即
为:2^hb(x)-1;这里有子树黑高hb(x)-1推出其父节点黑高为hb(x)时至少含2^hb(x)-1内部节点,所以有数学
归纳法可以证明任意节点x的至少含有2^hb(x)-1个内部节点是正确的
假设红黑树的高度为h节点总数为n,由红黑树的性质4可以推出从根节点到任意外部节点的简单路径上,黑节点的个
数至少为h/2,则说明高度为h的红黑树的黑高至少为h/2,则说明根节点至少含有2^(h/2)-1个内部节点,可以推出:
2^(h/2)-1<n,可推得2^(h/2)<n+1,不等式两边同时对2取对数后得:h/2<lg(n+1),最终可以得到高度h满足:
h<2lg(n+1),也就是说含n个节点的红黑树,高度至多为2lg(n+1)
红黑树也是一种二叉树,那么其查找节点的时间复杂度T(n)=O(2lg(n+1))=O(lgn),所以红黑树中插入节点前查找
代码时间复杂度为O(n);插入后为了维持红黑树的性质,需要沿着插入位置向根节点调整,如果违反了性质1,那么
重新着色和调整位置向上移动两层,所以循环最多执行lg(n+1)次;另外每次循环中旋转的次数不会超过两次,只要
进入了Case2和Case3执行两次旋转后,本次循环就结束了,所以维持插入后维持红黑树性质的代码时间复杂度为
O(lgn),所以插入总的时间复杂度为O(lgn)
删除节点
这里为了方便描述,称待删除节点为D,其左孩子为DL,右孩子为DR,其父节点为DP;用于替换待删除节点的节点称为X,
它的左孩子称为XL,右孩子称为XR,其父节点称为XP,红黑树节点的删除和二叉搜索树删除节点类似,分为以下三种
情况:
- 如果待删除的节点无左右孩子,则直接将其删除即可,并修改其父节点对应的孩子指针即可
上图中没有明显的画出D是父节点左孩子还是右孩子,这咩有关系,无论D是父节点左孩子还是右孩子处理方式都
一样 - 如果待删除节点只有一个孩子,直接用待删除节点的这个孩子来取代待删除节点,并重置相关指针指向即可
- 如果待删除节点两个孩子都存在,则从其右子树上寻找一个最小的节点来替换待删除节点,并重置相关指针指向即可
从待删除节点的右子树上找到的这个最小的节点,它一定没有左孩子,只要它有左孩子那么它就不是最小的节点,
可能会有右孩子
上图展现一个比较特殊的情况,当待删除节点的右孩子没有左孩子,那么待删除节点的右孩子就是待删除节点右
子树中节点值最小的节点,除了这种特殊情况外,更多是下图中的情况:
从上面三种情况可以看出节点的替换是一个常用操作,所以将此操作封装为一个成员函数:
/************************************************************************ // 函数名称: CRBTree<T>::ReplaceNode // 访问权限: private // 函数功能: 用pReplacer指向的节点替换pBeReplaced指向的节点 // 返回值: bool // 参数: PRBNode<T> pBeReplaced:待替换的节点 // 参数: PRBNode<T> pReplacer用于替换的节点 // 注意: pBeReplaced不能为空 ************************************************************************/ template<typename T> bool CRBTree<T>::ReplaceNode(PRBNode<T> pBeReplaced, PRBNode<T> pReplacer) if (pBeReplaced == nullptr) return false; if (pBeReplaced == m_Root) m_Root = pReplacer; else if (pBeReplaced->m_pParent->m_pLeft == pBeReplaced) pBeReplaced->m_pParent->m_pLeft = pReplacer; else pBeReplaced->m_pParent->m_pRight = pReplacer; if (pReplacer != nullptr) pReplacer->m_pParent = pBeReplaced->m_pParent; return true;
这个函数主要功能是建立DP(待删除节点的父节点)与X(用于替换的节点)间的父子关系,但是没有重置X与DL,DR
间的关系,如果要在这个函数中也完成X与DL,DR节点关系的建立,整个函数将会变得很复杂;
删除一个节点前,必须现在红黑树中寻找这个待删除节点,所以还需要实现一个查找的成员函数:
/************************************************************************ // 函数名称: CRBTree<T>::FindNodeByData // 访问权限: private // 函数功能: 查找树中是否存在元素值为data的节点 // 返回值: 返回指向该节点的指针,如果没有该节点则返回空指针 // 参数: T & data:待查找的元素 // 注意: ************************************************************************/ template<typename T> PRBNode<T> CRBTree<T>::FindNodeByData( T & data) PRBNode<T> pSearch = m_Root; while (pSearch != nullptr) if (data > pSearch->m_TDataElemet) pSearch = pSearch->m_pRight; else if (data < pSearch->m_TDataElemet) pSearch = pSearch->m_pLeft; else break; return pSearch;
两个辅助函数已经完成,下面则是删除节点函数的实现:
/************************************************************************ // 函数名称: CRBTree<T>::Delete // 访问权限: public // 函数功能: 删除一个节点元素为data的节点 // 返回值: bool:成功返回true,失败返回false // 参数: T & data:待查找的元素 // 注意: 1.data必须是一个左值,本函数用于支持左值作为参数时调用 ************************************************************************/ template<typename T> bool CRBTree<T>::Delete(T & data) /*先根据要查找的元素从红黑树中寻找元素所在的节点*/ PRBNode<T> pDelNode = FindNodeByData(data); PRBNode<T> pAdjustNode = nullptr; PRBNode<T> pParentOfReplacer = nullptr; if (pDelNode == nullptr) /*无此节点则返回失败*/ return false; RB_COLOR bColor = pDelNode->m_Color; if (pDelNode->m_pLeft == nullptr) /*待删除节点左孩子为空则使用其右孩子节点替代待删除节点*/ pAdjustNode = pDelNode->m_pRight; pParentOfReplacer = pDelNode->m_pParent; ReplaceNode(pDelNode, pAdjustNode); else if (pDelNode->m_pRight == nullptr) /*待删除节点右孩子为空则使用左孩子节点替代待删除节点*/ pAdjustNode = pDelNode->m_pLeft; pParentOfReplacer = pDelNode->m_pParent; ReplaceNode(pDelNode, pAdjustNode); else /*待删除节点左右孩子都存在,则使用右子树中节点值最小的节点替代待删除节点*/ PRBNode<T> pMinumNode = pDelNode->m_pRight; while (pMinumNode->m_pLeft != nullptr) pMinumNode = pMinumNode->m_pLeft; pParentOfReplacer = pMinumNode->m_pParent; bColor = pMinumNode->m_Color; pAdjustNode = pMinumNode->m_pRight; if (pDelNode->m_pRight != pMinumNode) /*用于替换的节点不是待删除节点的右孩子,而是在其右孩子的左子树上*/ /*先将用于替换的节点从它原来的位置上摘出来,因为用于替换的节点是待删除节点右子树中值最小 的节点,所以用于替换的节点没有左孩子,可能有右孩子*/ ReplaceNode(pMinumNode, pMinumNode->m_pRight); /*将待删除节点的右孩子置为用于替换的节点的右孩子*/ pMinumNode->m_pRight = pDelNode->m_pRight; if (pDelNode->m_pRight != nullptr) SET_PARENT(pMinumNode->m_pRight, pMinumNode); /*替换待删除节点*/ ReplaceNode(pDelNode, pMinumNode); /*将被删除节点的左孩子置为用于替换节点的左孩子*/ pMinumNode->m_pLeft = pDelNode->m_pLeft; if (pMinumNode->m_pLeft != nullptr) SET_PARENT(pMinumNode->m_pLeft, pMinumNode); SET_COLOR(pMinumNode, pDelNode->m_Color); if (bColor == BLACK) FixAfterDelete(pAdjustNode, pParentOfReplacer); delete pDelNode; return true; /************************************************************************ // 函数名称: CRBTree<T>::Delete // 访问权限: public // 函数功能: 删除一个节点元素为data的节点 // 返回值: bool:成功返回true,失败返回false // 参数: T & & data:待查找的元素 // 注意: 1.data必须是一个右值,本函数用于支持右值作为参数时调用 ************************************************************************/ template<typename T> bool CRBTree<T>::Delete(T && data) return Delete(data);
D被删除后,X坐在D原来的位置并且X的节点颜色被置为D的颜色;那么当X为红色时,用X取代D后不会影响X原来所在
简单路径上黑节点的个数,但是如果X为黑色时,一定会影响X原来所在简单路径上黑节点的个数,这时需要从X移走
前的位置开始向上进行调整,以维持红黑树性质;
- 当待调整节点为其父节点的左孩子时
Case1:待调整节点的兄弟节点为红色
调整方法:交换兄弟节点和其父节点的颜色,并对父节点进行一次左旋,左旋后待调整节点的兄弟节点发生了更
换,重新记录兄弟节点
因为调整前兄弟节点为红色,那么其孩子节点必须是黑色(不存在也视为黑色),经过左旋后,兄弟节点的左孩子
成为了待调整节点新的兄弟节点,此时Case1就转换为Case2,Case3或者Case4
Case2:待调整节点的兄弟节点是黑色的,父节点颜色任意,并且兄弟节点的左右孩子都为黑色
调整方法:将兄弟节点置为红色,并将父节点置为下一次的待调整节点
Case3:待调整节点的兄弟节点为黑色,并且兄弟节点的左孩子为红色,右孩子为黑色
调整方法:将兄弟节点的颜色和兄弟节点左孩子节点的颜色进行对换,然后对兄弟节点进行右旋
调整后兄弟节点的右孩子一定是红色的,此时Case3就转换成了Case4
Case4:待调整节点的兄弟节为黑色,并且兄弟节点的右孩子为红色,左孩子颜色任意
调整方法:将兄弟节点的颜色置为父节点的颜色,然后将父节点置为黑色,将兄弟节点的右孩子置为黑色,然后对
父节点进行左旋,最后将待调整节点置为根节点,如下图所示:
从上图可以看出此时待调整节点所在的子树已经满足红黑树的性质,但是节点D如果是根节点那么不满足性质2,
所以正对这种情况可以将待调整节点置为根节点即可 - 当待调整节点为其父节点的左孩子时
Case1:待调整节点的兄弟节点为红色
调整方法:交换兄弟节点和其父节点的颜色,并对父节点进行一次右旋,右旋后待调整节点的兄弟节点发生了更
换,重新记录兄弟节点
Case2:待调整节点的兄弟节点是黑色的,父节点颜色任意,并且兄弟节点的左右孩子都为黑色
调整方法:将兄弟节点置为红色,并将父节点置为下一次的待调整节点
Case3:待调整节点的兄弟节点为黑色,并且兄弟节点的左孩子为黑色,右孩子为红色
调整方法:将兄弟节点的颜色和兄弟节点右孩子节点的颜色进行对换,然后对兄弟节点进行左旋
Case4:待调整节点的兄弟节为黑色,并且兄弟节点的左孩子为红色,右孩子颜色任意
调整方法:将兄弟节点的颜色置为父节点的颜色,然后将父节点置为黑色,将兄弟节点的左孩子置为黑色,然后对
父节点进行右旋,最后将待调整节点置为根节点,如下图所示:
删除节点后调整代码的实现:
/************************************************************************ // 函数名称: CRBTree<T>::FixAfterDelete // 访问权限: private // 函数功能: 删除节点后,从pAdjustNode指向的节点开始进行调整 // 返回值: bool // 参数: PRBNode<T> pAdjustNode:待调整节点位置 // 参数: PRBNode<T> pParentOfAdjust:待调整节点的父节点 // 注意: ************************************************************************/ template<typename T> bool CRBTree<T>::FixAfterDelete(PRBNode<T> pAdjustNode, PRBNode<T> pParentOfAdjust) PRBNode<T> pBrother = nullptr; while ((pAdjustNode == nullptr || IS_BLACK_COLOR(pAdjustNode)) && pAdjustNode != m_Root) if (pAdjustNode == pParentOfAdjust->m_pLeft) pBrother = pParentOfAdjust->m_pRight; if (IS_RED_COLOR(pBrother)) /*Case1:待调整节点的兄弟节点是红色,则将兄弟节点置为黑色,将父节点置为红色, 并对父节点进行一次左旋,左旋后记录新的兄弟节点*/ SET_BLACK_COLOR(pBrother); SET_RED_COLOR(pBrother->m_pParent); LRotate(pBrother->m_pParent); pBrother = pParentOfAdjust->m_pRight; if ((pBrother->m_pLeft == nullptr || IS_BLACK_COLOR(pBrother->m_pLeft)) && (pBrother->m_pRight == nullptr || IS_BLACK_COLOR(pBrother->m_pRight))) /*Case2:待调整节点的兄弟节点的左右孩子均为黑色(孩子不存在也视为黑色),将兄弟节点 置为红色,并将待调整位置指向待调整节点的父节点*/ SET_RED_COLOR(pBrother); pAdjustNode = pParentOfAdjust; pParentOfAdjust = pAdjustNode->m_pParent; else /*Case3:待调整节点的兄弟节点的右孩子是黑色的,将兄弟节点置为红色, 将兄弟节点的左孩子置为黑色,并对兄弟节点进行一次右旋,然后记录新的兄弟节点*/ if (pBrother->m_pRight == nullptr || IS_BLACK_COLOR(pBrother->m_pRight)) SET_RED_COLOR(pBrother); SET_BLACK_COLOR(pBrother->m_pLeft); RRotate(pBrother); pBrother = pParentOfAdjust->m_pRight; /*Case4:待调整节点的兄弟节点是黑色的,兄弟节点的右孩子是红色的,*/ SET_COLOR(pBrother, pParentOfAdjust->m_Color); SET_BLACK_COLOR(pParentOfAdjust); SET_BLACK_COLOR(pBrother->m_pRight); LRotate(pParentOfAdjust); pAdjustNode = m_Root; else pBrother = pParentOfAdjust->m_pLeft; if (IS_RED_COLOR(pBrother)) SET_BLACK_COLOR(pBrother); SET_RED_COLOR(pBrother->m_pParent); RRotate(pBrother->m_pParent); pBrother = pParentOfAdjust->m_pRight; if ((pBrother->m_pLeft == nullptr || IS_BLACK_COLOR(pBrother->m_pLeft)) && (pBrother->m_pRight == nullptr || IS_BLACK_COLOR(pBrother->m_pRight))) SET_RED_COLOR(pBrother); pAdjustNode = pParentOfAdjust; pParentOfAdjust = pAdjustNode->m_pParent; else /*Case3:待调整节点的兄弟节点的右孩子是红色的,将兄弟节点置为红色, 将兄弟节点的右孩子置为黑色,并对兄弟节点进行一次左旋,然后记录新的兄弟节点*/ if (pBrother->m_pLeft == nullptr || IS_BLACK_COLOR(pBrother->m_pLeft)) SET_RED_COLOR(pBrother); SET_BLACK_COLOR(pBrother->m_pRight); LRotate(pBrother); pBrother = pParentOfAdjust->m_pLeft; /*Case4:待调整节点的兄弟节点是黑色的,兄弟节点的左孩子是红色的,*/ SET_COLOR(pBrother, pParentOfAdjust->m_Color); SET_BLACK_COLOR(pAdjustNode->m_pParent); if (pBrother->m_pLeft != nullptr) SET_BLACK_COLOR(pBrother->m_pLeft); RRotate(pAdjustNode->m_pParent); pAdjustNode = m_Root; if (pAdjustNode != nullptr) SET_BLACK_COLOR(pAdjustNode); return true;
删除节点的时间复杂度分析
n个节点的红黑树的高度为lg(n),删除节点前查找节点的时间复杂度为O(lgn),在查找待删除节点右子树中最小
节点值的操作时间复杂度不超过O(lgn),在删除节点后修复过程中,对于Case1,3,4至多旋转3次就结束调整了,
所以时间复杂度为O(1),Case2情形下是循环可以重复执行的唯一情况,从叶节点调整到根节点最多耗时O(lgn),
所以整个删除操作时间复杂度为lgn
下面是整个实现代码:
#pragma once enum RB_COLOR RED, BLACK ; #define IS_EMPTY(pointer) (((pointer) == nullptr)?true:false) #define SET_COLOR(pointer,color) do if ((pointer) != nullptr) (pointer)->m_Color = color; while(0) #define SET_RED_COLOR(pointer) do if((pointer) != nullptr) (pointer)->m_Color = RED; while (0) #define SET_BLACK_COLOR(pointer) do if((pointer) != nullptr) (pointer)->m_Color = BLACK; while (0) #define IS_RED_COLOR(pointer) ((((pointer)->m_Color) == RED) ?true:false) #define IS_BLACK_COLOR(pointer) ((((pointer)->m_Color) == BLACK) ?true:false) #define SET_PARENT(parent, child) if(child != nullptr) child->m_pParent = parent; #define SET_LEFT_CHILD(parent,child) do parent->m_pLeft = child; SET_PARENT(parent,child) while(0) #define SET_RIGHT_CHILD(parent,child) do parent->m_pRight = child; SET_PARENT(parent,child) while(0) #define RELINK_PARENT_AND_CHID(node) if (node->m_pParent->m_pLeft == node) node->m_pParent->m_pLeft = node->m_pLeft; else node->m_pParent->m_pRight = node->m_pLeft; template<typename T> struct tagRBNode struct tagRBNode<T> * m_pLeft; //指向左孩子 struct tagRBNode<T> * m_pRight; //指向右孩子 struct tagRBNode<T> * m_pParent;//指向父节点 RB_COLOR m_Color; //节点颜色 T m_TDataElemet; //数据域 tagRBNode(T & data, RB_COLOR bRed = RED) : m_pParent(nullptr), m_pLeft(nullptr), m_pRight(nullptr), m_TDataElemet(data), m_Color(bRed) ; /*定义模版节点类型别名*/ template<typename T> using RBNode = tagRBNode<T>; /*定义模版节点指针类型别名*/ template<typename T> using PRBNode = tagRBNode<T>*; template<typename T> class CRBTree PRBNode<T> m_Root; //指向根节点 int m_nNumOfNode; //记录节点个数 public: CRBTree(); bool Insert(T && data); bool Delete(T && data); bool Delete(T & data); private: bool LRotate(PRBNode<T> ParentOfPair); bool RRotate(PRBNode<T> ParentOfPair); bool FixAfterInsert(PRBNode<T> pNewNode); PRBNode<T> FindNodeByData(T & data); bool ReplaceNode(PRBNode<T> pBeReplaced, PRBNode<T> pReplacer); bool FixAfterDelete(PRBNode<T> pAdjustNode, PRBNode<T> pParentOfAdjust); ; template<typename T> CRBTree<T>::CRBTree() : m_nNumOfNode(0), m_Root(nullptr) /************************************************************************ // 函数名称: CRBTree<T>::Delete // 访问权限: public // 函数功能: 删除一个节点元素为data的节点 // 返回值: bool:成功返回true,失败返回false // 参数: T & data:待查找的元素 // 注意: 1.data必须是一个左值,本函数用于支持左值作为参数时调用 ************************************************************************/ template<typename T> bool CRBTree<T>::Delete(T & data) /*先根据要查找的元素从红黑树中寻找元素所在的节点*/ PRBNode<T> pDelNode = FindNodeByData(data); PRBNode<T> pAdjustNode = nullptr; PRBNode<T> pParentOfReplacer = nullptr; if (pDelNode == nullptr) /*无此节点则返回失败*/ return false; RB_COLOR bColor = pDelNode->m_Color; if (pDelNode->m_pLeft == nullptr) /*待删除节点左孩子为空则使用其右孩子节点替代待删除节点*/ pAdjustNode = pDelNode->m_pRight; pParentOfReplacer = pDelNode->m_pParent; ReplaceNode(pDelNode, pAdjustNode); else if (pDelNode->m_pRight == nullptr) /*待删除节点右孩子为空则使用左孩子节点替代待删除节点*/ pAdjustNode = pDelNode->m_pLeft; pParentOfReplacer = pDelNode->m_pParent; ReplaceNode(pDelNode, pAdjustNode); else /*待删除节点左右孩子都存在,则使用右子树中节点值最小的节点替代待删除节点*/ PRBNode<T> pMinumNode = pDelNode->m_pRight; while (pMinumNode->m_pLeft != nullptr) pMinumNode = pMinumNode->m_pLeft; pParentOfReplacer = pMinumNode->m_pParent; bColor = pMinumNode->m_Color; pAdjustNode = pMinumNode->m_pRight; if (pDelNode->m_pRight != pMinumNode) /*用于替换的节点不是待删除节点的右孩子,而是在其右孩子的左子树上*/ /*先将用于替换的节点从它原来的位置上摘出来,因为用于替换的节点是待删除节点右子树中值最小 的节点,所以用于替换的节点没有左孩子,可能有右孩子*/ ReplaceNode(pMinumNode, pMinumNode->m_pRight); /*将待删除节点的右孩子置为用于替换的节点的右孩子*/ pMinumNode->m_pRight = pDelNode->m_pRight; if (pDelNode->m_pRight != nullptr) SET_PARENT(pMinumNode->m_pRight, pMinumNode); /*替换待删除节点*/ ReplaceNode(pDelNode, pMinumNode); /*将被删除节点的左孩子置为用于替换节点的左孩子*/ pMinumNode->m_pLeft = pDelNode->m_pLeft; if (pMinumNode->m_pLeft != nullptr) SET_PARENT(pMinumNode->m_pLeft, pMinumNode); SET_COLOR(pMinumNode, pDelNode->m_Color); if (bColor == BLACK) FixAfterDelete(pAdjustNode, pParentOfReplacer); delete pDelNode; return true; /************************************************************************ // 函数名称: CRBTree<T>::FixAfterDelete // 访问权限: private // 函数功能: 删除节点后,从pAdjustNode指向的节点开始进行调整 // 返回值: bool // 参数: PRBNode<T> pAdjustNode:待调整节点位置 // 参数: PRBNode<T> pParentOfAdjust:待调整节点的父节点 // 注意: ************************************************************************/ template<typename T> bool CRBTree<T>::FixAfterDelete(PRBNode<T> pAdjustNode, PRBNode<T> pParentOfAdjust) PRBNode<T> pBrother = nullptr; while ((pAdjustNode == nullptr || IS_BLACK_COLOR(pAdjustNode)) && pAdjustNode != m_Root) if (pAdjustNode == pParentOfAdjust->m_pLeft) pBrother = pParentOfAdjust->m_pRight; if (IS_RED_COLOR(pBrother)) /*Case1:待调整节点的兄弟节点是红色,则将兄弟节点置为黑色,将父节点置为红色, 并对父节点进行一次左旋,左旋后记录新的兄弟节点*/ SET_BLACK_COLOR(pBrother); SET_RED_COLOR(pBrother->m_pParent); LRotate(pBrother->m_pParent); pBrother = pParentOfAdjust->m_pRight; if ((pBrother->m_pLeft == nullptr || IS_BLACK_COLOR(pBrother->m_pLeft)) && (pBrother->m_pRight == nullptr || IS_BLACK_COLOR(pBrother->m_pRight))) /*Case2:待调整节点的兄弟节点的左右孩子均为黑色(孩子不存在也视为黑色),将兄弟节点 置为红色,并将待调整位置指向待调整节点的父节点*/ SET_RED_COLOR(pBrother); pAdjustNode = pParentOfAdjust; pParentOfAdjust = pAdjustNode->m_pParent; else /*Case3:待调整节点的兄弟节点的右孩子是黑色的,将兄弟节点置为红色, 将兄弟节点的左孩子置为黑色,并对兄弟节点进行一次右旋,然后记录新的兄弟节点*/ if (pBrother->m_pRight == nullptr || IS_BLACK_COLOR(pBrother->m_pRight)) SET_RED_COLOR(pBrother); SET_BLACK_COLOR(pBrother->m_pLeft); RRotate(pBrother); pBrother = pParentOfAdjust->m_pRight; /*Case4:待调整节点的兄弟节点是黑色的,兄弟节点的右孩子是红色的,*/ SET_COLOR(pBrother, pParentOfAdjust->m_Color); SET_BLACK_COLOR(pParentOfAdjust); SET_BLACK_COLOR(pBrother->m_pRight); LRotate(pParentOfAdjust); pAdjustNode = m_Root; else pBrother = pParentOfAdjust->m_pLeft; if (IS_RED_COLOR(pBrother)) SET_BLACK_COLOR(pBrother); SET_RED_COLOR(pBrother->m_pParent); RRotate(pBrother->m_pParent); pBrother = pParentOfAdjust->m_pRight; if ((pBrother->m_pLeft == nullptr || IS_BLACK_COLOR(pBrother->m_pLeft)) && (pBrother->m_pRight == nullptr || IS_BLACK_COLOR(pBrother->m_pRight))) SET_RED_COLOR(pBrother); pAdjustNode = pParentOfAdjust; pParentOfAdjust = pAdjustNode->m_pParent; else /*Case3:待调整节点的兄弟节点的右孩子是红色的,将兄弟节点置为红色, 将兄弟节点的右孩子置为黑色,并对兄弟节点进行一次左旋,然后记录新的兄弟节点*/ if (pBrother->m_pLeft == nullptr || IS_BLACK_COLOR(pBrother->m_pLeft)) SET_RED_COLOR(pBrother); SET_BLACK_COLOR(pBrother->m_pRight); LRotate(pBrother); pBrother = pParentOfAdjust->m_pLeft; /*Case4:待调整节点的兄弟节点是黑色的,兄弟节点的左孩子是红色的,*/ SET_COLOR(pBrother, pParentOfAdjust->m_Color); SET_BLACK_COLOR(pAdjustNode->m_pParent); if (pBrother->m_pLeft != nullptr) SET_BLACK_COLOR(pBrother->m_pLeft); RRotate(pAdjustNode->m_pParent); pAdjustNode = m_Root; if (pAdjustNode != nullptr) SET_BLACK_COLOR(pAdjustNode); return true; /************************************************************************ // 函数名称: CRBTree<T>::Delete // 访问权限: public // 函数功能: 删除一个节点元素为data的节点 // 返回值: bool:成功返回true,失败返回false // 参数: T & & data:待查找的元素 // 注意: 1.data必须是一个右值,本函数用于支持右值作为参数时调用 ************************************************************************/ template<typename T> bool CRBTree<T>::Delete(T && data) return Delete(data); /************************************************************************ // 函数名称: CRBTree<T>::ReplaceNode // 访问权限: private // 函数功能: 用pReplacer指向的节点替换pBeReplaced指向的节点 // 返回值: bool // 参数: PRBNode<T> pBeReplaced:待替换的节点 // 参数: PRBNode<T> pReplacer用于替换的节点 // 注意: pBeReplaced不能为空 ************************************************************************/ template<typename T> bool CRBTree<T>::ReplaceNode(PRBNode<T> pBeReplaced, PRBNode<T> pReplacer) if (pBeReplaced == nullptr) return false; if (pBeReplaced == m_Root) m_Root = pReplacer; else if (pBeReplaced->m_pParent->m_pLeft == pBeReplaced) pBeReplaced->m_pParent->m_pLeft = pReplacer; else pBeReplaced->m_pParent->m_pRight = pReplacer; if (pReplacer != nullptr) pReplacer->m_pParent = pBeReplaced->m_pParent; return true; /************************************************************************ // 函数名称: CRBTree<T>::FindNodeByData // 访问权限: private // 函数功能: 查找树中是否存在元素值为data的节点 // 返回值: 返回指向该节点的指针,如果没有该节点则返回空指针 // 参数: T & data:待查找的元素 // 注意: ************************************************************************/ template<typename T> PRBNode<T> CRBTree<T>::FindNodeByData( T & data) PRBNode<T> pSearch = m_Root; while (pSearch != nullptr) if (data > pSearch->m_TDataElemet) pSearch = pSearch->m_pRight; else if (data < pSearch->m_TDataElemet) pSearch = pSearch->m_pLeft; else break; return pSearch; template<typename T> bool CRBTree<T>::FixAfterInsert(PRBNode<T> pNewNode) if (nullptr == pNewNode) return false; PRBNode<T> pAdjustNode = pNewNode; PRBNode<T> pParent = nullptr; PRBNode<T> pGrandParent = nullptr; PRBNode<T> pUncle = nullptr; while ((pParent = pAdjustNode->m_pParent) && IS_RED_COLOR(pParent)) pGrandParent = pParent->m_pParent; if (pGrandParent->m_pLeft == pParent) /*父节点为祖父节点的左孩子*/ pUncle = pGrandParent->m_pRight; if (pUncle != nullptr && IS_RED_COLOR(pUncle)) /*Case1:待调整节点的叔节点为红色,则将待调整节点的父节点和叔节点置为黑色, 并将祖父节点置为红色,并将下次待调整节点置为祖父节点*/ SET_BLACK_COLOR(pUncle); SET_BLACK_COLOR(pParent); SET_RED_COLOR(pGrandParent); pAdjustNode = pGrandParent; else if (pAdjustNode == pParent->m_pRight) /*Case2:待调整节点的叔节点为黑色(无叔节点也视为黑色), 并且待调整节点为其父节点的右孩子,则进行左旋*/ pAdjustNode = pParent; LRotate(pAdjustNode); pParent = pAdjustNode->m_pParent; /*Case3:待调整节点的叔节点为黑色(无叔节点也视为黑色), 并且待调整节点为其父节点的左孩子,则先将待调整 节点的父节点置为黑色,将祖父节点置为红色,在对 祖父节点和父节点进行一次右旋*/ SET_BLACK_COLOR(pParent); SET_RED_COLOR(pGrandParent); RRotate(pGrandParent); else /*父节点为祖父节点的右孩子*/ pUncle = pGrandParent->m_pLeft; if (pUncle != nullptr && IS_RED_COLOR(pUncle)) /*Case1:待调整节点的叔节点为红色,则将待调整节点的父节点和叔节点置为黑色, 并将祖父节点置为红色,并将下次待调整节点置为祖父节点*/ SET_BLACK_COLOR(pUncle); SET_BLACK_COLOR(pParent); SET_RED_COLOR(pGrandParent); pAdjustNode = pGrandParent; else if (pAdjustNode == pParent->m_pLeft) /*Case2:待调整节点的叔节点为黑色(无叔节点也视为黑色), 并且待调整节点为其父节点的右孩子,则进行左旋*/ pAdjustNode = pParent; RRotate(pAdjustNode); pParent = pAdjustNode->m_pParent; /*Case3:待调整节点的叔节点为黑色(无叔节点也视为黑色), 并且待调整节点为其父节点的左孩子,则先将待调整 节点的父节点置为黑色,将祖父节点置为红色,在对 祖父节点和父节点进行一次右旋*/ SET_BLACK_COLOR(pParent); SET_RED_COLOR(pGrandParent); LRotate(pGrandParent); SET_BLACK_COLOR(m_Root); return true; /************************************************************************ // 函数名称: CRBTree<T>::Insert // 访问权限: public // 函数功能: 插入新节点 // 返回值: bool:成功返回true,失败返回false // 参数: T & & data:要插入的节点元素 // 注意: 不支持重复节点值插入 ************************************************************************/ template<typename T> bool CRBTree<T>::Insert(T && data) PRBNode<T> pParentOfNewNode = nullptr; PRBNode<T> pCurrentNode = m_Root; while (pCurrentNode != nullptr) pParentOfNewNode = pCurrentNode; if (pCurrentNode->m_TDataElemet > data) /*当前节点值大于待插入的节点值则当前节点的左侧寻找插入位置*/ pCurrentNode = pCurrentNode->m_pLeft; else if (pCurrentNode->m_TDataElemet < data) /*当前节点值小于待插入的节点值则当前节点的右侧寻找插入位置*/ pCurrentNode = pCurrentNode->m_pRight; else /*树中已经有此节点值则返回失败*/ return false; PRBNode<T> pNewNode = new RBNode<T>(data); if (pNewNode == nullptr) return false; if (pParentOfNewNode == nullptr) m_Root = pNewNode; m_nNumOfNode++; else if (pParentOfNewNode->m_TDataElemet > data) pParentOfNewNode->m_pLeft = pNewNode; else pParentOfNewNode->m_pRight = pNewNode; pNewNode->m_pParent = pParentOfNewNode; m_nNumOfNode++; return FixAfterInsert(pNewNode); //************************************************************************ // 函数名称: CRBTree<T>::LRotate // 访问权限: private // 函数功能: 对一对父子节点进行左旋 // 返回值: bool:成功返回true,失败返回false // 参数: PRBNode<T> ParentOfPair:指向这对带旋转节点中的父节点 // 注意: //************************************************************************ template<typename T> bool CRBTree<T>::LRotate(PRBNode<T> ParentOfPair) PRBNode<T> RChidOfPair = ParentOfPair->m_pRight; PRBNode<T> SubRoot = nullptr; if (ParentOfPair->m_pRight == nullptr) /*待旋转的父节点无右孩子,此时无法进行左旋*/ return false; SubRoot = ParentOfPair->m_pParent; /*将这对待旋转节点中的子节点的左孩子置为待旋转节点父节点的右孩子*/ ParentOfPair->m_pRight = RChidOfPair->m_pLeft; if (RChidOfPair->m_pLeft != nullptr) RChidOfPair->m_pLeft->m_pParent = ParentOfPair; /*将这对待旋转节点中的父节点置为待旋转子节点的左孩子*/ RChidOfPair->m_pLeft = ParentOfPair; ParentOfPair->m_pParent = RChidOfPair; /*重置这对待旋转节点中的子节点的父节点*/ RChidOfPair->m_pParent = SubRoot; if (SubRoot == nullptr) m_Root = RChidOfPair; else if (ParentOfPair == SubRoot->m_pLeft) SubRoot->m_pLeft = RChidOfPair; else SubRoot->m_pRight = RChidOfPair; return true; /************************************************************************ // 函数名称: CRBTree<T>::RRotate // 访问权限: private // 函数功能: 对一堆父子节点进行右旋 // 返回值: bool:成功返回true,失败返回false // 参数: PRBNode<T> ParentOfPair:指向这对带旋转节点中的父节点 // 注意: ************************************************************************/ template<typename T> bool CRBTree<T>::RRotate(PRBNode<T> ParentOfPair) PRBNode<T> LChildOfPair = ParentOfPair->m_pLeft; PRBNode<T> SubRoot = ParentOfPair->m_pParent; if (LChildOfPair == nullptr) return false; /*将这对待旋转节点的子节点的右孩子置为带旋转父节点的左孩子*/ ParentOfPair->m_pLeft = LChildOfPair->m_pRight; if (LChildOfPair->m_pRight != nullptr) LChildOfPair->m_pRight->m_pParent = ParentOfPair; /*颠倒这对待旋转节点的父子关系*/ LChildOfPair->m_pRight = ParentOfPair; ParentOfPair->m_pParent = LChildOfPair; /*重置旋转后父节点的父节点*/ LChildOfPair->m_pParent = SubRoot; if (SubRoot == nullptr) m_Root = LChildOfPair; else if (SubRoot->m_pLeft == ParentOfPair) SubRoot->m_pLeft = LChildOfPair; else SubRoot->m_pRight = LChildOfPair; return true;
以上是关于红黑树的主要内容,如果未能解决你的问题,请参考以下文章